Écrire ses propres plugins avec jQuery

Synbioz

Dans cet article, je vais aborder un outil que n'importe quel développeur Web ou intégrateur a déjà dû utiliser. Je veux parler de jQuery.

En effet, jQuery est présent sur une très grande majorité de sites Web, il est présent par défaut dans des frameworks comme Ruby on Rails et possède une très importante base de plugins et d'utilisateurs.

Cet article est publié avec l'aimable autorisation de Synbioz, l'article original peut être lu sur le blog de Synbioz : Écrire ses propres plugins avec jQuery.

Commentez ♪

- Titre : Écrire ses propres plugins avec jQuery

- Auteur : Synbioz

- Parution : 28 janvier 2014

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2014 Synbioz. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Avant de commencer

- II. Partager et réutiliser

- III. Un plugin jQuery vu de l'extérieur

- IV. Un plugin jQuery vu de l'intérieur

- V. C'est pas déjà la fin ?

- V-A. jQuery et pas $

- V-B. Définir des options par défaut

- V-C. Où mettre son code et comment l'organiser

- V-D. Gestion des événements

- VI. Conclusion

- VII. Remerciements

I - Avant de commencer

Il existe une controverse concernant l'utilisation inconditionnelle de jQuery et le fait que cette dernière tend à favoriser l'ignorance de JavaScript. C'est-à-dire que beaucoup de développeurs ne connaissent que jQuery pour tout ce qui concerne le DOM, la propagation d'événements, la sélection d'éléments, les manipulations de propriétés, etc.

Attention, l'article est rédigé par une de ces personnes. N'étant pas un développeur JavaScript, j'ai baigné dans le jQuery avant toute autre chose. Cela ne veut pas dire que je ne connais rien au JavaScript, mais jQuery a été ma porte d'entrée et je le trouve toujours très pragmatique même s'il n'est pas exempt de défaut.

II - Partager et réutiliser

Il m'arrive souvent d'avoir à intégrer du code fourni par un intégrateur. Parfois, certains composants JavaScript sont manquants. Commence alors une quête du composant préconstruit idéal. Très souvent, la base de code est dépendante de jQuery et j'utilise Unheap, que Victor m'a conseillé, pour trouver la perle rare qui fera exactement ce dont j'ai besoin.

Oui, mais non ! L'intégrateur avait probablement fait la même chose avant moi, car en général, je ne trouve pas ce qu'il me faut.

Voilà donc un billet qui s'adresse aux intégrateurs de la part d'un développeur et qui lance un message : « Développez vos propres plugins ! ».

III - Un plugin jQuery vu de l'extérieur

Voilà comment s'utilisent une majorité des plugins jQuery :

// Initialisation du plugin
$("#mon-element").monPlugin({
"des options": "et des valeurs",
// ...
});

// Appel de méthodes
$("#mon-element").monPlugin("le nom de ma methode", "ses", "arguments");

On a deux cas d'utilisation : l'initialisation et l'appel de méthode. Dans les deux cas, on voit que l'appel se fait sur un objet jQuery.

IV - Un plugin jQuery vu de l'intérieur

Pour cet article, je vais utiliser un plugin jQuery de démonstration. Ce plugin permet d'afficher un calendrier et de mettre en avant la présence d'événements sur certains jours. Dans la suite de l'article, c'est bCalendar et pas monPlugin qui sera utilisé.

Pour traiter les deux cas d'utilisation que nous avons vus dans la partie précédente, il faudra écrire le code ci-dessous :

$.fn.bCalendar = function(optionsOrMethod) {

var args = arguments;

if (typeof optionsOrMethod === "string")
this.each(function(_, element){
bCalendarMethodCall.apply($(element), args);
});
else
this.each(function(_, element){
bCalendarInit.apply($(element), args);
});

return this;
};

Ajouter une fonction bCalendar à $.fn permet de rendre cette fonction disponible sur n'importe quel objet jQuery, comme $("#mon-element"). Notre fonction bCalendar sera bindée avec notre objet jQuery sur lequel la fonction bCalendar aura été appelée.

Les fonctions auxiliaires que j'utilise (bCalendarInit et bCalendarMethodCall) attendent un objet jQuery ne contenant qu'un seul élément. Pour traiter le cas où l'appel à bCalendar se ferait sur plusieurs éléments, j'utilise each().

À la fin de bCalendar, on donne la valeur de retour : this, afin de pouvoir chaîner les appels. Attention, selon les méthodes disponibles, vous voudrez peut-être retourner autre chose. Je pense à des accesseurs potentiels comme getMonth qui devrait retourner autre chose qu'un objet jQuery.

De manière générale, on préfère minimiser le nombre de fonctions que l'on ajoute sur $.fn. C'est pour cela qu'on garde une seule fonction bCalendar plutôt que deux fonctions bCalendarInit et bCalendarMethodCall directement dans $.fn. Ce dernier point relève d'une convention, ce n'est pas une contrainte.

C'est tout, on a fait le tour du nécessaire pour que jQuery offre à l'utilisateur les interfaces classiques d'un plugin.

V - C'est pas déjà la fin ?

Je vous rassure, je ne m'arrête pas là. En effet, même si je vous ai présenté le strict minimum, il y a encore des astuces tout aussi primordiales à connaître.

V-A - jQuery et pas $

Dans mon exemple précédent, j'ai utilisé directement la variable $. Dans le cas où jQuery est utilisé en mode sans conflit, $ est laissé inchangé. Pour permettre à votre plugin de fonctionner aussi bien en mode normal qu'en mode sans conflit, il faut légèrement transformer le code :

(function($){
// $.fn.bCalendar = ...
})(jQuery)

V-B - Définir des options par défaut

Quand le nombre d'options d'un plugin devient important, il est bon d'éviter à l'utilisateur de devoir les saisir toutes. Dans ces cas, il faut fournir un ensemble d'options par défaut. C'est d'autant plus agréable lorsque l'on peut les modifier.

Voici le code qu'il est possible d'ajouter pour mettre à disposition ces options par défaut :

$.fn.bCalendar = function(optionsOrMethod) {
// ...
};

$.fn.bCalendar.defaults = {
begin: moment().date(1),
events: function(startAt, callback){ callback([]) },
eventClass: 'bc-event'
};

function bCalendarInit(options) {
options = $.extend({}, $.fn.bCalendar.defaults, options);

// ...
};

Cela permettra à l'utilisateur d'écrire ce type de code avant l'initialisation, mais après le chargement du plugin :

$.fn.bCalendar.defaults.eventClass = "event";

V-C - Où mettre son code et comment l'organiser

C'est bien beau, mais tout ce que je viens de montrer n'est que la glue nécessaire pour coller votre code dans jQuery. Voici quelques conseils pour organiser votre code.

Une pratique que je trouve intéressante est de créer un objet par élément et surtout de conserver cet objet tout au long de la vie de l'élément.

Voici l'intégralité de la fonction bCalendarInit :

function bCalendarInit(options) {

options = $.extend({}, $.fn.bCalendar.defaults, options);

var calendar = new Calendar(this, options);
this.data('bCalendar', calendar);

};

On voit que je crée un objet à partir du constructeur Calendar et que je l'associe à l'objet jQuery qui est bindé à ma fonction bCalendarInit via la fonction data().

La fonction data() permet en effet d'affecter une valeur à un attribut data d'un élément. La valeur de cet attribut peut être un objet JavaScript, aussi complexe soit-il.

L'avantage de ce type de pratique est qu'à partir de notre élément du DOM, on va pouvoir accéder à notre objet Calendar très facilement. C'est ce qu'on fait dans la fonction bCalendarMethodCall :

function bCalendarMethodCall() {
var calendar = this.data('bCalendar');

if (calendar) {
var methodName = arguments[0];
var methodArgs = arguments.slice(1);
return calendar[methodName].apply(calendar, methodArgs);
}

throw "Please init the bCalendar before calling methods on it.";
};

Cet objet Calendar me permet d'encapsuler tout le comportement de mon plugin. Si je dois créer des objets additionnels, utiliser des fonctions auxiliaires ou autre, alors ce sera dans Calendar.

V-D - Gestion des événements

Lorsque l'on fait un composant graphique dynamique, on doit attacher des événements aux actions réalisées par l'utilisateur comme un clic sur un jour du calendrier. Dans ce cas, le calendrier va déclencher un événement que l'utilisateur va pouvoir utiliser. Voici un exemple d'une telle utilisation :

var calendar = $("#calendar-widget").bCalendar({
events: function(startAt, callback) {
callback([
{ date: moment(startAt).add(10, "days"), title: "Event 1" }
]);
}
});

// Utilisation d'un événement personnalisé

calendar.on('eventClicked', function(event, clickedEvent) {
console.log("Click on event:", clickedEvent);
alert(clickedEvent.title);
});

Pour arriver à ce résultat, voici le code que j'ai mis en place :

function Calendar(container, options) {

// ...

// this.table est l'élément 'table' qui est inséré dans
// le conteneur (#calendar-widget)

this.table.on("click", "tbody td.bc-event", function() {
var event = $(this).data("bcEvent");
container.trigger("eventClicked", [event]);
});

//...

};

Dans cet extrait de mon constructeur de Calendar, j'attache un seul gestionnaire pour l'événement. Une mauvaise solution aurait été d'attacher un événement sur chaque <td> en utilisant la fermeture (closure) de chaque gestionnaire pour référencer le paramètre à envoyer avec l'événement. Voici un comparatif des deux pratiques :

// events = [ { title: ... }, { ... }, ... ];

tdWithEvent.each(function(i, td) {
var td = $(td),
ev = events[i];

// Cas n°1 : Attacher un handler pour chaque cellule
td.on('click', function(){ container.trigger("eventClicked", [ev]); });

// Cas n°2 : Simplement passer par l'attribut data les informations utiles au handler
td.data("bcEvent", ev);
});

Au lieu d'utiliser la fermeture du gestionnaire pour passer un argument, j'utilise l'attribut data-bcEvent, donc le second cas dans le code ci-dessus.

VI - Conclusion

C'est tout pour cette introduction à la réalisation de plugin jQuery. Je vous invite à parcourir le code qui m'a servi de support pour cet article. Il est bien entendu perfectible et je suis ouvert aux pull-requests.

J'espère que cela vous encouragera à créer vos propres composants réutilisables et à les partager avec la communauté.

VII - Remerciements

Cet article est publié avec l'aimable autorisation de Synbioz, l'article original peut être lu sur le blog de Synbioz : Écrire ses propres plugins avec jQuery.

N'hésitez pas à faire part de vos remarques et commentaires sur le forum. Commentez