Sommaire du tutoriel
Développer un plugin jQuery UI
Notre bloc a pris forme. Il est maintenant ouvrable et fermable.
Imaginons qu'un autre développeur utilise le plugin et qu'il veuille déclencher une action à chaque fois que le bloc s'ouvre ou se ferme. En l'état, il serait obligé de modifier directement le code source du plugin (dans les méthodes _close et _open) pour y implanter ses actions. Une option pas franchement intelligente : à la prochaine mise à jour du plugin, il faudra qu'il refasse la même manipulation.
Facilitons la vie de ce pauvre développeur. Deux choix s'offrent à nous.
Ajoutons des callbacks
Les callbacks sont des fonctions javascript passées en paramètre, destinées à être exécutées lors d'un événement précis.
Ajoutons donc deux options à notre widget.
options: {
title: 'Titre',
togglable: true,
opened : true,
open : null, // Variable de type callback à déclencher à l'ouverture
close : null // Variable de type callback à déclencher à la fermeture
},
Modifions nos méthodes _open et _close pour tenir compte de ces options.
_close: function() {
this.uiBlocContainer.children().not(this.uiBlocTitle).hide();
this.uiBlocTitleToggle.removeClass('ui-icon-pin-s').addClass('ui-icon-pin-w');
// On vérifie si l'option close est bien une fonction
if ($.isFunction(this.options.close)) {
// Exécution de la fonction close avec comme contexte l'instance de notre widget (this)
this.options.close.call(this);
}
},
_open: function() {
this.uiBlocContainer.children().show();
this.uiBlocTitleToggle.removeClass('ui-icon-pin-w').addClass('ui-icon-pin-s');
// On vérifie si l'option open est bien une fonction
if ($.isFunction(this.options.open)) {
// Exécution de la fonction open avec comme contexte l'instance de notre widget (this)
this.options.open.call(this);
}
},
Et pour finir, modifions l'initialisation de notre bloc.
$('#monbloc1').bloc({
title: 'Hello bloc 1',
open: function() {
// this représente l'instance de notre widget
alert('Ouverture > ' + this.options.title);
},
close: function() {
alert('Fermeture > ' + this.options.title);
}
});
Je ne vous fais pas de démo mais ayez confiance, ça marche parfaitement.
Vous l'avez peut-être compris, la deuxième solution à ma préférence.
Ajoutons des triggers
Cette solution est légèrement différente. Plutôt que de déclencher les actions de l'intérieur du plugin quand les événements surviennent, envoyons juste une information à l'élément de notre widget comme quoi l'événement a eu lieu. L'avantage est qu'on n'est pas obligé de définir l'action à l'initialisation du widget. On peut le faire a posteriori aussi simplement que l'on intercepte un événement click sur un bouton (ce n'est pas tout à fait vrai, c'est également possible avec la première solution en utilisant le setter $('#monbloc').('open', function() {...}); mais c'est un peu plus compliqué et moins intuitif).
Pour envoyer le signal de l'événement, il existe la méthode trigger avec jQuery. Le framework jQuery UI, dans sa grande bonté, fournit la méthode _trigger qui en fait un peu plus. Que fait-elle de plus ?
- Elle ajoute le nom de votre widget en préfixe du nom de l'événement (si vous déclenchez un événement nommé close, l'événement réellement propagé sera nomduwidgetclose). Pas de conflit possible avec d'autres événements pouvant être déclenchés par d'autres plugins ou par le framework.
- Elle crée automatiquement un objet $.Event si vous n'en fournissez pas un.
- And last but not least, elle vérifie si une option de type callback du même nom que votre événement – sans le préfixe du widget – existe. Si c'est le cas, elle l'exécute avec, comme contexte, l'élément de votre widget.
Mise en application, toujours dans nos deux méthodes _open et _close.
_close: function() {
this.uiBlocContainer.children().not(this.uiBlocTitle).hide();
this.uiBlocTitleToggle.removeClass('ui-icon-pin-s').addClass('ui-icon-pin-w');
// Envoi du signal de fermeture
// _trigger accepte 3 paramètres, les deux derniers étant optionnels :
// - le nom de l'événement
// - l'objet événement
// - des données additionnelles envoyées aux fonctions interceptant l'événement
this._trigger('close');
},
_open: function() {
this.uiBlocContainer.children().show();
this.uiBlocTitleToggle.removeClass('ui-icon-pin-w').addClass('ui-icon-pin-s');
// Envoi du signal d'ouverture
this._trigger('open');
},
Le code d'initialisation de notre bloc fourni dans la première solution basée sur les callbacks fonctionne toujours. Mais, maintenant, on peut aussi faire ça :
$('#monbloc1').bloc({
title: 'Hello bloc 1',
});
$('#monbloc1').bind('blocopen', function() {
// this représente l'élément sur lequel est appliqué notre widget bloc
alert('Ouverture > ' + $(this).text());
});
$('#monbloc1').bind('blocclose', function() {
alert('Fermeture > ' + $(this).text());
});
Osons l'interruptionnabilité !!
Je sens qu'il vous reste encore quelques neurones pour une dernière étape dans ce chapitre. L'utilisateur de notre plugin peut maintenant être averti de l'ouverture et de la fermeture du bloc. Bien ! Mais, ce qu'il veut, lui, c'est pouvoir arrêter l'ouverture et la fermeture s'il le décide.
_close: function() {
// Si l'événement beforeClose retourne false, on arrête la fermeture
if (false === this._trigger('beforeClose')) {
return;
}
this.uiBlocContainer.children().not(this.uiBlocTitle).hide();
this.uiBlocTitleToggle.removeClass('ui-icon-pin-s').addClass('ui-icon-pin-w');
this._trigger('close');
},
_open: function() {
// Si l'événement beforeOpen retourne false, on arrête l'ouverture
if (false === this._trigger('beforeOpen')) {
return;
}
this.uiBlocContainer.children().show();
this.uiBlocTitleToggle.removeClass('ui-icon-pin-w').addClass('ui-icon-pin-s');
this._trigger('open');
},
MAJ du 17/08/2011
Petit bug dans l'ajout des événements beforeClose et beforeOpen : si les événements renvoient false, les méthodes − respectivement _close ou _open − doivent renvoyer false aussi et non pas simplement return;. De cette façon, la méthode toggle peut, elle aussi, tester le retour de _open et _close et s'arrêter s'il le faut.
toggle : function() {
var self = this;
if (!self.options.togglable) {
return self;
}
if (self.options.opened) {
// Si _close renvoie false, arrêt de l'exécution de toggle
if (!self._close()) {
return;
}
} else {
// Si _open renvoie false, arrêt de l'exécution de toggle
if (!self._open()) {
return;
}
}
self.options.opened = !self.options.opened;
return self;
},
MAJ du 24/08/2011
Le patch du 17 août corrigeait un bug mais il en a introduit un autre. Les méthodes _open et _close ne faisaient aucun return final, toggle interprétait ça comme un false et s'arrêtait. Résultat : le premier toggle marchait mais les suivants non. Je ne vous fais pas l'affichage du bout de code : ajouter simplement return true; à la fin des méthodes _open et _close.
Corrigeons un effet de bord
Je ne vous cache rien. En l'état, ce plugin a un petit bug. La méthode _close est appelée lors de la fermeture du bloc, mais également à l'initialisation si l'utilisateur a demandé à ce que le bloc soit fermé par défaut. S'il a, en plus, ajouté des options close ou beforeClose pour exécuter des callbacks aux déclenchements de ces événements, ces callbacks s'exécuteraient aussi à la création du bloc. C'est tout de même un peu ballot. Corrigeons rapidement ça.
_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._title();
if (this.options.togglable && !this.options.opened) {
// Le paramètre true est là pour indiquer à la méthode _close qu'elle est appelée par la création du widget
this._close(true);
}
},
_close: function(isCreate) {
// Pas d'événement si c'est la création du widget
if (!isCreate && false === this._trigger('beforeClose')) {
return;
}
this.uiBlocContainer.children().not(this.uiBlocTitle).hide();
this.uiBlocTitleToggle.removeClass('ui-icon-pin-s').addClass('ui-icon-pin-w');
if (!isCreate) {
this._trigger('close');
}
},
La démo, la démo !!!
Juste une précision avant la démo. Quand vous interceptez vos événements, le nom de l'événement est en minuscule. Dans notre cas, il faut donc intercepter blocbeforeopen et non blocbeforeOpen.



