I. Alors d'abord, un slideshow, c'est quoi ?

Je vous propose de commencer par la fin en faisant un détour par la démo, ici : http://jquery.mathieurobin.com/slideshow/ (petite précision : vous le trouverez « peu » réactif aux clics, c'est normal, les photos ne sont pas hébergées sur mon serveur ce qui rend tout ça un peu plus lourd).

II. OK, c'est super mais on fait comment ?

On va faire simple, très simple. Tout d'abord, posons quelques règles de travail :

  • je veux écrire le moins de HTML possible ;
  • je veux que le nombre de slides soit dynamique ;
  • je veux pouvoir positionner les liens où je le veux, aussi bien dans le slideshow qu'à l'extérieur ;
  • je veux pouvoir personnaliser ces liens ;
  • je veux que ça tourne tout seul, que l'utilisateur n'ait pas à cliquer.

Bon, on va s'arrêter là parce que ça ne fait déjà pas mal pour démarrer. Évidemment, on va faire en sorte qu'il soit suffisamment évolutif pour pouvoir revenir dessus. On va oublier pour cette première version la possibilité de faire un slide avec du texte, une page Web ou encore de la vidéo. On laisse tomber aussi l'idée qu'il soit dynamique, il faudra cliquer à la main pour faire défiler. Mais bon, je vous rassure. Ce n'est que provisoire.

On veut écrire le moins de HTML possible, OK, alors pour moi le plus simple, c'est ça :

index.htm
Sélectionnez
<html>
	<head>
		<title>Démo slideshow - Labs jQuery de Mathieu ROBIN</title>
		<script type="text/javascript" src="../jquery-1.4.3.min.js"></script>
		<script type="text/javascript" src="jquery.slideshow.js"></script>
		<script type="text/javascript">
			<!-- Ici on mettra le code Javascript nécessaire -->
		</script>
		<style type="text/css" media="screen">
			/* Et ici le code CSS nécessaire */
		</style>
	</head>
	<body>
		<div id="slideshow"></div>
	</body>
</html>

Vous comprendrez à la vue de ce code HTML plus que minimaliste que c'est bien votre code JS qui va se charger d'absolument tout. Bon évidemment il y aura du code CSS aussi pour rendre tout ça plus sympa. On va commencer par réaliser un joli cadre autour du slideshow et le positionner correctement, dans la partie CSS, on aura donc :

index.htm/CSS
Sélectionnez

div.slideshow{
	border:1px #000 solid;
	cursor:pointer;
	font-family:Arial;
	font-size:12px;
	height:420px;
	width:640px;
	position: absolute;
	top:50px;
	left:50px;
}
div.slideshow span.slideshow-link{
	background-color:#FFF;
	border:1px #000 solid;
	cursor:pointer;
	left:3px;
	line-height:22px;
	margin:3px;
	padding:2px 6px;
	position:relative;
	text-align:center;
	top:3px;
	vertical-align:top;
}
div.slideshow span.selected {
	font-weight: bold;
}

Je mets ce code CSS ici parce qu'il n'entre pas en compte dans l'exercice. Bien entendu, si vous êtes amené un jour à réaliser un « vrai » plugin jQuery avec des styles CSS spécifiques, pensez à les mettre dans un fichier. Maintenant on va appeler la fonction créatrice du slideshow, mais avant, il faut définir une structure de données JSON pour le contenu des slides. La suite va donc dans la partie script définie précédemment :

index.htm/Javascript
Sélectionnez

var data = {'imgs':[
	'http://farm1.static.flickr.com/79/244741330_3f6a68924f_z.jpg',
	'http://farm1.static.flickr.com/177/426373909_15671ba709_z.jpg',
	'http://farm5.static.flickr.com/4096/4909713714_736916b723_z.jpg',
	'http://farm5.static.flickr.com/4115/4767003134_c75abfecb5_z.jpg'
]};
$(document).ready(function(){
	$('#slideshow').slideshow();
});

On commence simplement avec juste les adresses des images. Bien entendu, vous pourrez modifier le plugin pour charger du contenu Web, ajouter des liens, etc. On se contentera de ce petit bout de code, tout le reste est dans le plugin. Vous avez peut-être remarqué cette ligne-là dans le template HTML plus haut :

index.htm
Sélectionnez

<script type="text/javascript" src="jquery.slideshow.js"></script>

Bon et bien dans le même dossier que votre fichier HTML, vous allez créer un fichier nommé « jquery.slideshow.js ». C'est lui qui contiendra le code du plugin.

III. On y arrive enfin !

Un plugin jQuery se construit toujours avec cette structure minimale là :

jquery.slideshow.js
Sélectionnez

(function($) {
	// Le code de votre plugin
})(jQuery);

Vous pouvez toujours essayer de la modifier, il y a effectivement des petites choses qu'on peut changer mais on sortirait trop du cadre de l'exercice et honnêtement : c'est à vos risques et périls de tenter le coup. On commence par créer une fonction qui englobera tout notre code et permettra de faire un premier test de notre plugin :

jquery.slideshow.js
Sélectionnez

(function($) {
	$.fn.slideshow = function(args) {
		alert('Ceci est mon premier plugin');
		return $(this);
	};
})(jQuery);

Si vous chargez la page HTML créée auparavant, vous devriez avoir une alerte de votre navigateur. Ne pas oublier la ligne de return qui sert à retourner l'objet jQuery lui-même. Comme ça vous pourrez admirer les jolis appels chaînés qui sont l'une des marques de fabrique de jQuery. Oui, je parle de ce genre de choses là :

Exemple d'appels chaînés
Sélectionnez

$('#maDiv').removeClass('classeAuPif').prev().parent().next().addClass('classeAuPif');

On va découper le plugin en trois parties, une pour les variables, une pour les fonctions internes (je suis tenté de dire privé mais ce n'est pas exactement ça) et une troisième pour le corps principal du plugin. On va parler directement des variables. Je vous laisse regarder rapidement l'implémentation que je vous propose et on voit après le pourquoi.

jquery.slideshow.js
Sélectionnez

(function($) {
	$.fn.slideshow = function(args) {
		// Variables
		var defaults = {
			speed: 2000
		};
		var opts = $.extend(defaults, args);
		var imgsLength = data.imgs.length;
		var ele = this;
		var tokenValue = 0;
		// Fonctions
		// Main
		return $(this);
	};
})(jQuery);

J'utilise une variable intermédiaire (ele) pour désigner l'objet this parce que, dans le code du plugin, this ne désignera pas toujours la div visée. Je vous recommande de toujours utiliser cet intermédiaire. On a aussi tokenValue, cette variable servira à une chose très simple : contenir le numéro du slide en cours pour qu'on sache toujours où on en est. Nous aurons besoin à plusieurs reprises de connaître le nombre d'images à manipuler, pour faire plus simple et pour des raisons de performances, j'utilise la variable imgsLength.

IV. T'as sauté args, defaults et opts

Disons que je les gardais pour la fin. Comme vous le savez peut-être, Javascript ne permet pas l'utilisation de valeurs par défaut. Mais jQuery propose une solution simple pour contourner ce problème. La fonction extend() fusionne deux tableaux en un. Vous pouvez l'utiliser de deux façons. Soit vous la mettez après un =, dans ce cas elle retourne le tableau créé (comme dans l'exemple), soit vous mettez l'appel seul, et dans ce cas, c'est le premier tableau passé en paramètre qui est modifié. Ici j'ai mis un seul élément dans le tableau, variable "speed", c'est l'intervalle de temps entre deux slides que j'ai mis à deux secondes. Vous pourrez donc faire cet appel à la place de l'ancien par exemple:

index.htm/Javascript
Sélectionnez

$(document).ready(function(){
	// On remplace $('#slideshow').slideshow(); par
	$('#slideshow').slideshow({speed: 100});
});

Mais bon vu que ce sont des millisecondes, ça risque d'être un peu rapide. Ceci dit, ça vous permettra d'aller dans l'autre sens aussi en mettant par exemple trois secondes de délai. Avec cette façon de faire, vous pourrez donc passer un seul objet JSON qui contient tous vos paramètres et même les passer dans le désordre. C'est beau n'est-ce pas.

Bon maintenant faut mettre une petite fonction qu'on va appeler de façon cyclique pour changer l'image. Hop, d'abord le code à insérer directement dans le code du plugin :

jquery.slideshow.js
Sélectionnez

var rotate = function() {
	$(ele).css('background-image','url(' + data['imgs'][tokenValue] + ')');
	$('.selected').removeClass('selected');
	$('#slideshow-' + tokenValue).addClass('selected');
	tokenValue++;
	if(imgsLength == tokenValue)
		tokenValue = 0;
	timer = setTimeout(this.rotate, opts.speed);
};

On a désormais une fonction qui s'appelle « rotate », que fait-elle? Elle retrouve l'élément visé par ele (anciennement this) et change la propriété CSS « background-image ». Si vous voulez afficher un script HTML, c'est cette instruction-là qu'il faudra remplacer. Au passage, j'en profite pour retirer la classe CSS « selected » de tout élément la possédant, pour ensuite l'appliquer à l'indicateur de position dans le slideshow. Si vous regardez la source CSS plus haut dans ce billet, vous verrez que la classe CSS « selected » ne fait que mettre en gras le texte. Vous pouvez aussi bien entendu modifier ça pour ajouter une couleur de fond par exemple. Vu qu'on souhaite se déplacer en cycle, il vaut mieux changer la valeur de tokenValue pour que l'image suivante (d'un point de vue du temps) ne soit pas la même. Au passage on s'assure que si on dépasse le nombre de photos, on revient à 0, pour éviter de ne rien afficher parce qu'on a oublié de s'arrêter.

IV-A. Euh attends là, tu peux attaquer directement la variable data qui n'est pas dans le plugin ?

Oui. J'ai fait une petite erreur volontairement pour vous montrer qu'il est très important de faire attention à ce qu'on utilise quand on code un plugin jQuery. Ici data est une variable globale, je peux donc l'utiliser de partout, tout le temps. Imaginez ce que ça donnerait si on appelle slideshow() et plus loin dans le script de votre application vous changez la variable « data ». Hum, le joli bazar que ça va mettre. Exemple à ne pas suivre donc. Vous me passerez donc le tableau d'adresses d'images en paramètre de votre appel à slideshow. Je vous aide, l'appel :

index.htm/Javascript
Sélectionnez

$('#slideshow').slideshow(data);

Et la fonction corrigée :

jquery.slideshow.js
Sélectionnez

var imgsLength = opts.imgs.length;
var rotate = function() {
	$(ele).css('background-image','url(' + opts['imgs'][tokenValue] + ')');
	$('.selected').removeClass('selected');
	$('#slideshow-' + tokenValue).addClass('selected');
	tokenValue++;
	if(imgsLength == tokenValue)
		tokenValue = 0;
	setTimeout(rotate, opts.speed);
};

Voilà, là c'est propre. Et ça a moins de chances de se planter. J'ai oublié de vous parler de la dernière ligne. Je définis un timeOut (fonction JS de base, pas jQuery) qui exécute une fonction (premier paramètre) après un temps défini (second paramètre). Ici on appelle donc récursivement la même fonction « rotate », on a donc une boucle et comme délai, on utilise la valeur qui sera sortie de extend(), donc soit la valeur par défaut, soit la valeur possiblement passée en paramètre.

Passons au programme principal (partie Main, cf. plus haut). D'abord le code :

jquery.slideshow.js
Sélectionnez

$(ele).addClass('slideshow');
for(var i = 0; i < imgsLength; i++){
	$(ele).append('<span id="slideshow-' + i + '" class="slideshow-link">' + (i + 1) + '</span>');
}
rotate();

Rien de bien sorcier ici. On ajoute la classe CSS « slideshow » à ele (voir partie CSS), on ajoute nos curseurs de position à notre slideshow. Et on fait le premier appel à la fonction rotate() définie précédemment.

V. Je voulais aussi que mon utilisateur puisse cliquer sur les curseurs pour aller à un slide bien précis

On y vient. On rajoute ce dernier bout de code à la partie « main » du plugin :

jquery.slideshow.js
Sélectionnez

$("div.slideshow").delegate("span.slideshow-link", "click", function(pEvent){
	tokenValue = parseInt($(pEvent.target).text()) - 1;
	$('.selected').removeClass('selected');
	$(pEvent.target).addClass('selected');
	$(ele).css('background-image','url(' + opts['imgs'][tokenValue] + ')');
});

Il faut savoir que jQuery vous permet d'ajouter des handlers d'évènements sur des objets créés à la volée, tels que nos curseurs. Pour ça on utilise la fonction delegate()jQuery.live(). On commence donc par récupérer la valeur du curseur cliqué, on change de position la classe CSS « selected » comme déjà vu dans rotate() et on change l'image utilisée en appelant le bon tableau (n'oubliez pas qu'on ne touche plus à data).

VI. Voilà, votre plugin est terminé et fonctionne.

Parce que c'est sûrement un peu brouillon, voilà le code complet :

jquery.slideshow.js
Sélectionnez

(function($) {
	$.fn.slideshow = function(args) {
		// Variables
		var defaults = {
			speed: 2000,
			imgs:[]
		};
		var opts = $.extend(defaults, args);
		var imgsLength = opts.imgs.length;
		var ele = this;
		var tokenValue = 0;
		// Fonctions
		var rotate = function() {
			$(ele).css('background-image','url(' + opts['imgs'][tokenValue] + ')');
			$('.selected').removeClass('selected');
			$('#slideshow-' + tokenValue).addClass('selected');
			tokenValue++;
			if(imgsLength == tokenValue)
				tokenValue = 0;
			setTimeout(rotate, opts.speed);
		};
		// Main
		$(ele).addClass('slideshow');
		for(var i = 0; i < imgsLength; i++){
			$(ele).append('<span id="slideshow-' + i + '" class="slideshow-link">' + (i + 1) + '</span>');
		}
		rotate();
		$("div.slideshow").delegate("span.slideshow-link", "click", function(pEvent){
			tokenValue = parseInt($(pEvent.target).text()) - 1;
			$('.selected').removeClass('selected');
			$(pEvent.target).addClass('selected');
			$(ele).css('background-image','url(' + opts['imgs'][tokenValue] + ')');
		});
		return $(this);
	};
})(jQuery);

VII. Je peux télécharger les sources et les utiliser sur mon site ?

Bien sûr, même si c'est pour une utilisation commerciale. Les contenus de mes labs sont sous licence Creative Commons et je ne demande qu'une chose: qu'on respecte la paternité de ma création. Pour ceux qui ont des doutes sur la façon dont ils ont recopié mon code, ils peuvent fouiller les sources des pages, ils devraient tout trouver assez facilement.

VII-A. Et les photos de la démo ?

J'ai utilisé des photos provenant du site FlickRFlickR, ces photos sont sous licence CreativeCommons, vous pouvez donc les utiliser comme bon vous semble tant que vous n'oubliez pas de respecter certaines règles. D'ailleurs, c'est mon tour de respecter la licence :

VIII. Remerciements

Je tiens à remercier Claude LELOUPClaude LELOUP pour sa relecture attentive de cet article et Daniel HAGNOULDaniel HAGNOUL pour ses conseils et ses tests. Je les remercie aussi tous les deux, ainsi que BovinoBovino, pour leur bonne humeur et leur accompagnement pour cette première publication.

Votre avis et vos suggestions sur cet article m'intéressent ! Alors, après votre lecture, n'hésitez pas : 5 commentaires Donner une note à l'article (5)