Archives pour la catégorie jQuery

Play – Upload de fichiers multiples avec le composant jQuery File Upload (File API HTML5)

Les nouvelles APIs « HTML5 » apportent des nouveautés intéressantes comme la File API, permettant de faire du Drag n Drop de fichiers directement depuis sa machine vers une page web. Très pratique pour éviter d’utiliser les « input type file » classiques (boutons « Parcourir… »), surtout quand vous avez plusieurs fichiers à uploader.

Un bon exemple d’utilisation est celui de http://cartodb.com/ (après inscription) sur lequel on peut envoyer un fichier SIG ShapeFile qui n’est pas constitué d’un seul fichier mais de 5/6 fichiers différents. Il est plus pratique de faire glisser les 6 fichiers vers la page web que de créer une archive zip à uploader.

Bref, on va voir comment faire cela de manière simple avec jQuery et Play Framework côté serveur.

Le composant jQuery File Upload

La File API n’est pas facile à manipuler, avec notamment pas mal de cas spéciaux et des comportements qui diffèrent selon les navigateurs. Il y a donc plusieurs librairies qui ont émergé comme uploadify (merci @clacote). Pour ma part, j’ai trouvé ce composant assez puissant qui fonctionne plutôt bien, le jQuery File Upload:

http://blueimp.github.com/jQuery-File-Upload/

Première chose à faire, aller récupérer le code source (et lire les très bonnes docs accessoirement):

https://github.com/blueimp/jQuery-File-Upload/downloads

Application Play Framework 1.x

Si vous n’avez pas téléchargez Play, rendez-vous sur le site officiel. Prenez la 1.2.5:

http://www.playframework.org/download

Dans ce tutorial, on utilisera la 1.x (v1.2.5 lors de l’écriture de cet article). Créez une application si vous n’en avez pas déjà une:

play new tuto-upload

Puis lancez la commande qui permet d’ouvrir le projet dans votre IDE préféré (« play idealize » pour IntelliJ par exemple).

Copiez les fichiers du composant jQuery File Upload que vous avez téléchargé dans le dossier /public/javascripts de votre application. Prenez tout ce qu’il y a dans « /js/ » et copiez-le dans /public/javascripts/ ». Prenez aussi ce fichier qui va se charger de faire du templating pour jQuery (jQ Templates):

http://blueimp.github.com/JavaScript-Templates/tmpl.min.js

Mettez ensuite le contenu de /css/ dans /stylesheets/. Au passage, allez aussi chercher le CSS de Twitter Bootstrap, qui va donner une bonne tête à votre page:

http://twitter.github.com/bootstrap/assets/css/bootstrap.css

Le template de base de Play qui sert de squelette pour vos pages (app/views/main.html) contient déjà jQuery, rien à changer de ce côté là. On va travailler dans le fichier index.html directement.

On va commencer par rajouter les links vers les fichiers JS que l’on vient de copiez en utilisant les hooks « moreScripts » et « moreStyles » + les balises Groovy #{script} et #{style} qui se chargent de setter les attributs obligatoires autres que src:

#{extends 'main.html' /}
#{set title:'Home' /}
#{set 'moreScripts'}
    #{get 'moreScripts' /}
    #{script 'vendor/jquery.ui.widget.js'/}
    #{script 'jquery.iframe-transport.js'/}
    #{script 'tmpl.min.js'/}
    #{script 'jquery.fileupload.js'/}
    #{script 'jquery.fileupload-fp.js'/}
    #{script 'jquery.fileupload-ui.js'/}
    #{script 'locale.js'/}
#{/set}
#{set 'moreStyles'}
    #{get 'moreStyles' /}
    #{stylesheet 'jquery.fileupload-ui.css'/}
    #{stylesheet 'bootstrap.css'/}
#{/set}

Création de l’action d’upload

On commence par ajouter une route dans le fichier /conf/routes:

# Home page
GET     /                                       Application.index
POST    /upload                                 Application.upload

On est bien en POST car on souhaite faire de l’upload. On va créer l’action dans notre controller Application.java:

package controllers;

import play.*;
import play.mvc.*;

import java.io.File;
import java.util.*;

import models.*;

public class Application extends Controller {

    public static void index() {
        render();
    }

    public static void upload(File[] files) {

    }

}

Maintenant, on va créer notre formulaire à partir de cette action dans notre template Groovy (index.html, là où on a mis les moreScript). Pour cela, Play dispose d’un tag #{form} qui permet de faire du reverse routing et de ne pas avoir à taper l’url d’upload (attribut « action ») à la main, pratique:

#{extends 'main.html' /}
#{set title:'Home' /}
#{set 'moreScripts'}
    #{get 'moreScripts' /}
    #{script 'vendor/jquery.ui.widget.js'/}
    #{script 'jquery.iframe-transport.js'/}
    #{script 'jquery.fileupload.js'/}
    #{script 'jquery.fileupload-fp.js'/}
    #{script 'jquery.fileupload-ui.js'/}
    #{script 'locale.js'/}
#{/set}
#{set 'moreStyles'}
    #{get 'moreStyles' /}
    #{stylesheet 'jquery.fileupload-ui.css'/}
#{/set}

#{form @Application.upload() , id:'fileupload', enctype:'multipart/form-data'}
#{/form}

On lui a juste ajouté un simple id pour plus tard et un enctype qui dit que l’on va faire de l’upload.

On va lancer notre application pour voir ce que cela donne. Lancez la commande :

play run

Le serveur Play est lancé sur le port 9000. Rendez-vous donc sur :

http://localhost:9000/

Si tout va bien, vous devriez arriver sur une page blanche. Allez voir la source de la page et vous verrez que votre formulaire est bien là:

<form action="/upload" method="POST" accept-charset="utf-8" enctype="multipart/form-data" id="fileupload" ><input type="hidden" name="authenticityToken" value="58abd56b3f8b6c62a0e0180b59cc65e1e74a6b43">

</form>

Maintenant, on va ajouter le composant jQuery File Upload.

Lire la suite

Créer un EventDispatcher en JavaScript similaire à la version ActionScript 3

UPDATE: Grâce à un commentaire de Lionel, j’ai découvert JS-Signals qui émule exactement le comportement que j’essayai de recréer (en beaucoup mieux). Je laisse l’article pour la forme mais si vous chercher à réaliser ce genre de fonctionnalité, dirigez vous vers JS-Signals: http://millermedeiros.github.com/js-signals/

Dans le dernier article, j’ai tenté d’expliqué comment rendre son application complètement modulaire grâce à un modèle évènementiel emprunté aux applications Flex / ActionScript 3:

Modifier le comportement d’une application modulaire grâce à des évènements

Comme expliqué à la fin de l’article, il n’existe pas de solution « native » en JavaScript pour écrire ce genre de comportement. Donc j’ai pris mon IntelliJ, 2h de temps, un très bon mix dubstep de Cutline et j’ai écrit un EventDispatcher en JavaScript similaire à celui que l’on peut utiliser en ActionScript.

Vous pouvez parachuter ce code n’importe où dans votre code, il n’y a aucune dépendance vers jQuery ou autre librairie:

 var util = util || {};
/**
 *
 * Lightweight Event Dispatcher class, similar to the EventDispatcher class in ActionScript 3
 * Supports multiple listeners, priorities and stop propagation.
 * Can be used as a local event dispatcher for a component (using composition) or as a pub/sub mechanism if global
 *
 * Annoted for Google Closure Compiler (http://code.google.com/closure/compiler/docs/js-for-compiler.html)
 * @example var bus = new util.EventDispatcher(this);
 * @param {Object} context Scope that will be passed to the event listener callback
 * @author Fabien Nicollet - fnicollet@gmail.com
 * @constructor
 */
util.EventDispatcher = function (context) {
    this.events = [];
    this.context = context;
};
/**
 * Internal map of events
 * @private
 * @type {Array.<Object>}
 */
util.EventDispatcher.prototype.events = null;
/**
 * Internal reference to the original context
 * @private
 * @type {Object}
 */
util.EventDispatcher.prototype.context = null;
/**
 * Add an event listener (callback for a specific event type.
 * Call return false if a listener to stop the event propagation
 * @example bus.addEventListener('someEventType', onSomeEventType);
 * @param {string} type Event type, typically a String that will be stored in your event class
 * @param {function(event)} callback Reference to the event handler
 * @param {Object=} context Optionnal scope for the event handler
 * @param {number=} priority Positive Number (0 by default). The higher the priority is, the sooner the event handler gets called
 */
util.EventDispatcher.prototype.addEventListener = function (type, callback, context, priority) {
    // default
    priority = priority || 0;
    // add an entry for this event type if not in the map already
    this.events[type] = this.events[type] || {};
    var listenerToInsert = {context:context, callback:callback, priority:priority};
    // same for listeners map (Array) for this event type
    if (this.events[type].listeners) {
        // insert at the right spot
        var listeners = this.events[type].listeners;
        var inserted = false;
        for (var i = 0, l = listeners.length; i < l; i++) {
            var listener = listeners[i];
            var eventPriority = listener.priority;
            if (priority < eventPriority) {
                listeners.splice(i, 0, listenerToInsert);
                inserted = true;
                break;
            }
        }
        if (!inserted) {
            listeners.push(listenerToInsert);
        }
    } else {
        this.events[type].listeners = [listenerToInsert];
    }
};
/**
 * Returns wether an event listener is registered for the specified event type (callback parameter specified) or if there is at least one listener specified for the event type (no callback parameter specified)
 * @example bus.hasEventListener('someEventType', onSomeEventType);
 * @example bus.hasEventListener('someEventType');
 * @param {string} type
 * @param {function(Object)=} callback
 * @return {boolean} true if an event listener is registered for the specified event type (callback parameter specified) or true if there is at least one listener specified for the event type (no callback parameter specified)
 */
util.EventDispatcher.prototype.hasEventListener = function (type, callback) {
    var listeners = this.events[type] ? this.events[type].listeners : null;
    if (!listeners) {
        return false;
    }
    // if no callback is provided, check if any callback is defined for this event
    if (!callback) {
        return listeners.length > 0;
    }
    // looking for a specific event
    for (var i = 0, l = listeners.length; i < l; i++) {
        var listener = listeners[i];
        if (listener.callback === callback) {
            return true;
        }
    }
    return false;
};
/**
 * Remove an event handler for a specific type. If no callback is specified, all the event listeners for this event type are removed
 * @example bus.removeEventListener('someEventType', onSomeEventType);
 * @example bus.removeEventListener('someEventType');
 * @param {string} type
 * @param {function(Object)=} callback
 */
util.EventDispatcher.prototype.removeEventListener = function (type, callback) {
    var listeners = this.events[type] ? this.events[type].listeners : null;
    if (!listeners || listeners.length < 1) {
        return false;
    }
    // not defining a callback = remove all listeners
    if (!callback) {
        this.events[type].listeners = [];
        return true;
    }
    for (var i = 0, l = listeners.length; i < l; i++) {
        var listener = listeners[i];
        if (listener.callback === callback) {
            listeners.splice(i, 1);
            return true;
        }
    }
    return false;
};
/**
 * Dispatch and event on the event dispatched that will be caught by one of the event listener if attached.
 * @example bus.dispatchEvent({type:'someEventType, data:{someData:'someDataValue'}});
 * @param {Object} event Any Object with a type property. Data can be passed to the event handler if there is a data property on the event object
 */
util.EventDispatcher.prototype.dispatchEvent = function (event) {
    var type = event.type;
    // default
    var listeners = this.events[type] ? this.events[type].listeners : null;
    if (!listeners || listeners.length < 1) {
        // no listeners for this event
        return;
    }
    for (var i = listeners.length - 1; i >= 0; i--) {
        var listener = listeners[i];
        var callback = listener.callback;
        // merge listener data and event triggered data
        var callbackContext = listener.context ? listener.context : this.context;
        var result = callback.call(callbackContext, event);
        if (result !== undefined && !result) {
            break;
        }
    }
};

Vous pouvez aussi retrouver cette classe sur GitHub:

https://github.com/fnicollet/HTML5Tutorial/blob/master/HTML5Tutorial/util/EventDispatcher.js

Et la version minifiée (1.2K):

https://raw.github.com/fnicollet/HTML5Tutorial/master/HTML5Tutorial/util/EventDispatcher.min.js

Cette « classe » JavaScript n’est pas strictement équivalente à EventDispatcher, car il m’a fallu m’adapter à certaines subtilités du langage comme la gestion du scope (contexte d’exécution de la callback).

Veuillez noter que cette version JavaScript fonctionne par composition (ajout d’une propriété de type EventDispatcher dans votre classe) plutôt que par héritage comme en ActionScript.

Exemples d’utilisation

Voici quelques exemples (7) pour vous montrer l’utilité de cette petite classe et les différences avec la version AS3. Ici, je crée un objet EventDispatcher au même niveau que le code de test pour simplifier les exemples. Veuillez notez que comme dans l’exemple 7, l’objet de type EventDispatcher peut-être une propriété de n’importe quel objet, que ce soit votre composant, votre « Mediator » ou votre « Facade ».

Lire la suite

Modifier le comportement d’une application modulaire grâce à des évènements

Par certains aspects, le développement JavaScript et ActionScript (Flex) ont certains points communs. Vous pouvez par exemple vous inscrire à certains évènements sur un composant comme « blur » sur un composant « input ». De la même manière, vous pouvez vous inscrire sur l’évènement « focusOut » sur un composant TextInput en Flex.

Les éléments du DOM envoient donc des évènements que vous pouvez utiliser dans votre application. Admettons que vous utilisiez jQuery, vous aurez une instruction bind du genre:

$('#foo').bind('click', function() {
  alert('User clicked on "foo."');
});

Tout ça c’est bien pratique mais si vous avez suivi mon article sur l’approche « composant » lors du  développement d’application web, vous avez compris que ce qui est encore plus intéressant est de pouvoir propager ses propres évènements. Vous ne pouvez pas vous restreindre aux évènements du DOM, vous devez propager vos propres évènements « métier ».

Pour cela, jQuery a aussi une méthode nommée trigger:

http://api.jquery.com/trigger/

Voici donc le même exemple mais cette fois-ci, on déclenche un évènement « click » à la main :

$('#foo').bind('click', function() {
      alert($(this).text());
    });
    $('#foo').trigger('click');

La méthode trigger prend en premier paramètre, le type de l’évènement. Vous n’êtes donc pas limité aux types d’évènement du DOM. Voyons maintenant quelles fonctionnalités nous apporte la classe EventDispatcher en ActionScript

EventDispatcher:  la version ActionScript 3

La classe EventDispatcher est sûrement une des classes les plus utilisées en ActionScript. Le plus souvent, on ne le voit pas car le tout se fait par héritage. Regardez donc la documentation qui donne toutes les classes héritant de EventDispatcher:

http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/events/EventDispatcher.html

Rajoutez en plus tous les objets graphiques car ils héritent de DisplayObject / UIComponent et vous verrez que c’est une classe centrale. Grâce à un objet de type EventDispatcher, vous pouvez:

  • Propager des évènements avec dispatchEvent
  • Ajouter un écouteur d’évènement avec addEventListener
  • Supprimer un écouteur d’évènement avec removeEventListener
  • Savoir si un type d’évènement à un écouteur avec hasEventListener

Stopper la propagation d’un évènement pour un code plus évolutif / modulaire

Dans un des derniers billets, je parlais aussi du chargement de module dans une application. En chargeant du code de manière dynamique, on peut ajouter des fonctionnalités mais l’on peut aussi modifier le comportement de l’application. Le « challenge » est que vous devez modifier le comportement de l’application sans que les composants impactés aient connaissance de ce que le module va faire.

La solution est de laisser des points d’entrée, alias « hooks ».

Il y a plusieurs manières de laisser des « hooks » dans le code, plus ou moins bonnes.

Classes « dynamic » qui peuvent être redéfinies à la volée

Au temps de Flex 2 chez Business Geografic, on avait commencé par rendre certaines classes dynamic (par le mot clé public dynamic class … que vous ne connaissez peut-être pas, mais c’est sûrement mieux comme ça!). De base, l’ActionScript est un langage dynamique (prototype, basé sur les specs ECMA) et si vous créez un « Object », vous pouvez lui ajouter dynamiquement n’importe quel propriété, de n’importe quel type. Viens ensuite le système de classes. Ces objets ne sont plus dynamiques et les méthodes / propriétés accessibles sont définies au niveau de la classe et vous ne pouvez plus en ajoutez / supprimer par la suite (sauf si vous utilisez le mot-clé « dynamic »).

En JavaScript, vous pouvez redéfinir le prototype de n’importe quel objet et donc ajouter des propriétés / méthodes à la volée. Il n’y a pas ce système de classes qui « scelle » à tout jamais les propriétés d’un objet en particulier.

Admettons que vous ayez de base une classe A qui a une méthode « getClients() » dans votre application. Si vous souhaitez modifier le comportement de cette méthode lorsque vous chargez un module, vous pourriez être tenté de remplacer la méthode getClients() de A par une méthode contenue dans votre module.

C’est une mauvaise décision pour plusieurs raisons:

  • Vous allez créer des liaisons fortes dans votre code entre le module et l’application qui l’accueille
  • Vous venez de supprimer complètement le traitement qui était fait dans la méthode getClients() originale. Vous n’avez pas le choix de faire autrement.
  • Si vous voulez faire un traitement avant ou après l’appel à getClients dans votre module et garder le traitement original, vous pourrez peut-être en faisant une feinte moche (en transformant getClients en une méthode proxy en gros).
  • Si vous chargez une 2ème module qui souhaite lui aussi modifier getClients, vous allez supprimer la modification de votre premier module.

Bref, ce n’est pas propre et vous allez vous en mordre les doigts plus tard lorsque vous devrez maintenir vos modules, voire lancer des tests.

Rendre son code modulaire grâce aux évènements

La solution qui s’est révélée être la plus viable pour le développement et la maintenance (par expérience) consiste à utiliser un modèle évènementiel. Ce sont en fait les évènements propagés par chaque composants qui seront vos points d’entrées pour vos modules.

Prenons un exemple simple. Je vais expliquer l’exemple tel qu’on le fait en AS3, pour que vous puissiez voir où cela coince en JavaScript.

Vous avez une application et lors d’une action utilisateur, vous allez ouvrir une fiche d’information sur un client, représenté par un objet Client. On va assumer que dans votre application, vous avez un objet qui s’en occupe, que l’on va appeler « FicheInfoManager ». Sur ce FicheInfoManager, vous avez une méthode qui a la signature suivante:

package {
  import flash.events.EventDispatcher;

  public class FicheInfoManager extends EventDispatcher {
    public function FicheInfoManager() {
      super();
    }

    public function showInfo(client:Client):void {
      // open
    }
  }
}

Pour ouvrir une fiche d’information, vous allez récupérer une instance de FicheInfoManager et appeler showInfo(client), très bien.

Vous avez maintenant un module qui charge une classe « SpecialClientModule », qui va devoir, pour un client particulier, ouvrir une fiche d’information différente de la fiche d’information de base. Si vous re-définissez la méthode showInfo à la volée, vous devrez prendre en charge l’affichage de la fiche d’information par défaut, vous aurez donc une duplication du code. Bad.

On va introduire notre « hook » où l’on en a besoin, c’est à dire lors de la demande de fiche d’information. Au lieu d’appeler directement la méthode « showInfo », on va propager un évènement de type « onShowInfo » sur la classe FicheInfoManager. Voici la définition de l’évènement:

package bg.event {
  import flash.events.Event;

  public class FicheInfoEvent extends Event {

    public static var ON_SHOW_INFO:String = "onShowInfo";
    public var client:Client;

    public function FicheInfoEvent(type:String, client:Client) {
      super(type);
      this.client = client;
    }
  }
}

Notez que pour pouvoir propager cet évènement, FicheInfoManager doit hériter de la classe EventDispatcher. L’instance de FicheInfoManager va s’inscrire à cet évènement et exécuter le comportement par défaut, c’est-à-dire exécuter la méthode showInfo:

package {
  import bg.event.FicheInfoEvent;

  import flash.events.Event;
  import flash.events.EventDispatcher;

  public class FicheInfoManager extends EventDispatcher {
    public function FicheInfoManager() {
      super();
      addEventListener(FicheInfoEvent.ON_SHOW_INFO, onShowInfoHandler);
    }

    protected function onShowInfoHandler(event:FicheInfoEvent):void {
      var client:Client = event.client;
      showInfo(client);
    }

    public function showInfo(client:Client):void {
      // open
    }
  }
}

Pour l’instant, vous avez exactement le même comportement qu’avant sauf que pour demander une fiche d’information, vous n’allez pas appeler directement showInfo mais propager un évènement. Vous utilisez votre point d’entrée comme ceci:

// au lieu de
ficheInfoManager.showClient(client);
// on a
ficheInfoManager.dispatchEvent(new FicheInfoEvent(FicheInfoEvent.ON_SHOW_INFO, client));

Maintenant, lors du chargement de « SpecialClientModule », vous allez vous inscrire à l’évènement « onShowInfo » de FicheInfoManager, mais avec une priorité plus haute (4 dans notre exemple, supérieur à 0 la valeur par défaut de addEventListener), ce qui est possible en ActionScript:

package {
  import bg.event.FicheInfoEvent;

  import flash.events.Event;

  public class SpecialClientModule {
    public function SpecialClientModule() {
      // récupérer une instance de FicheInfoManager sur votre application
      var ficheInfoManager:FicheInfoManager = ...;
      ficheInfoManager.addEventListener(FicheInfoEvent.ON_SHOW_INFO, onShowSpecialInfo, false, 4);
    }

    protected function onShowSpecialInfo(event:FicheInfoEvent):void {
      var client:Client = event.client;
    }
  }
}

L’écouteur d’évènement de SpecialClientModule sera donc appelé avant celui de FicheInfoManager. A ce moment-là, SpecialClientModule a le pouvoir et peut faire plusieurs choses.

Remplacer le comportement initial de manière arbitraire

Le comportement par défaut de FicheInfoManager ne sera effectué que s’il reçoit l’évènement onShowInfo. Grâce aux méthode stopPropagation() et stopImmediatePropagation(), SpecialClientModule peut stopper la propagation de l’évènement, qui ne va donc jamais arriver à FicheInfoManager:

protected function onShowSpecialInfo(event:FicheInfoEvent):void {
  var client:Client = event.client;
  // lalala, you won't get it anymore
  event.stopImmediatePropagation();
  event.stopPropagation();
}

Remplacer le comportement initial selon une certaine règle

Dans notre exemple initial, SpecialClientModule ne devait supprimer le traitement par défait que pour un certain client. Avant  de stopper la propagation, on peut donc faire une vérification:

protected function onShowSpecialInfo(event:FicheInfoEvent):void {
  var client:Client = event.client;
  if (client.name == "Fabien Nicollet"){
	event.stopImmediatePropagation();
	event.stopPropagation();
	// show something else instead
  }
}

Ici, on a donc introduit un traitement spécial, dans un cas particulier, sans jamais impacter le traitement original de l’application. Cool, non ?

Effectuer un traitement avant l’appel par défaut

Admettons que votre module doive logger toutes les ouvertures de fiches d’information, ce qui n’est pas fait par défaut par votre application (fonctionnalité Analytics payante par exemple). L’exemple est un peu nul mais vous avez compris. Il vous suffit de faire:

protected function onShowSpecialInfo(event:FicheInfoEvent):void {
  doSomeLogging(client);
  // let it be
}

Le problème de cette approche en JavaScript

Grâce à cette technique, vous avez introduit du code autour du comportement par défaut, sans jamais le modifier. Vous aurez peut-être aussi remarqué que cela fonctionne avec un module, mais aussi avec plusieurs modules. Tout est en fait une question de priorité, ce qui va décider qui sera appelé et dans quel ordre. L’ordre d’appel est important car vous pouvez couper la propagation d’un évènement.

Voilà donc comment cela fonctionne en ActionScript 3 / Flex. Voulant effectuer le même genre de manipulation en JavaScript, j’ai rencontré pas mal de problèmes:

Pas d’objet « EventDispatcher » en JavaScript

Il n’existe pas vraiment de classe spécialisée pour la gestion d’évènement comme EventDispatcher. Une solution intéressante est que vous pouvez  créer un « faux » objet jQuery qui va vous servir à faire transiter les évènements, car c’est dans les cordes de n’importe quel objet jQuery.

Cette méthode est expliquée dans cet article:

http://dailyjs.com/2009/11/14/jquery-custom-events/

Au delà de l’aspect « bricolage » de cette technique, on rencontre d’autres problèmes.

Pas de concept de priorité sur les évènements en JavaScript

Le concept de priorité n’existe pas sur les évènements en JavaScript. Les écouteurs sont appelés suivant leur ordre d’ajout. C’est moche, vous ne voulez pas qu’un module soit plus prioritaire car il a été chargé après un autre.

Les méthodes preventDefault() et stopPropagation() ne sont utiles que pour les évènements du DOM

Il existe bien des méthodes preventDefault(), stopPropagation() et stopImmediatePropagation() sur les évènements en JavaScript mais ceux-ci ne sont utiles que pour les évènements qui proviennent du DOM, comme pour stopper un « form » au moment du « submit ». Pour les évènements que l’on va créer soi-même, comme il n’y a pas de concept de priorité, ces méthodes ne peuvent être utilisées.

Les mécanismes de « pub / sub » ont les mêmes problèmes

Il y a pas mal de librairies pour faire du publisher / subscriber en JavaScript. Vous pouvez vous inscrire sur un « topic » et écouter ce qu’il s’y passe. Pratique pour faire du « broadcast » mais dans notre cas, l’EventDispatcher doit pouvoir rester local et pas global à l’application.

Solution : Créer son propre EventDispatcher JavaScript

On voit bien que le coeur du problème est cette classe EventDispatcher inexistante en JavaScript. L’alternative qui consiste à utiliser un objet jQuery n’est pas suffisante car il nous manque des fonctionnalités.

Il ne nous reste plus qu’à créer notre propre EventDispatcher!

Comme j’en avais besoin, j’ai écrit cet EventDispatcher JavaScript. Et oui, cet article était en fait une introduction! :)

Les technologies qui seront abordées sur html5-tutorial

Quand on veut parler de nouvelles technologies « web », il y a de quoi faire. Depuis des mois, de nombreuses librairies ont émergé, souvent pour contourner les bugs/incompatibilités des navigateurs (jQuery) ou pour simplifier le développement d’applications web (Backbone, Knockout, AngularJS, …).

Contrairement à Flex, il n’y a pas de framework « full stack » côté client. Vous devrez donc décider quelles librairies vous allez utiliser, vous assurer qu’elles « cohabitent » bien, assembler, mélanger et vous obtiendrez peut-être une application web robuste.

Il y a des nombreuses applications web comme GMail, Google Maps, WordPress que je trouve remarquables et que j’aimerai savoir réaliser.

Au cours des derniers mois, j’ai pu tester plusieurs librairies afin de me donner une idée. Vous vous en doutez, il est impossible de tout tester dans des cas d’utilisation « réels », faute de temps. L’analyse que je vais faire n’engage donc que moi et l’expérience que j’ai eu avec ces librairies. N’hésitez pas à utiliser les commentaires si vous pensez que ce que je dis devrait être corrigé.

Mes critères sont assez simples et personnels. A noter que certains sont aussi basés sur ce que je sais déjà faire. Par exemple, je suis sûr que l’on peut faire des merveilles en PHP mais je n’y connais pas grand chose alors je ne vais pas essayer de faire le malin là dessus.

Je ne suis pas forcement intéressé par la « beauté » d’un langage / framework et je ne pense pas que le code soit « de l’art » ou de la « poésie ». Flex m’a apporté une facilité de développement dont j’ai du mal à me passer. En gros, créer des composants, taper du code, lancer en debug, faire des watch dans Eclipse ou du pas à pas dans le code, corriger et cela jusque ça marche.

Voici donc les critères qui me tiennent à coeur:

Productivité

Dans l’ensemble, il faut que la solution soit productive. C’est-à-dire que l’on arrive à créer du contenu sans y passer des heures. Ne pas passer plusieurs minutes pour tester une application, la mettre en production ou pour compiler X ou Y.

Il faut que ce contenu soit facile à maintenir et qu’il puisse être facilement réutilisé par les membres de l’équipe. Pour vous donner un exemple, la notion de « composant » en Flex est primordiale, et nous permettait de réaliser des applications complexes parfois sans effort. L’application du DRY (Don’t Repeat Yourself) et du KISS (Keep It Simple and Smart/Stupid) de manière globale permet de faciliter la maintenance et de limiter les effets de bord.

Dans l’ensemble, il faut que le développement, aussi bien côté serveur que côté client soit rapide.

Debugging

Dans une journée, je passe *beaucoup* de temps à debugger, par rapport au temps nécessaire pour écrire le code. Il faut que je puisse faire du pas-à-pas facilement, qui je puisse inspecter une valeur dans son contexte et que je puisse naviguer facilement dans mon code. Cela relève souvent du travail de l’environnement de développement utilisé (IDE) mais aussi du navigateur qui propose une partie de ces outils.

Multi-plateforme

Flash Player ne tourne pas sur le navigateur de l’iPad et cela est très embêtant, surtout d’un point de vue commercial. Sur Android, Flash Player est à la limite adapté pour afficher des vidéos mais pas du tout pour le reste. « HTML5 » fait la promesse de pouvoir s’adapter à toutes les plateformes, presque de manière « magique ». On sait tous que ce n’est pas tout à fait vrai.

Dans l’idéal, il faut une solution qui permette de s’adapter facilement à une plateforme. Ou tout du moins, qu’elle n’apporte pas de blocage pour le faire.

Un minimum de « couches »

Par expérience, plus il y a de « couches » logicielles (soit en nombre de librairies utilisées, soit en nombre de « X compilé en Y ») impliquées dans un projet, plus il est complexe à gérer. D’abord parce que la compatibilité transversale des versions est assez difficile à assurer mais aussi parce que l’introduction d’un nouveau langage / DSL doit ensuite être appris par les membres de votre équipe.

Le but est donc de minimiser le nombre de langages, sans allez dans l’excès, pour pouvoir profiter pleinement des capacités de chaque langage, individuellement.

Communauté

La « Flash Platform » a un gros atout, c’est son énorme communauté, avec de nombreuses ressources déjà disponibles sur Internet. Cela est du en partie à son âge, mais aussi à pas mal de passionnés.

Une communauté active permet d’obtenir de l’aide rapidement et de trouver des librairies ou des bouts de code de qualité sur le net.

Voici donc un florilège de ces nouvelles technos que j’ai pu utiliser.


Play! Framework

Certainement mon plus gros « coup de coeur » ces derniers temps. Si vous ne connaissez pas Play! Framework, je vous conseille très vivement de suivre le tutorial officiel:

http://www.playframework.org/documentation

Ce n’est pas un simple « Hello, world » mais un tutorial bien plus long qui vous permettra de découvrir les nombreuses facettes de Play. Vous allez ainsi réaliser un « blog engine », avec personnalisation, soumission de formulaire, administration, internationalisation, tests et mise en production. Pour l’avoir fait de bout en bout, cela prend moins d’une heure et permet de bien comprendre les mécanismes qui se trouvent derrière Play.

Ici, on se trouve donc côté serveur mais même si on fait beaucoup de bruit sur tout ce qui se passe côté client, le serveur reste primordial. D’autant plus que tous les moteurs JavaScript que vous utiliser vos clients ne sont pas toujours des Webkits sous stéroïdes. Vous ne pourrez pas réaliser de « Rich Internet Application » sans faire une grosse partie du travail côté serveur.

Voici ce que j’aime chez Play!:

Aucun temps de compilation

Play! s’appuie sur le mécanisme de « hot reload » d’Eclipse, qui permet de recharger du code directement dans la JVM. On peut donc modifier les sources de son projet, rafraîchir la page dans son navigateur et voir la modification.

Personnellement, ça m’a rappelé ma (toute petite) expérience que j’ai eu en PHP, et le plaisir de ne pas devoir à recharger un Tomcat ou à lancer un build install Maven pour changer 3 caractères. Productivité maximale.

Possibilités de debug

Même si tout est rechargé à la volée, on peut toujours effectuer le debug dans son IDE, cela fonctionne sans problème. Rajoutez en plus les messages d’erreurs « humainement lisibles » générés pas Play qui ne vous donne pas simplement une grosse stack trace et vous obtenez un debug bien plus aisé.

En plus de cela, on peut voir que dans la version 2.0 de Play, Google Closure Compiler sera utilisé et affichera même des warnings/erreurs de compilation JavaScript directement dans le navigateur.

Encore une fois, très bonne expérience pour le développeur.

Le templating

Je vous rabâche ça depuis un moment mais j’essaie de trouver une sorte d’équivalent aux composant Flex. Et pour l’instant, ce qui y ressemble le plus, c’est peut-être le système de templating (côté serveur) de Play. Il est en effet possible d’isoler une partie du code dans un template (ou « tag » dans le jargon Play) mais aussi d’avoir des templates dans des templates pour la création de page complexes.

Dans sa version 1.x, Play utilise Groovy pour son engine de templating. Groovy est un langage simple, qui tourne sur la JVM. Pour ce que l’on utilise dans les templates (if / for principalement), il n’y a pas besoin d’avoir une réelle maîtrise de ce langage. Si vous connaissez un peu Java, c’est presque « naturel ».

Système de build et de dépendance

Le système de build est très important dans le cycle de développement d’une application « entreprise ». Le plus répandu et bien sûr Apache Maven mais je pense ne pas me tromper en vous disant que tous les développeurs qui y ont touché ont perdu quelques cheveux.

En version 1.x, le système de dépendances de Play est constitué d’un simple fichier dependencies.yml à plat qui parait du coup très simple et plus facile à manipuler que les XML de 5 pages de Maven.

Sur ce point, je ne suis pas sûr de savoir si c’est une bonne chose. Tous nos projets Java chez Business Geografic utilisent Maven et même si cela nous a pris des semaines à être configuré correctement, il est très apprécié par les développeurs de pouvoir ajouter une dépendance et de laisser Maven se débrouiller pour les dépendances transitives au travers des repository Maven déjà populaires. Par contre, le fort ralentissement d’Eclipse au démarrage est une plaie.

Dans sa prochaine version (2.x), Play intègre un nouveau système de build nommé « SBT » (Simple Build Tool) que je ne connais pas du tout. J’espère que cela pourra permettre de gérer les projets « mixtes », car cela m’a posé beaucoup de problèmes lors de mes tests en 1.x.

Une communauté active

Le Google Groups de Play est *très* actif:

https://groups.google.com/forum/?hl=fr#!forum/play-framework

Beaucoup de messages par jour et des réponses souvent pertinentes. J’ai personnellement laissé quelques questions dessus lors de mon apprentissage et j’ai reçu les premières réponses 20 minutes après avoir posté mon message.

De même, Play est basé sur un système modulaire, avec déjà de nombreux modules développés par la communauté (100+ je crois):

http://www.playframework.org/modules

Un très bon point pour les développeurs. Côté tutoriaux, c’est un peu plus léger mais la très bonne documentation officielle comble largement ce manque.

Lire la suite