Introduction aux wrappers

Parent Previous Next



Introduction aux wrappers



La problématique

Il se peut que, par moments, vous ayez besoin de créer une méthode supplémentaire pour certains objets natifs du Javascript. Pour cela, rien de plus simple, il vous suffit de l'ajouter au prototype de l'objet souhaité. Exemple :

Array.prototype.myMethod = function() {

    // Votre code…

};

 

[].myMethod(); // La méthode myMethod() est maintenant disponible pour toutes les instances de tableaux


Mais est-ce que vous vous souvenez de ce qui a été dit dans le premier chapitre de cette partie du cours ? Voici un petit rappel :


Citation


En théorie, chaque objet peut se voir attribuer des méthodes via prototype. Mais en pratique, si cela fonctionne avec les objets natifs génériques comme StringDate,ArrayObjectNumberBoolean et de nombreux autres, cela fonctionne moins bien avec les objets natifs liés au DOM comme NodeElement ou encore HTMLElement, en particulier dans Internet Explorer.

De plus, la modification d'un objet natif est plutôt déconseillée au final, car vous risquez de modifier une méthode déjà existante. Bref, nous avons besoin de méthodes et de propriétés supplémentaires mais nous ne pouvons pas les ajouter sans risques, alors comment faire ?


La solution


Il existe une solution nommée « wrapper ». Un wrapper est un code qui a pour but d'encadrer l'utilisation de certains éléments du Javascript. Il peut ainsi contrôler la manière dont ils sont employés et peut réagir en conséquence pour fournir des fonctionnalités supplémentaires aux développeurs.


Vous vous souvenez lorsque nous avions abordé l'objet Image ? La propriété complete avait été évoquée mais non étudiée en raison de son comportement hasardeux. Nous allons ici essayer de permettre son support.


Tout d'abord, par quoi commence-t-on le développement d'un wrapper ? Comme dit plus haut, il s'agit d'un code qui a pour but d'encadrer l'utilisation de certains éléments, il s'agit en fait d'une surcouche par laquelle nous allons passer pour pouvoir contrôler nos éléments. Dans l'idéal, un wrapper doit permettre au développeur de se passer de l'élément original, ainsi le travail ne s'effectuera que par le biais de la surcouche que constitue le wrapper.


Puisque notre wrapper doit servir de surcouche de A à Z, celui-ci va se présenter sous forme d'objet qui sera instancié à la place de l'objet Image :

function Img() {

 

    var obj = this; // Nous faisons une petite référence vers notre objet Img. Cela nous facilitera la tâche.

 

    this.originalImg = new Image(); // On instancie l'objet original, le wrapper servira alors d'intermédiaire

     

}


Notre but étant de permettre le support de la propriété complete, nous allons devoir créer par défaut un événement load qui se chargera de modifier la propriété complete lors de son exécution. Il nous faut aussi assurer le support de l'événement load pour les développeurs :

function Img() {

 

    var obj = this; // Nous faisons une petite référence vers notre objet Img. Cela nous facilitera la tâche.

     

    this.originalImg = new Image(); // On instancie l'objet original, le wrapper servira alors d'intermédiaire

     

    this.complete = false;

    this.onload = function() {}; // Voici l'événement que les développeurs pourront modifier

     

    this.originalImg.onload = function() {

 

        obj.complete = true; // L'image est chargée !

        obj.onload(); // On exécute l'événement éventuellement spécifié par le développeur

 

    };

     

}


Actuellement, notre wrapper fait ce qu'on voulait qu'il fasse : assurer un support de la propriété complete. Cependant, il nous est actuellement impossible de spécifier les propriétés standards de l'objet original sans passer par notre propriété originalImg, or ce n'est pas ce que l'on souhaite car le développeur pourrait compromettre le fonctionnement de notre wrapper, par exemple en modifiant la propriété onload de l'objet original. Il va donc nous falloir créer une méthode permettant l'accès à ces propriétés sans passer par l'objet original.


Ajoutons donc deux méthodes set() et get() assurant le support des propriétés d'origine :

Img.prototype.set = function(name, value) {

 

    var allowed = ['width', 'height', 'src']; // On spécifie les propriétés dont on autorise la modification

     

    if (allowed.indexOf(name) != -1) {

        this.originalImg[name] = value; // Si la propriété est autorisée alors on la modifie

    }

 

};

 

Img.prototype.get = function(name) {

 

    return this.originalImg[name]; // Pas besoin de contrôle tant qu'il ne s'agit pas d'une modification

 

};


Vous remarquerez au passage qu'un wrapper peut vous donner un avantage certain sur le contrôle de vos objets, ici en autorisant la lecture de certaines propriétés mais en interdisant leur écriture.


Nous voici maintenant avec un wrapper relativement complet qui possède cependant une certaine absurdité : l'accès aux propriétés de l'objet d'origine se fait par le biais des méthodes set() et get(), tandis que l'accès aux propriétés relatives au wrapper se fait sans ces méthodes. Le principe est plutôt stupide vu qu'un wrapper a pour but d'être une surcouche transparente. La solution pourrait donc être la suivante : faire passer les modifications/lectures des propriétés par les méthodes set() et get() dans tous les cas, y compris lorsqu'il s'agit de propriétés appartenant au wrapper.


Mettons cela en place :

Img.prototype.set = function(name, value) {

 

    var allowed = ['width', 'height', 'src'], // On spécifie les propriétés dont on autorise la modification

        wrapperProperties = ['complete', 'onload'];

     

    if (allowed.indexOf(name) != -1) {

        this.originalImg[name] = value; // Si la propriété est autorisée alors on la modifie

    }

 

    else if(wrapperProperties.indexOf(name) != -1) {

        this[name] = value; // Ici, la propriété appartient au wrapper et non pas à l'objet original

    }

 

};

 

Img.prototype.get = function(name) {

 

    // Si la propriété n'existe pas sur le wrapper, on essaye alors sur l'objet original :

    return typeof this[name] != 'undefined' ? this[name] : this.originalImg[name];

 

};


Nous approchons grandement du code final. Il nous reste maintenant une dernière chose à mettre en place qui peut se révéler pratique : pouvoir spécifier l'adresse de l'image dès l'instanciation de l'objet. La modification est simple :

function Img(src) { // On ajoute un paramètre « src »

 

    var obj = this; // Nous faisons une petite référence vers notre objet Img. Cela nous facilitera la tâche.

     

    this.originalImg = new Image(); // On instancie l'objet original, le wrapper servira alors d'intermédiaire

     

    this.complete = false;

    this.onload = function() {}; // Voici l'événement que les développeurs pourront modifier

     

    this.originalImg.onload = function() {

 

        obj.complete = true; // L'image est chargée !

        obj.onload(); // On exécute l'événement éventuellement spécifié par le développeur

 

    };

 

    if (src) {

        this.originalImg.src = src; // Si elle est spécifiée, on défini alors la propriété src

    }

     

}


Et voilà ! Notre wrapper est terminé et entièrement opérationnel ! Voici le code complet dans le cas où vous auriez eu du mal à suivre :

function Img(src) {

 

    var obj = this; // Nous faisons une petite référence vers notre objet Img. Cela nous facilitera la tâche.

     

    this.originalImg = new Image(); // On instancie l'objet original, le wrapper servira alors d'intermédiaire

     

    this.complete = false;

    this.onload = function() {}; // Voici l'événement que les développeurs pourront modifier

     

    this.originalImg.onload = function() {

 

        obj.complete = true; // L'image est chargée !

        obj.onload(); // On exécute l'événement éventuellement spécifié par le développeur

 

    };

 

    if (src) {

        this.originalImg.src = src; // Si elle est spécifiée, on défini alors la propriété src

    }

     

}

 

Img.prototype.set = function(name, value) {

 

    var allowed = ['width', 'height', 'src'], // On spécifie les propriétés dont on autorise la modification

        wrapperProperties = ['complete', 'onload'];

     

    if (allowed.indexOf(name) != -1) {

        this.originalImg[name] = value; // Si la propriété est autorisée alors on la modifie

    }

 

    else if(wrapperProperties.indexOf(name) != -1) {

        this[name] = value; // Ici, la propriété appartient au wrapper et non pas à l'objet original

    }

 

};

 

Img.prototype.get = function(name) {

 

    // Si la propriété n'existe pas sur le wrapper, on essaye alors sur l'objet original :

    return typeof this[name] != 'undefined' ? this[name] : this.originalImg[name];

 

};


Faisons maintenant un essai :

var myImg = new Img(); // On crée notre objet Img

 

alert('complete : ' + myImg.get('complete')); // Vérification de la propriété complete : elle est bien à false

 

myImg.set('onload', function() { // Affichage de diverses informations une fois l'image chargée

    alert(

        'complete : ' + this.get('complete') + '\n' +

        'width : ' + this.get('width') + ' px\n' +

        'height : ' + this.get('height') + ' px'

    );

});

 

myImg.set('src', 'http://www.sdz-files.com/cours/javascript/part3/chap9/img.png'); // On spécifie l'adresse de l'image



Alors, c'est plutôt convaincant, non ?


Pour information, sachez que les wrappers sont à la base de nombreuses bibliothèques Javascript. Ils ont l'avantage de permettre une gestion simple du langage sans pour autant l'altérer.


En résumé


Créé avec HelpNDoc Personal Edition: Créer des livres électroniques EPub facilement

Site à deux balles