Pour mon premier article sur ce blog, j'ai choisi de présenter MooTools, un framework Javascript open source orienté objet distribué sous licence MIT.
Après une rapide présentation des bases, je montrerai, grâce à quatre courts exemples, quelques unes des possibilités de MooTools, en espérant vous donner l'envie d'aller plus loin par vous-mêmes.

Récupération de la librairie MooTools

La version actuelle est la 1.2.4, le core est disponible sur le site officiel en version compressée (par YUI ou JSMin) et non compressée.
Il est également possible de définir sa propre version de MooTools en choisissant les modules à intégrer au moment du téléchargement, ce qui permet d'en optimiser la taille au maximum.
Les plugins officiels sont disponibles ici.

La classe Element

Element est la classe de base de MooTools, c'est par elle que l'on doit passer pour créer ou récupérer un noeud DOM.
Des getters et setters permettent de manipuler les attributs des noeuds, les styles et les événements :

 var myButton = new Element('button');
 myButton.set('text', 'Je suis un bouton');

Ci-dessous, un exemple de création d'un bouton plus complet utilisant la syntaxe JSON :

 var myOtherButton = new Element('button', {
   'class': 'BigButton',
   'html': 'Je suis un très gros bouton',
   'styles': { 'font-size': '14px', 'font-weight': 'bold' },
   'events': {
     'click': function(){ alert('Click'); },
     'mouseover': function(){ alert('Mouseover'); }
   }
 });

Le parcours de l'arborescence

La fonction $ permet de récupérer un Element unique :

 var buttonOK = $('btnOK'); // retourne l'Element ayant l'ID 'btnOK';

La fonction $$ permet de récupérer une collection d'Element :

 var forms = $$('form'); // retourne tous les formulaires
 var inputsWithValue = $$('input[value]'); // retourne tous les inputs ayant l'attribut 'value'
 var gifImages = $$('img[src$=.gif]'); // retourne toutes les images ayant l'extension '.gif'
 var buttons = $$('button', 'input[type=button]'); // retourne tous les boutons et les inputs de type bouton
 var bigInputs = $$('#form input.BigButton'); // retourne tous les inputs de classe 'BigButton' appartenant à l'élément ayant l'ID 'form'

A noter qu'il est possible de travailler directement à partir des noeuds DOM :

 var buttonOK = $(document.getElementById('btnOK'));
 var inputs = $$(document.getElementsByTagName('input'))

Pour les recherches qui doivent s'effectuer à partir d'un élément précis, il existe les fonctions getElement() et getElements() :

 var loginForm = $('loginForm');
 var loginInputs = loginForm.getElements('input');

Exemple 1 : création d'un bouton qui change le style de la page

Nous allons commencer par créer un bouton en HTML, avec un identifiant afin de le retrouver plus facilement.
Le code Javascript se résumera à l'ajout de l'évènement 'click' sur ce bouton et à la création de la fonction à appeler.
Cette dernière se contente ici de modifier la couleur du body et de la police, et d'ajouter un padding-left à tous les paragraphes.

Code HTML :

 <button id="btnStyle">Changer le style</button>

Code Javascript :

 window.addEvent('domready', function() {
   $('btnStyle').addEvent('click', changeStyle);
 }
 var changeStyle = function() {
   $(document.body).setStyles({ 'background-color': '#000000', 'color': '#ffffff' });
   $$('p').each(function(p) {
     p.setStyle('padding-left', '20px');
   });
 }

Les évènements

L'évènement 'domready' permet de lancer l'exécution d'une fonction lorsque le DOM est prêt, c'est-à-dire lorsque l'arborescence est prête à être lue et écrite.
La différence avec 'onload' est que la vérification ne se fait que sur le DOM et non sur les ressources annexes telles que CSS, images... etc...

 window.addEvent('domready', function() {
   alert('Traitement...');
 });

Les autres évènements sont les évènements Javascript traditionnels :
- boutons de la souris (click, dblclick, mouseup, mousedown, contextmenu, mousewheel, ...)
- mouvements de la souris (mousemove, mouseover, mouseout, ...)
- clavier (keypress, keydown, keyup, ...)
- fenêtre (load, unload, beforeunload, resize, move, ...)
- éléments (focus, blur, change, reset, ...)

Il est possible de créer un évènement personnalisé grâce à table Element.Events, puis de l'appeler comme un autre évènement.

Exemple 2 : création d'un évènement pour gérer le déplacement d'un conteneur

La première étape consiste à créer un container en position absolute et de lui donner un identifiant pour nous permettre de le récupérer rapidement.
A l'intérieur nous mettons une image, un avion qui va se déplacer lorsqu'on appuie sur la flèche de droite ou sur la touche D.
Puis, nous créons l'évènement, en lui fournissant un évènement de base, keydown, et une fonction précisant les conditions pour que l'évènement soit lancé.

Code HTML :

 <div id="plane" style="position: absolute"><img src="avion.png"/></div>

Code Javascript :

 Element.Events.planemove = {
   'base': 'keydown',
   'condition': function(event) {
     return event.key == "d" || event.key == "right";
   }
 };
 $(document).addEvent('planemove', function(event) {
   var image = $('plane');
   var pos = image.getPosition();
   pos.x++;
   image.setPosition({ 'x': pos.x, 'y': pos.y });
 });

Les classes

MooTools permet de créer ses propres classes.
L'héritage se définit par Extends et Implements, le premier permettant de redéfinir les variables et fonctions de la classe mère mais pas le second.
Le constructeur correspond à la fonction initialize.

Exemple 3 : création d'une classe Square pour dessiner des carrés sur la page

Afin de présenter l'héritage, commençons par créer une classe Shape.
Celle-ci ne définit qu'un constructeur, contenant les coordonnées du point d'origine de la forme à dessiner.
Puis nous créons la classe Square, qui hérite de Shape, et qui possède une largeur.
La fonction draw() nous permet de dessiner le carré dans le body, en utilisant un div correspondant aux positions et dimensions.

 var Shape = new Class({
   'initialize': function(options) {
     this.x = options.x;
     this.y = options.y;
   }
 });
 var Square = new Class({
   'Extends': Shape,
   'initialize': function(options) {
     this.parent(options)
     this.width = options.width;
   },
   'draw': function() {
     if (this.width) {
       var element = new Element('div', {
         'text': 'Carré',
         'styles': {
           'position': 'absolute',
           'top': this.x,
           'left': this.y,
           'height': this.width,
           'width': this.width,
           'background-color': '#AA0000'
         }
       }).inject(document.body);
     }
     return this;
   }
 });
 var square = new Square({ 'x': 300, 'y': 30, 'width': 100 });
 square.draw();

Les effets

La librairie utilisée est MooFX, disponible également pour Prototype.
Les effets utilisent des transitions, une transition pouvant être linéaire, cubique, élastique... etc...
Les effets présents dans le Core sont Tween et Morph, le premier permet d'appliquer une transition sur une valeur CSS, et le second de le faire sur plusieurs.

Exemple 4 : création d'un menu simple qui apparait lorsqu'on clique sur un bouton
Attention, cet exemple requiert un plugin disponible sur le site officiel, Fx.Slide.

Le menu ci-dessous se compose d'une liste de liens affichés en colonne, et masqués (barre de menu).
Ils apparaissent et disparaissent lorsqu'on clique sur un bouton (flèche), avec un effet de glissement.
Remarque : le style (couleurs, images, marges...) doit être codé en CSS et donc ne se trouve pas ci-dessous.

 // Menus à afficher
 var menuLabels = [['Menu 1', 'http://mootools.net'],
     ['Menu 2', 'http://www.sfeir.com'],
     ['Menu 3', 'http://www.google.fr']];
 // Barre de menu
 var menuBar;
 // Classe créée pour la barre de menu
 var MenuBar = new Class({
   'initialize': function(items) {
   // Menus à afficher
   this.items = items;
   // Element de l'arborescence
   this.rootElement = new Element('div');
   if (this.items) {
     var l = this.items.length;
       for (var i = 0; i < l; i++) {
         var a = new Element('a', {
           'text': this.items[i][0],
           'class': 'Menu',
           'href': this.items[i][1],
           'events': {
             // Petit effet de rollover
             'mouseover': function() { this.set('class', 'MenuOver'); },
             'mouseleave': function() { this.set('class', 'Menu'); }
           }
         }).inject(this.rootElement);
       }
     }
   },
   // Fonction permettant d'ajouter la barre de menu à un élément parent
   // Le slide hide permet de la masquer instantanément au démarrage
   'appendTo': function(parentElement) {
     this.rootElement.inject(parentElement).slide('hide');
   },
   // Ouverture de la barre : on fait apparaître les liens avec la transition Bounce
   'open': function() {
     this.rootElement.set('slide', {duration: 'long', transition: 'bounce:out'}).slide('in');
   },
   // Fermeture de la barre : on fait disparaître les liens avec la transition Pow
   'close': function() {
     this.rootElement.set('slide', {duration: 'long', transition: 'pow:out'}).slide('out');
   }
 });
 // Bouton pour ouvrir le menu
 var opacity = 0.4;
 var openArrow = new Element('div', {
   'html': '&#9660;',
   'class': 'Arrow',
   'styles': { 'opacity': opacity },
   'events': {
     'mouseover': function() {
       this.setStyle('text-decoration', 'underline');
       this.fade(1);
     },
     'mouseleave': function() {
       this.setStyle('text-decoration', 'none');
       this.fade(opacity);
     },
     'click': function() {
       openMenu();
     }
   }
 });
 // Bouton pour fermer le menu
 var closeArrow = new Element('div', {
   'html': '&#9650;',
   'class': 'Arrow',
   'events': {
     'click': function() { closeMenu(); }
   }
 });
 // Fonction d'ouverture du menu : on change de flèche et on ouvre la barre de menus
 function openMenu() {
   closeArrow.replaces(openArrow);
   menuBar.open();
 }
 // Fonction de fermeture du menu : on change de flèche et on ferme la barre de menus
 function closeMenu() {
   openArrow.replaces(closeArrow);
   menuBar.close();
 }
 window.addEvent('domready', function() {
   // Création d'un wrapper en position absolute
   var menuWrapper = new Element('div', {
     'styles': { 'position': 'absolute', 'width': '100%', 'top': '0px', 'left': '0px', 'text-align': 'center' }
   });
   menuWrapper.inject($(document.body));
   // Création de la barre de menu
   menuBar = new MenuBar(menuLabels);
   menuBar.appendTo(menuWrapper);
   // Ajout du bouton d'ouverture
   openArrow.inject(menuWrapper);
 }

Conclusion

Cet article a présenté les bases de MooTools, c'est-à-dire comment manipuler les éléments, créer des classes et des événements.
Le framework contient d'autres outils, par exemple pour gérer les cookies, les requêtes au format XML, HTML et JSON.

La spécificité de MooTools est qu'il dispose d'un ensemble de classes qui en fait quasiment un langage de programmation complet.
Là où avec jQuery par exemple, la création d'un élément se résume à une copie de code HTML, MooTools utilise une syntaxe plus naturelle au programmeur objet, plus structurée et plus simple à maintenir.

La contrepartie est la nécessité de connaître un minimum Javascript et la programmation objet, ce qui peut rebuter ceux qui ne se sentent pas particulièrement à l'aise et qui souhaitent simplement avoir rapidement du code qui tourne.

De plus, MooTools possède une communauté moins importante que ses concurrents les plus célèbres, les principaux inconvénients étant les plugins moins nombreux, et le fait qu'on peut avoir un peu plus de difficulté à trouver des réponses à ses questions lorsqu'on se trouve face à un problème.