Sommaire du tutoriel
Développer un plugin jQuery UI
- Introduction
- Mise en forme
- Options et méthodes
- Événements
- Fonctionnalités avancées
- Héritage
Le prototype pour widgets jQuery UI implémente par défaut plusieurs méthodes qui ont vocation à être étendues. Libre à vous de les implémenter ou non. Nous avons déjà vu la méthode _create, mieux vaut l'implémenter sans quoi notre widget ne ferait pas grand chose. Mais il y a aussi _init, destroy, enable, disable et option (les descriptions qui suivent sont fortement inspirées de la documentation jQuery UI) :
- _init() : Peut facilement être confondu avec _create. À la création d'un widget, _create puis _init sont exécutées successivement. Si nous ré-exécutons notre widget sur un élément du DOM déjà instancié, seul _init est appelée.
- destroy() : Supprime l'instance du widget qui a été stocké en data dans notre élément du DOM. Le retour de l'élément du DOM, sur lequel est basé notre widget, dans l'état où il était avant la création du widget est à notre charge.
- option(String key[, String value]) ou option(JSON {key1 : value1, key2 : value2} ) : Renvoi ou modifie une ou plusieurs options de notre widget.
- enable() : Met l'option disabled à false. Le widget est libre de respecter ou non cette option.
- disable() : Met l'option disabled à true. Le widget est libre de respecter ou non cette option.
Rien ne vous oblige à implémenter toutes ces méthodes. Si vous le faites ce sont des possibilités supplémentaires que vous offrez aux utilisateurs de votre plugin. Nous imaginons rarement tout ce que les utilisateurs peuvent vouloir faire avec nos créations. Vous ne voyez peut-être pas l'utilité de pouvoir réinitialiser, détruire ou désactiver votre widget mais il y a de fortes chances pour que certains de vos utilisateurs en aient besoin.
Voyons maintenant tout cela dans le détail en essayant de trouver des applications fonctionnelles sur notre exemple fil rouge. Je vous laisse juge du résultat dans la démo en fin d'article.
_init
Je m'auto-cite : « À la création d'un widget _create puis _init sont exécutées successivement. Si nous ré-exécutons notre widget sur un élément du DOM déjà instancié, seul _init est appelée. »
Si nous poussons le résonnement, _create doit contenir la création d'éléments indispensables à notre widget mais indépendants de toutes options. A l'inverse _init doit juste se contenter de modifications sur le widget en fonction des options.
Dans le cadre de notre exemple cela veut donc dire que l'encapsulation de notre élément dans un container et que la création du bloc pour le titre doivent être dans _create. Par contre la modification du contenu du titre et l'application du toggle, qui eux dépendent d'options, doivent se faire dans l'_init.Reprenons notre code.
// La fonction _create est appelée à la construction du widget
_create: function() {
this.uiBlocContainer = $('<div></div>')
.addClass('ui-bloc ui-widget ui-widget-content ui-corner-all')
.insertAfter(this.element);
this.element.addClass('ui-bloc-content').appendTo(this.uiBlocContainer);
this.uiBlocTitle = $('<h5></h5>').addClass('ui-bloc-title ui-widget-header ui-corner-top')
.prependTo(this.uiBlocContainer);
// On encapsule un SPAN dans le bloc titre pour y écrire le titre et pouvoir le modifier à posteriori
$('<span></span>').appendTo(this.uiBlocTitle);
// On ajoute un SPAN au bloc titre pour le picto indicateur de l'état d'ouverture / fermeture
// Mais on le cache au cas où la fonctionnalité serait désactivée
this.uiBlocTitleToggle = $('<span></span>')
.addClass('ui-bloc-title-toggle ui-icon ui-icon-pin-s')
.appendTo(this.uiBlocTitle)
.hide();
},
// La fonction _init est appelée à la construction ET à la réinitialisation du widget
_init : function() {
var self = this;
// On renseigne le texte du titre avec la valeur présente dans les options
self.uiBlocTitle.children('span:first').text(self.options.title);
// On enlève l'événement click sur le bloc titre
// En cas de réinitialisation, cela évite d'ajouter un nouvel événement click
// à notre titre qui en a déjà potentiellement un
self.uiBlocTitle.unbind('click');
if (self.options.togglable) {
// On ajoute l'événement click au bloc titre si le bloc est ouvrable / fermable
self.uiBlocTitle.click(function(event) {
self.toggle(event);
return false;
}).css('cursor', 'pointer'); // On modifie le curseur au survol du titre
// On affiche le picto indicateur de l'état d'ouverture / fermeture
// précédemment crée dans _create
self.uiBlocTitleToggle.show();
if (!self.options.opened) {
// Si le bloc est initialisé avec le paramètre opened à false, on ferme le bloc
self._close();
} else {
// Si le bloc est initialisé avec le paramètre opened à true, on ouvre le bloc
self._open();
}
} else {
// Le bloc n'est pas togglable
// On réinitialise le curseur au survol du titre
self.uiBlocTitle.css('cursor', 'auto');
// On cache le picto indicateur de l'état d'ouverture / fermeture
self.uiBlocTitleToggle.hide();
// On ouvre le bloc
self._open();
}
},
toggle : function() {
var self = this;
if (!self.options.togglable) {
return self;
}
if (self.options.opened) {
if (false === this._trigger('beforeClose')) {
return false;
}
self._close();
this._trigger('close');
} else {
if (false === this._trigger('beforeOpen')) {
return false;
}
self._open();
this._trigger('open');
}
self.options.opened = !self.options.opened;
return self;
},
_close: function() {
this.uiBlocContainer.children().not(this.uiBlocTitle).hide();
this.uiBlocTitleToggle.removeClass('ui-icon-pin-s').addClass('ui-icon-pin-w');
},
_open: function() {
this.uiBlocContainer.children().show();
this.uiBlocTitleToggle.removeClass('ui-icon-pin-w').addClass('ui-icon-pin-s');
}
A noter que la méthode _title − elle encapsulait la création et l'initialisation du bloc titre − a été purement et simplement détruite.
destroy
Maintenant que l'on a bien séparé ce qui était de la création et de l'initialisation, la méthode destroy revient à défaire ce que fait _create.
Allons-y gaiement.
// La fonction destroy ramène l'élément du DOM, sur lequel est basé notre widget,
// dans l'état où il était avant la création du widget.
// Elle défait ce que _create a fait
destroy: function() {
// On réaffiche l'élément éventuellement caché
// On enlève les classes css propres au widget
// Et on sort l'élément du container
this.element.show()
.removeClass('ui-bloc-content')
.insertBefore(this.uiBlocContainer);
// On détruit le container
// Ce qui détruit par ricochet tous les autres éléments créés par notre widget
this.uiBlocContainer.remove();
// On appelle la méthode originale du framework
// Elle supprime l'instance du widget qui a été stocké en data dans l'élément
$.Widget.prototype.destroy.apply(this);
return this;
},
option
Cette méthode sert aussi bien de setter que de getter. Elle peut traiter une seule option ou plusieurs en même temps.
- option() : renvoie le JSON des options de notre widget
- option(String key) : renvoie la valeur de l'option key
- option(String key, value) : met à jour l'option key avec la valeur value dans le JSON d'options de notre widget
- option(JSON {key1 : value1, key2 : value2}) : fusionne le JSON passé en paramètre avec le JSON d'options de notre widget
En mode setter, l'implémentation par défaut de la méthode option dans le framework jQuery UI se contente de modifier la ou les options. Si ces modifications impliquent une mise à jour de votre widget, c'est à vous de surcharger option pour répercuter ces modifications − en n'oubliant pas d'appeler la méthode originale. En fait, la méthode option du framework appelle 2 autres méthodes : _setOptions quand le paramètre est un JSON et _setOption quand le premier paramètre est une chaîne. _setOptions appelle elle-même _setOption pour chaque clé du JSON. Ce n'est donc pas option qu'il faut surcharger mais _setOption.
// Surcharge de la méthode _setOption qui est appelée par la méthode option
// qui permet de modifier des options de notre widget
_setOption: function(key, value){
var self = this;
// On appelle la méthode originale du framework qui modifie le tableau d'options
$.Widget.prototype._setOption.apply(self, arguments);
if ($.inArray(key, ['title', 'togglable', 'opened']) != -1) {
// Si l'option modifiée est une des 3 options title, togglable, opened
// On appelle la méthode d'initialisation
self._init();
}
},
Notre ancienne méthode title − elle agissait comme un setter / getter sur l'option title − ne sert plus à rien : Poubelle !
enable / disable
Par défaut dans le framework jQuery UI, ces deux méthodes font deux choses :
- Elles modifient l'option disabled respectivement à false et à true
- Elles suppriment / ajoutent une classe css ui-state-disabled sur notre élément
Si notre élément de base du widget servait lui-même de container aux éléments créés par le widget, l'aspect visuel du widget pourrait simplement être changé via la classe ui-state-disabled.
Mais avant d'aller plus loin il nous faut définir ce que signifie "désactivé notre widget". Si notre widget était juste un bouton, ça pourrait être un simple grisage et la désactivation du clic. Dans notre cas je propose :
- Grisage du bloc (titre et contenu)
- Désactivation du clic sur le titre dans le cas où le bloc serait togglable
Plutôt que de redéfinir enable et disable, nous allons utiliser l'option disabled gérée nativement. On va donc revenir dans _setOption pour le grisage.
_setOption: function(key, value){
var self = this;
$.Widget.prototype._setOption.apply(self, arguments);
if ($.inArray(key, ['title', 'togglable', 'opened']) != -1) {
self._init();
} else if (key === 'disabled') {
// L'option disabled a été modifiée
// On ajoute ou supprime, en fonction du cas, la classe ui-state-disabled au container
// Dans le framework css de jQuery UI, la classe ui-state-disabled grise un élément
if (value) {
this.uiBlocContainer.addClass('ui-state-disabled');
} else {
this.uiBlocContainer.removeClass('ui-state-disabled');
}
}
},
Pour la désactivation du clic sur le titre on va se contenter de modifier l'événement click définit dans _init.
self.uiBlocTitle.click(function(event) {
// Si le widget est disabled, il ne se passe rien au click
if (!self.options.disabled) {
self.toggle(event);
return false;
}
});
La démo !
Comme promis la démo de toutes ces nouvelles fonctionnalités.



