dimanche 22 mars 2009
Hier, j'ai démontré comment on pouvait programmer un diaporama de base pour afficher des photos. Bien que le code était fonctionnel, sa structure laissait à désirer. Avant de lire cette deuxième partie, je vous suggère de lire l'introduction du sujet, vous pourrez mieux comparer les améliorations apportées pour transformer le code en orientée objet. J'introduirai aussi le concept des closures JavaScript (fermetures), qui sont nécessaires à la réalisation de cet exercice.
La première amélioration apportée sera d'ajouter deux boutons pour pouvoir démarrer et arrêter le défilement des photos :
<input type="button" id="btnStart" value="Démarrer" />Contrairement à l'exemple précédent, comme le diaporama ne débutera pas automatiquement, on voudra cacher la photo et sa vignette jusqu'à ce que le bouton soit cliqué. Nous allons placer les balises <img> et <span> dans un conteneur que nous allons cacher au chargement de la page.
<input type="button" id="btnEnd" value="Terminer" />
<div id="slideshow">Avec Prototype (mais on aurait aussi pu prendre jQuery), on appelle un fonction d'initialisation qui attache les événements aux deux boutons.
<p><img id="pictureHolder" src=""></p>
<span id="counter"></span>
</div>
document.observe('dom:loaded', initialize );On définit les méthodes et on inclut les appels à un objet "s" encore inexistant, qui sera l'objet slideshow, instancié globalement.
function initialize() {
initSlideshow();
Event.observe('btnStart', 'click', showSlideshow );
Event.observe('btnEnd', 'click', hideSlideshow );
}
function initSlideshow() {Jusqu'ici, nous n'avons pas encore couvert la refonte orientée objet de notre slideshow. Par contre, on sait qu'on voudra obtenir un code semblable à ceci, où le constructeur (new Slideshow) prendra comme attributs :
hideSlideshow();
}
function showSlideshow() {
$('slideshow').show();
s.start();
}
function hideSlideshow() {
$('slideshow').hide();
s.end();
}
- l'ID du conteneur d'image <img>
- l'ID du compteur, la vignette <span>
- le chemin du répertoire où sont placées les images
- un array qui contient la liste des images
var path = 'images/vacances/';Pour commencer, il faut définir l'objet :
var pics = new Array(
'001.jpg',
'002.jpg',
'003.jpg',
'004.jpg',
'005.jpg',
'006.jpg',
'007.jpg',
'008.jpg',
'009.jpg',
'010.jpg'
);
var s = new Slideshow('pictureHolder', 'counter', path, pics);
s.showLengthInSeconds(1);
// et plus tard, contrôler le défilement
s.start();
s.end();
function Slideshow(pImageId, pCounterId, pPath, pPicsArray) {Ici, on utilise le mot clé "this" pour attacher des propriétés à l'objet Slideshow. Les variables sont initialisées aux valeurs par défaut et selon les paramètres reçus.
var _self = this; // closure :-)
this.holderId = pImageId;
this.counterId = pCounterId;
this.path = pPath;
this.pics = pPicsArray;
this.appearLength;
this.currentPicture = 0;
this.timerDisplay;
this.timerChange;
// pour le moment, j'assume qu'il y a au moins une photo
this.totalPictures = this.pics.length;
}
La première ligne sert à créer une fermeture (closure), c'est-à-dire une référence sur l'objet actuel Slideshow. Comme notre objet contiendra des fonctions, si nous essayons d'utiliser "this" à l'intérieur, ce sera le "this" de la portée (scope) de la fonction, et non pas le "this" de notre objet. En créant une référence dans une variable dès le départ, nous serons en mesure de faire la distinction entre la portée locale de la fonction et globale de l'objet.
À l'intérieur de Slideshow(), on attachera des fonctions anonymes (leur définition ne contient pas de nom, on les appellera par le nom associé à l'objet)
this.showLengthInSeconds = function(sec) {En conclusion, il y a trois notions importantes à retenir pour réussir à faire fonctionner le tout sans problème. Elles se retrouvent dans les fonctions displayFor() et waitFadeOutEnd() :
_self.appearLength = sec;
}
this.start = function() {
_self.loadPicture();
}
this.loadPicture = function() {
// remarquez l'utilisation de _self
$(_self.holderId).src = _self.path + _self.pics[_self.currentPicture];
_self.showCounter();
Effect.Appear(_self.holderId, { duration:1, from:0.0, to:1.0 });
_self.displayFor(_self.appearLength);
}
this.showCounter = function() {
$(_self.counterId).update( (_self.currentPicture + 1) + ' / ' + _self.totalPictures + ' - ' + _self.pics[_self.currentPicture]);
}
this.displayFor = function(sec) {
// utilisation du closure pour faire le callback
_self.timerDisplay =
setTimeout(
function() { _self.changePicture(); },
sec * 1000
);
}
this.changePicture = function() {
_self.currentPicture++;
if (_self.currentPicture == _self.totalPictures )
_self.currentPicture = 0;
Effect.Fade(_self.holderId, { duration:1, from:1.0, to:0.0 });
_self.waitFadeOutEnds(1); // 1 sec, according to duration
}
this.waitFadeOutEnds = function(sec) {
// setTimeout s'attend à recevoir des millisecondes, donc paramètre secondes * 1000
_self.timerChange =
setTimeout(
function() { _self.loadPicture(); },
sec * 1000
);
}
this.end = function() {
clearTimeout( _self.timerDisplay );
clearTimeout( _self.timerChange );
}
- l'utilisation de setTimeout pour faire un appel à une fonction à un intervalle régulier
- conserver une référence sur le timerID (_self.timerDisplay et _self.timerChange). Ceci nous permettra de stopper le callback, donc d'arrêter le diaporama
- l'utilisation du closure _self pour appeler la fonction à l'intérieur d'une fonction anonyme, plutôt que d'utiliser setTimeout avec le code en texte