Ce blog a déménagé et parle maintenant uniquement anglais.

This blog has moved and now only speaks English.

blog.floriancargoet.com

See you there!

/home/florian

le blog de florian cargoet : du linux, du web et du logiciel libre



Extjs : les ajouts au prototype de Function

9 September, 2009 (22:31) | Ext JS, Webdev | Florian Cargoet

Catégorie Webdev : A propos du web, de son contenu, de ses outils...

Si vous développez en Javascript, vous savez certainement que les objets ont un prototype et qu’on peut le modifier dynamiquement. On peut ainsi ajouter des fonctionnalités aux objets standards du Javascript (la librairie Prototype en fait d’ailleurs un usage intensif). Dans cet article, je vais me concentrer sur les ajouts fait par ExtJS à Function.

Function.prototype

Petit rappel : les fonctions sont des objets même si on a tendance à l’oublier à cause de la syntaxe habituelle  :

?View Code JAVASCRIPT
function maFonction(arg1, arg2){
    return arg1+arg2;
}
 
//ou encore comme ceci
var maFonction = function(arg1, arg2){
    return arg1+arg2;
}

En fait, on pourrait aussi écrire :

?View Code JAVASCRIPT
var maFonction = new Function('arg1,arg2','return arg1+arg2;');

C’est un peu moche de travailler avec des chaines de caractères mais ça nous permet de voir que les fonctions sont bien des objets.

Les fonctions sont donc des objets et comme tout objet, elles peuvent avoir des méthodes. Vous connaissez peut-être déjà call et apply qui permettent d’appeler une fonction en spécifiant le scope de celle ci. Et bien sachez que vous pouvez ajouter vos propres méthodes via le prototype de Function. En guise d’exemple, ajoutons une méthode repeat qui permettra d’exécuter à répétition une fonction :

?View Code JAVASCRIPT
Function.prototype.repeat = function(time){
    return setInterval(this, time);
}

On peut donc désormais écrire :

?View Code JAVASCRIPT
function f(){
    console.log('f');
}
f.repeat(500);

Cette façon d’écrire ne permet pas de passer des arguments à la fonction mais nous verrons plus loin ce qu’ExtJS nous propose pour y arriver.

Les ajouts par ExtJS

ExtJS ajoute 5 méthodes à utiliser sur les fonctions :

  • createCallback
  • createDelegate
  • createInterceptor
  • createSequence
  • defer

Tout ceci est bien évidemment documenté par ExtJS mais revoir ça en français ne fera pas de mal ;)

createCallback

createCallback, comme son nom l’indique, sera utile là où vous aurez besoin d’une callback. Quand vous passez une fonction en paramètre, si celle ci ne prend pas de paramètre, tout va bien, vous indiquez simplement le nom de la fonction :

?View Code JAVASCRIPT
new Ext.Button({
    text:'Mon bouton',
    handler : maCallback
});

Mais si vous avez besoin de lui passer des paramètres, vous ne pouvez pas faire ceci :

?View Code JAVASCRIPT
new Ext.Button({
    text:'Mon bouton',
    handler : maCallback(arg1, arg2)
});

car le code “maCallback(arg1, arg2)” est alors interprété et remplacé par la valeur retournée. La solution est donc d’encapsuler l’appel à maCallback dans une fonction anonyme créée pour l’occasion :

?View Code JAVASCRIPT
new Ext.Button({
    text:'Mon bouton',
    handler : function(){
        maCallback(arg1, arg2)
    }
});

Que nous propose donc ExtJS pour simplifier tout ça ? Regardez plutôt :

?View Code JAVASCRIPT
new Ext.Button({
    text:'Mon bouton',
    handler : maCallback.createCallback(arg1, arg2)
});

maCallback.createCallback(arg1, arg2) retourne en effet une fonction équivalente à la fonction anonyme de l’exemple précédent !

Attention cependant : si vous avez déjà joué avec les Ext.Button comme nous le faisons là, vous avez sans doute déjà rencontré des problèmes de scope. Par défaut, le handler est exécuté dans le scope du Ext.Button (comprenez this == instance d’Ext.Button) et on peut ajouter scope : leScope pour fixer le scope désiré. Avec createCallback, pas le choix, le scope est fixé à window. Si vous avez besoin de préciser le scope d’exécution de votre callback, c’est de createDelegate dont vous avez besoin.

createDelegate

Le principe de createDelegate est le même que celui de createCallback sauf que cette nouvelle méthode permet de préciser le scope de la callback.

?View Code JAVASCRIPT
new Ext.Button({
    text:'Mon bouton',
    handler : maCallback.createDelegate(scope, [arg1, arg2])
});

Mais se contenter de ça, c’est ignorer une autre possibilité de createDelegate : la prise en compte des arguments passés par celui qui appelle le handler. En effet, jusque là, les fonctions créées fixaient les arguments une bonne fois pour toute et appeler handler(arg3, arg4) n’aurait rien changé, c’est toujours maCallback(arg1, arg2) qui aurait été appelée.

Le troisième paramètre de createDelegate permet de contrôler ce qui va se passer avec les arguments.

  • Par défaut, les arguments précisés à la création du delegate remplaceront tout argument passé à l’appel du delegate.
  • Mis à true, les arguments précisés à la création du delegate seront ajoutés aux arguments passés à l’appel du delegate.
  • Mis à N, les arguments précisés à la création du delegate seront insérés à la position N parmi les arguments passés à l’appel du delegate.

Un exemple pour fixer tout ça (j’ai volontairement laissé le scope à window pour me concentrer sur la gestion des paramètres) :

?View Code JAVASCRIPT
var f = function(){
    console.log(arguments);
};
var d1 = f.createDelegate(window,[1,2]);
var d2 = f.createDelegate(window,[1,2],true);
var d3 = f.createDelegate(window,[1,2],1);
d1(3,4); // [1, 2]
d2(3,4); // [3, 4, 1, 2]
d3(3,4); // [3, 1, 2, 4]

J’espère que la gestion des paramètres est un peu plus claire maintenant.

createInterceptor

createInterceptor va vous permettre de contrôler simplement l’exécution d’une fonction. Si on a une fonction maFonction qui accepte certains paramètres, on va pouvoir la remplacer par une fonction monIntercepteur qui acceptera les mêmes paramètres mais qui décidera si on doit ou non exécuter maFonction.

?View Code JAVASCRIPT
var maFonction = function(prenom){
    console.log('Bonjour '+prenom);
};
 
var monIntercepteur = maFonction.createInterceptor(function(prenom){
    return prenom!='Florian';
});
 
monIntercepteur('Pierre');//Bonjour Pierre
monIntercepteur('Florian');//maFonction n'est pas exécutée

createInterceptor permet donc d’exécuter une fonction avant une autre. Il faut savoir que l’intercepteur créé retourne le résultat qu’aurait retourné maFonction. Notez également que vous pouvez préciser un scope en second paramètre de createInterceptor si celui de maFonction ne convient pas.

createSequence

createSequence est le complément de createInterceptor : elle permet d’exécuter une fonction après une autre. Comme pour createInterceptor, les valeurs de retour sont conservées :

?View Code JAVASCRIPT
var bonjour = function(prenom){
    console.log('Bonjour '+prenom);
    return 1;
}
var bonjourAurevoir = bonjour.createSequence(function(prenom){
    console.log('Au revoir '+prenom);
    return 2;
});
bonjour('Florian'); //retourne 1 et affiche Bonjour Florian
bonjourAurevoir('Florian');//retourne 1 aussi mais affiche Bonjour Florian et Au revoir Florian

Note (valable pour createInterceptor également) : avec ces deux méthodes, vous pouvez exécuter une fonction avant ou après une autre sans rien changer à votre code si vous remplacez votre fonction de départ par la fonction générée par ces méthodes :

?View Code JAVASCRIPT
var bonjour = function(prenom){
    console.log('Bonjour '+prenom);
}
bonjour('flo');//affiche Bonjour flo
 
bonjour = bonjour.createSequence(function(prenom){
    console.log('Au revoir '+prenom);
});
bonjour('flo');//affiche Bonjour flo et Au revoir flo

defer

Dernière méthode de Function que nous offre ExtJS : defer.

J’ai commencé l’article avec l’exemple de Function.repeat qui utilisait setInterval pour répéter l’exécution d’une fonction. Et bien c’était inspiré par la méthode defer qui utilise setTimeout pour déclencher l’exécution d’une fonction après un certain délai.

?View Code JAVASCRIPT
var bonjour = function(){
    console.log('Bonjour ');
};
 
bonjour.defer(2000); //bonjour() sera exécutée dans 2 secondes (2000 ms)

J’écrivais au début de l’article qu’avec ma version de repeat on ne pouvait pas passer d’arguments et qu’on verrait plus tard comment faire. Le moment est venu : regardons defer plus en détails.

Il est possible de passer des arguments et un scope à defer qui seront utilisés pour appeler la fonction :

?View Code JAVASCRIPT
var bonjour = function(prenom){
    console.log('Bonjour '+prenom);
}
 
bonjour.defer(2000, this, ['Florian']); // delay, scope, array of args

Comment cela fonctionne ? Jetons un oeil au code source :

?View Code JAVASCRIPT
defer : function(millis, obj, args, appendArgs){
    var fn = this.createDelegate(obj, args, appendArgs);
    if(millis > 0){
        return setTimeout(fn, millis);
    }
    fn();
    return 0;
}

defer commence par créer un delegate pour fixer les arguments puis passe cette fonction à setTimeout ! En bonus, on vérifie si le délai est positif, sinon on n’exécute qu’une seule fois la fonction. Si vous vous souvenez de createDelegate vue plus haut, vous devriez voir que le paramètre appendArgs n’est pas très utile puisque setTimeout appelle la fonction sans paramètre…

On peut donc reprendre le code de notre introduction :

?View Code JAVASCRIPT
Function.prototype.repeat = function(delay,scope,args){
    var fn = this.createDelegate(scope,args);//appendArgs est inutile
    return setInterval(fn, delay);
}

Cependant s’il vous arrive plus fréquemment de ne pas avoir besoin de scope, vous pourriez préférer cette version :

?View Code JAVASCRIPT
Function.prototype.repeat = function(delay,args,scope){
    var fn = this.createDelegate(scope||window,args);//appendArgs est inutile
    return setInterval(fn, delay);
}

Conclusion

Nous avons fait le tour des ajouts au prototype de Function faits par ExtJS et j’espère que tout ça est désormais plus clair. Function n’est pas la seule classe de base qu’ExtJS complète, et si vous êtes curieux de voir ce que vous pouvez faire avec les instances de String, Array, Number ou Date, je vous renvoie à la doc d’ExtJS !

Commentaires

Commentaire de Laurent
le 18 January 2010, 2:21

clair et concis. merci.