Expertise (5/5)
J'utilise régulièrement JavaScript depuis 2 ans. J'ai développé de nombreux greffons pour JQuery.
Intégration de Google Map (sélection d'un lieu avec la souris).
Carrousel de photos.
Trombinoscope animé.
Slider (dans toutes les directions).
... et de très nombreuses autres fonctions plus ou moins spécifiques.
Ce document contient quelques exemples simples de réalisation.
Déclaration d'une variable: var x;
Assignation d'une valeur à une variable: x = 10;
La portée des variables déclarées dans une fonction est limitée à la fonction. Mais, contrairement à d’autres langages (C, C++, Java, GO...),
la portée des variables déclarées dans un bloc ({...}
) n’est pas limitée au bloc. De nombreux développeurs sont spécialisés dans des langages
tels que C, C++ ou Java et ce dernier point peut engendrer de nombreuses erreurs: en déclarant des variables dans des blocs,
ils pensent que la portée de ces dernières est limitée aux blocs dans lesquels sont déclarées. C'est faux.
Le script ci-dessous utilise une fonction anonyme pour « isoler » les variables utilisées dans le bloc d’une boucle.
La variable x n'est pas définie en dehors de la boucle. En revanche la variable i est définie. Pour limiter la portée de la variable i
on peut utiliser une fonction anonyme de la façon suivante:
var n = 2;
(function() {
for (var newi=0; newi<5; newi++) {
var x = newi*n;
console.log(newi + ': ' + x);
}
})();
console.log("The variable newi is " + ('undefined' === typeof newi ? "not ": "") + "defined."); // => The variable newi is not defined.
console.log("The variable x is " + ('undefined' === typeof x ? "not ": "") + "defined."); // => The variable x is not defined.
Note importante sur le processus de "déclaration implicite ":
la déclaration d'une variable peut être effectuée de façon implicite lors de la première assignation d'une valeur à une variable
(sans utilisation du mot clé var ).
Par exemple
var i = 0; // Déclaration explicité de la variable i.
var f = function() {
x = 10; // Déclaration **implicite** de la variable x.
};
Ce mécanisme de déclaration implicite est la cause de très nombreux dysfonctionnements extrêmement difficiles à diagnostiquer. En effet, il est très
facile d’oublier d’utiliser le mot clé
var . Et, dans ce cas, l’interpréteur JavaScript ne lance aucun avertissement.
L’interpréteur va « remonter » les « zones de portées des variables » à la recherche de la variable.
Si une variable de même nom est déclarée dans une « zone de portée » alors la nouvelle valeur sera assignée à la variable.
Sinon (si l’interpréteur « remonte » jusqu’à la zone de portée « terminale »), l’interpréteur crée une variable globale.
Illustration:
if ('undefined' === typeof y) { console.log("The variable y is not (yet) declared."); }
else { console.log("The variable y is declared and y = " + y); }
var f1 = function() {
var x;
var f2 = function() {
// Anonymous function af1
(function() {
// Anonymous function af2
(function() {
x = 3;
y = 4;
})();
})();
}
f2();
console.log("Within the function f1(): x = " + x);
console.log("Within the function f1(): y = " + y);
}
f1();
if ('undefined' === typeof x) { console.log("The variable x is not declared."); }
else { console.log("The variable x is declared and x = " + y); }
if ('undefined' === typeof y) { console.log("The variable x is not declared."); }
else { console.log("The variable y is declared and y = " + y); }
Traitement de la variable x:
La variable x n'est pas déclarée dans la function anonyme "of2()". Donc l'interpréteur va « remonter »: il cherche la déclaration de la variable x dans la fonction anonyme "of1()".
La variable x n'est pas déclarée dans la function anonyme "of1()". Donc l'interpréteur ba « remonter »: il cherche la déclaration de la variable x dans la fonction "f2()".
La variable x n'est pas déclarée dans la function anonyme "f2()". Donc l'interpréteur va « remonter »: il cherche la déclaration de la variable x dans la fonction "f1()".
La variable x est déclarée dans le function "f1()". Donc l'interpréteur assigne la valeur 3 à la variable x déclarée dans la fonction "f1()".
Traitement de la variable y:
La variable y n'est pas déclarée dans la function anonyme "of2()". Donc l'interpréteur va « remonter »: il cherche la déclaration de la variable y dans la fonction anonyme "of1()".
La variable y n'est pas déclarée dans la function anonyme "of1()". Donc l'interpréteur va « remonter »: il cherche la déclaration de la variable y dans la fonction "f2()".
La variable y n'est pas déclarée dans la function anonyme "f2()". Donc l'interpréteur va « remonter »: il cherche la déclaration de la variable y dans la fonction "f1()".
La variable y n'est pas déclarée dans la function anonyme "f1()". Donc l'interpréteur va « remonter »: il cherche la déclaration de la variable y dans la zone de portée terminale.
La variable y n'est pas déclarée dans la zone de portée terminale. Donc l'interpréteur va déclarer la variable y dans la zone de portée terminale. y est donc une variable globale.
Résultat à l'exécution:
$ node test.js
The variable y is not (yet) declared.
Within the function f1(): x = 3
Within the function f1(): y = 4
The variable x is not declared.
The variable y is declared and y = 4
Le "hoisting" (to "hoist" peut être traduit pas "hisser" ou "lever") est une caractéristique originale du JavaScript. Ce mécanisme, s'il n'est pas bien
compris par le développeur, peut engendrer des erreurs extrêmement difficiles à détecter .
Le "hosting" consiste à déplacer les déclarations des variables au début des blocs qui délimitent leurs portées.
Autrement dit:
(function() {
console.log("x = " + x + " f = " + f); // => x = undefined f = undefined
// Declaration + initialization
var x = 1;
var f = function() {};
})();
Est équivalent à:
(function() {
// Declaration
var x, f;
console.log("x = " + x + " f = " + f); // => x = undefined f = undefined
// initialization
x = 2;
f = function() {};
})();
Mais, attention , ce n'est pas équivalent à:
(function() {
try {
console.log("x = " + x + " f = " + f);
// initialization
x = 2; // This is NOT a declaration (var).
f = function() {}; // This is NOT a declaration (var).
} catch(e) {
console.log("ERROR: " + e.message); // => ERROR: x is not defined (and f is not defined either).
}
})();
Note importante sur le mécanisme de "hoisting": le mécanisme de "hoisting" produit parfois des effets difficiles à prédire.
// The variable "x" should be declared (since it is implicitly declared below).
if ('undefined' === typeof x) { console.log("The variable x is not defined, but it is declared due to the hoisting mechanism."); }
x = 1;
// The variable "y" should be declared (since it is implicitly declared within the anonymous function below).
if ('undefined' === typeof y) { console.log("The variable y is not defined, but it is declared due to the hoisting mechanism."); }
// In the anonymous function below we implicitly declare the variable "y".
(function() {
try {
var variable = y;
} catch (e) {
console.log("ERROR: " + e.message + " (you should understand: y is not **declared**)");
}
y = 2; // Here we "declare" y.
})();
// The variable "y" should be declared and its value shold be 2.
if ('undefined' === typeof y) { console.log("The variable y is not defined, but it is declared due to the hoisting mechanism."); }
else { console.log("The variable y is declared and tis value is: " + y); }
(function() {
var variable = y;
// The variable "y" should be declared and its value shold be 2.
console.log("The variable y is declared and tis value is: " + y);
})();
Résultat à l'exécution:
$ node test.js
The variable x is not defined, but it is declared due to the hoisting mechanism.
The variable y is not defined, but it is declared due to the hoisting mechanism.
ERROR: y is not defined (you should understand: y is not **declared**)
The variable y is declared and tis value is: 2
The variable y is declared and tis value is: 2
Vous constatez le message suivant:
ERROR: y is not defined (you should understand: y is not **declared**)
Wikipedia: http://fr.wikipedia.org/wiki/Fermeture_%28informatique%29
Dans un langage de programmation, une fermeture ou clôture (en anglais : closure) est une fonction qui capture des références à des variables libres
dans l'environnement lexical. Une fermeture est donc créée, entre autres, lorsqu'une fonction est définie dans le corps d'une autre fonction et fait
référence à des arguments ou des variables locales à la fonction dans laquelle elle est définie.
La nature asynchrone du JavaScript rend indispensable l’utilisation des closures. En effet, entre le moment où un gestionnaire d’évènement est exécuté,
et le moment où il a été préalablement défini, les valeurs des variables utilisées par le gestionnaire peuvent avoir été modifiées. Il est donc indispensable
de « capturer » la valeur d’une variable à un instant T afin de pouvoir utiliser cette valeur ultérieurement. Pour cela on utilise la construction suivante :
var eventHandler = function(valueToCapture1, valueToCapture2) {
return function() {
// Use the captured values.
console.log("(valueToCapture1, valueToCapture2) = " + valueToCapture1, valueToCapture2);
};
}
Exemple d'utilisation
var handlers = new Array();
for (var i=0; i<3; i++) {
var eventHandler = function(valueToCapture1, valueToCapture2) {
return function() {
// Use the captured values.
console.log("(valueToCapture1, valueToCapture2) = (" + valueToCapture1 + ", " + valueToCapture2.a + ")");
};
}
var object = { a: i };
handlers.push(eventHandler(i*2, object));
}
console.log("Execute the elements of the array handlers:");
for (var i=0; i
ou:
var handlers = new Array();
for (var i=0; i<3; i++) {
var eventHandler = function(valueToCapture1, valueToCapture2) {
return function() {
// Use the captured values.
console.log("(valueToCapture1, valueToCapture2) = (" + valueToCapture1 + ", " + valueToCapture2.a + ")");
};
}
var object = { a: i };
setTimeout(eventHandler(i*2, object), 200);
}
Dans le code "handlers.push(eventHandler(i*2, object))
", "i*2
" est transmis pae copie. En revanche "object
" est
transmis par référence. Mais à chaque appel à la fonction "setTimeout()
", une nouvelle référence est transmise. Ainsi les fonctions retournées
par l'appel à "eventHandler()
" utilisent des instances distinctes d'objets.
Le code "var object = { a: i }
" signifie que la valeur de "object
" est une référence vers un objet.
A chaque passage dans la boucle, "object
" prend une nouvelle valeur (car un nouvel objet - "{ a: i }
" - est créé).
Ainsi, à chaque appel de la fonction "
eventHandler()
", le paramètre "
valueToCapture2
" (qui est une référence vers l'objet "
object
") prend une nouvelle valeur.
Résultat à l'exécution:
$ node test.js
Execute the elements of the array handlers:
(valueToCapture1, valueToCapture2) = (0, 0)
(valueToCapture1, valueToCapture2) = (2, 1)
(valueToCapture1, valueToCapture2) = (4, 2)
or (using setTimeout()):
$ node test.js
(valueToCapture1, valueToCapture2) = (0, 0)
(valueToCapture1, valueToCapture2) = (2, 1)
(valueToCapture1, valueToCapture2) = (4, 2)
Remarque sur l'utilisation d'objets (passage par référence)
Maintenant, cosidérons le code ci-dessous:
var object = { a: 1 };
for (var i=0; i<3; i++) {
var eventHandler = function(valueToCapture1, valueToCapture2) {
return function() {
// Use the captured values.
console.log("(valueToCapture1, valueToCapture2) = (" + valueToCapture1 + ", " + valueToCapture2.a + ")");
};
}
object.a = i;
setTimeout(eventHandler(i*2, object), 200);
}
Résultat à l'exécution:
$ node test.js
(valueToCapture1, valueToCapture2) = (0, 2)
(valueToCapture1, valueToCapture2) = (2, 2)
(valueToCapture1, valueToCapture2) = (4, 2)
Contrairement à l'exemple précédent, on constate que la valeur affichée de "valueToCapture2.a
" ne change pas (elle vaut 2).
Cette valeur correspond à la valeur de "i
" lors du dernier passage dans la boucle.
Explication: contrairement à ce qui se passe dans l'exemple précédent, la valeur de "valueToCapture2
" ne change pas d'un passage
à un autre dans la boucle. Cette valeur est égale à la référence de l'objet "object
".
Pour obtenir le comportement "attendu", une solution serait de créer un clone de l'objet
object
, comme dans l'exemple ci-dessous.
// sudo npm install extend (see https://www.npmjs.com/package/merge)
var extend = require('extend');
var object = { a: 1 };
for (var i=0; i<3; i++) {
var eventHandler = function(valueToCapture1, valueToCapture2) {
return function() {
// Use the captured values.
console.log("(valueToCapture1, valueToCapture2) = (" + valueToCapture1 + ", " + valueToCapture2.a + ")");
};
}
object.a = i;
// We create a copy of "object", which means that we pass a new reference (to a new object).
setTimeout(eventHandler(i*2, extend(null, {}, object)), 200);
}
Cette section présente deux techniques pour la création d’objets :
La première technique consiste à définir un prototype qui servira à la construction de l’objet. Ce prototype définit les propriétés et les
méthodes de l’objet. Les éventuelles opérations d’initialisation de l’objet sont effectuées dans le constructeur.
La seconde technique consiste à se passer de prototype : la définition des propriétés et des méthodes de l’objet est incluse dans le
constructeur. Ce dernier effectue également les éventuelles opérations d’initialisation de l’objet.
Technique 1: avec prototype
var Personne = function(inName) { // This is the constructor.
// Do some initialization.
console.log("Executing the constructor Personne.");
if ('undefined' != typeof inName) {
this.name = inName;
}
}
var PersonnePrototype = { // This is the prototype.
name: undefined,
setName: function(inName) { this.name = inName; },
getName: function() { return this.name; }
};
Personne.prototype = PersonnePrototype;
var p = new Personne("Tom");
console.log("The personne's name is %s.", p.getName());
p.setName("Joe");
console.log("The personne's name is %s.", p.getName());
Technique 2: sans prototype
var Personne = function(inName) {
// We define the prototype here.
this.name = undefined;
this.setName = function(inName) { this.name = inName; },
this.getName = function() { return this.name; }
// Do some initialization.
console.log("Executing the constructor Personne with inName=%s.", inName);
if ('undefined' !== typeof inName) {
this.name = inName;
}
}
Personne.prototype = PersonnePrototype;
var p = new Personne("Tom");
console.log("The personne's name is %s.", p.getName());
p.setName("Joe");
console.log("The personne's name is %s.", p.getName());
Ces deux techniques ne sont pas équivalentes. Elles diffèrent, principalement, sur deux points :
La deuxième technique consomme plus de ressources. En effet, à chaque instanciation d’un nouvel objet, les propriétés et les méthodes
sont clonées. Ce n’est pas le cas si l’on définit un prototype.
La modification d’un prototype entraine la modification instantanée de tous les objets instanciés via ce prototype.
Ainsi, si l’on désire ajouter une propriété à toutes les personnes, il suffit d’ajouter cette propriété au prototype ayant servi à l’instanciation des personnes.
Illustration:
var Personne = function(inName) { // This is the constructor.
if ('undefined' != typeof inName) {
this.name = inName;
}
}
var PersonnePrototype = { // This is the prototype.
name: undefined,
setName: function(inName) { this.name = inName; },
getName: function() { return this.name; }
};
Personne.prototype = PersonnePrototype;
for (var i=0; i<10; i++) {
var p = new Personne("Tom" + i);
// =====> At this point, p has no method "getAge()" !!!!!
setTimeout(function() { console.log("Name: %s", p.getName(), p.getAge()); }, 1000)
}
// Now, lets add an age to all personnes.
PersonnePrototype.age = 12;
PersonnePrototype.getAge = function() { return this.age; }
A l'exécution:
$ node oo.js
Name: Tom9 12
Name: Tom9 12
Name: Tom9 12
Name: Tom9 12
Name: Tom9 12
Name: Tom9 12
Name: Tom9 12
Name: Tom9 12
Name: Tom9 12
Name: Tom9 12
Le code ci-dessous présente deux techniques qui permettent de faire hériter un objet des propriétés et des méthodes d’un autre objet.
if (process.argv.length < 3) {
console.log('Usage: node "%s" ', process.argv[1]);
return;
}
var test = process.argv[2];
if (test == 1) {
// -------------------------------------------------
// Executing test 1.
// -------------------------------------------------
console.log("Executing test 1");
(function() {
// ---------------------------------------------
// We define a Personne.
// ---------------------------------------------
var Personne = function(inName) { // This is the constructor.
// Do some initialization.
console.log("Executing the constructor Personne.");
if ('undefined' != typeof inName) {
this.name = inName;
}
}
var PersonnePrototype = { // This is the prototype.
name: undefined,
setName: function(inName) { this.name = inName; },
getName: function() { return this.name; }
};
Personne.prototype = PersonnePrototype;
// ---------------------------------------------
// We define a Student.
// ---------------------------------------------
var Student = function(inAge, inName) { // This is the constructor.
// Do some initialization.
console.log("Executing the constructor Student with inAge=%d and inName=%s.", inAge, inName);
if ('undefined' !== typeof inAge) {
this.setAge(inAge);
}
if ('undefined' !== typeof inName) {
this.setName(inName);
}
}
Student.prototype = new Personne();
Student.prototype.age = undefined;
Student.prototype.setAge = function(inAge) { this.age = inAge; };
Student.prototype.getAge = function() { return this.age; };
var Tom = new Student(12, "Tom");
console.log("The student %s is %d years old.", Tom.getName(), Tom.getAge());
var Joe = new Student();
Joe.setName("Joe");
Joe.setAge(20);
console.log("The student %s is %d years old.", Joe.getName(), Joe.getAge());
console.log("The student %s is %d years old.", Tom.getName(), Tom.getAge());
console.log(Joe.__proto__);
console.log(Object.getPrototypeOf(Joe));
})();
return;
}
if (test == 2) {
// -------------------------------------------------
// Executing test 2.
// -------------------------------------------------
console.log("Executing test 2");
(function() {
// ---------------------------------------------
// We define a Personne.
// ---------------------------------------------
var Personne = function(inName) {
// We define the prototype here.
this.name = undefined;
this.setName = function(inName) { this.name = inName; },
this.getName = function() { return this.name; }
// Do some initialization.
console.log("Executing the constructor Personne with inName=%s.", inName);
if ('undefined' !== typeof inName) {
this.name = inName;
}
}
// ---------------------------------------------
// We define a Student.
// ---------------------------------------------
var Student = function(inAge, inName) {
// We define the prototype here.
Personne.call(this, inName);
this.age = undefined;
this.setAge = function(inAge) { this.age = inAge; };
this.getAge = function() { return this.age; }
// Do some initialization.
console.log("Executing the constructor Student with inAge=%d and inName=%s.", inAge, inName);
if ('undefined' !== typeof inAge) {
this.setAge(inAge);
}
}
var Tom = new Student(12, "Tom");
console.log("The student %s is %d years old.", Tom.getName(), Tom.getAge());
var Joe = new Student();
Joe.setName("Joe");
Joe.setAge(20);
console.log("The student %s is %d years old.", Joe.getName(), Joe.getAge());
console.log(Joe.__proto__);
console.log(Object.getPrototypeOf(Joe));
})();
return;
}
console.log("Bad test number %d.", test);
A l'exécution:
$ node test.js 1
Executing test 1
Executing the constructor Personne.
Executing the constructor Student with inAge=12 and inName=Tom.
The student Tom is 12 years old.
Executing the constructor Student with inAge=NaN and inName=undefined.
The student Joe is 20 years old.
The student Tom is 12 years old.
{ age: undefined, setAge: [Function], getAge: [Function] }
{ age: undefined, setAge: [Function], getAge: [Function] }
Et:
$ node test.js 2
Executing test 2
Executing the constructor Personne with inName=Tom.
Executing the constructor Student with inAge=12 and inName=Tom.
The student Tom is 12 years old.
Executing the constructor Personne with inName=undefined.
Executing the constructor Student with inAge=NaN and inName=undefined.
The student Joe is 20 years old.
{}
{}
Ces deux techniques ne sont pas équivalentes. Veuillez consulter la rubrique Constructeurs et prototypes .
Cet exemple illustre une utilisation du greffon. Une série de 6 images défile sur trois "cases".
La fréquence de remplacement d'une image par une nouvelle image a été paramétrée à 1 image par seconde. Ce paramètre de configuration est configurable.
Les images sont cliquables. Chaque image est associée à une URL. Cette association est facultative. Si une image n'est pas associée à un lien, alors elle n'est pas cliquable.
Code du greffon
(function($)
{
/**
* Autheur: Denis BEURIVE
* Attention : Ce fichier a fait l'objet d'un dépôt de droit d'auteur auprès de l'INPI.
*
* Ce greffon permet de créer des trombinoscopes.
* Les trombinoscopes sont définis par des listes non ordonnées ().
*
* Chaque trombinoscope définit les propriétés suivantes :
* o [Obligatoire] Une liste de "canvas". Un "canvas" est une zone d'affichage pour une image (nom de l'attibut au choix).
* o [Obligatoire] La durée (en ms) entre deux changements d'images (attribut "period").
* o [facultatif] Un drapeau pour activer le mode aléatoire (attribut "rand").
*
* Ex:
*
* Chaque trombinoscope contient une liste d'images.
* Une image est définie par un item () de la liste non ordonnée qui représente le trombinoscope.
* Chaque image définit les propriétés suivantes :
* o [Obligatoire] Le lien vers la source de l'image (attribut "img").
* o [facultatif] Un lien qui sera ouvert lorsque l'utilisateur cliquera sur l'image (attribut "href").
*
* Ex:
*
* Un "canvas" est une division () dans laquelle est affichée une image.
* L'ID du canvas est représenté par l'attribut "id".
* Le "canvas" peut contenir plusieurs images, mais *une seule* sera affectée au trombinoscope.
* L'image affectée au trombinoscope est repérée par l'attribut "canvas".
*
* Ex:
*
*
*
*
* @example
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
* Mon text 1
* Mon text 2
* Mon text 3
* Mon text 4
* Mon text 5
* Mon text 6
* Mon text 7
* Mon text 8
*
*
*
* Mon text 1
* Mon text 2
* Mon text 3
* Mon text 4
*
*/
$.fn.trombino = function()
{
// http://farinspace.com/jquery-image-preload-plugin/
if(! $.imgpreload) { alert('trombino: Could not load JQuery plugin "imgpreload.js"!'); return; }
if (0 == $(this).length) { return; }
var all = $(this);
// Quel est l'attribut qui représente les trombinoscopes?
// CF questions sur l'URL: http://api.jquery.com/selector/
var trombinoAttribut = this.selector;
trombinoAttribut = trombinoAttribut.replace(/^\[/, '');
trombinoAttribut = trombinoAttribut.replace(/\]$/, '');
// On établit la liste de toutes les images à utiliser.
var allImages = { };
var allImagesList = new Array();
for (i=0; i<$(this).length; i++)
{
// "Loop closure": http://trephine.org/t/index.php?title=JavaScript_loop_closures
var f = function(configContainerElement)
{
var images = trombinoGetImages(configContainerElement);
for (j=0; j
'); }
runTrombino(all, trombinoAttribut);
}
});
}
/**
* Cette fonction construit le trombinoscope et amorce le mécanisme de remplacement périodique des images.
* @param inConfigContainers Liste des éléments du DOM qui représentent les trombinoscopes (
).
* @param inTrombinoAttribut Valeur de l'attribut qui sert à distinguer les trombinoscopes parmi tous les élements du document.
* Cette valeur sert de sélecteur JQuery pour l'exécution du greffon ($(function() { $('[trombi]').trombino(); }););
*/
function runTrombino(inConfigContainers, inTrombinoAttribut)
{
// alert(document.images.length);
if (0 == inConfigContainers.length) { return; }
for (i=0; i) sur lequel on change une image.
* @param inTrombinoAttribut inTrombinoAttribut Valeur de l'attribut qui sert à distinguer les trombinoscopes parmi tous les élements du document.
* Cette valeur sert de sélecteur JQuery pour l'exécution du greffon ($(function() { $('[trombi]').trombino(); }););
* @param Durée (en ms) entre deux exécutions de la fonction.
*/
function trombinoEventHandler(inConfigContainer, inTrombinoAttribut, inPeriod)
{
// Le mode aléatoire d'affichage des images est-il activé?
var rand = inConfigContainer.attr('rand');
if (undefined == rand) { rand = false; }
else { rand = rand == 'yes' ? true : false; }
var currentImage = parseInt(inConfigContainer.attr('currentImage')); // Index courant (dans la liste des images).
var images = trombinoGetImages(inConfigContainer); // Liste des images.
var imagesNumber = images.length; // Nombre d'images.
var canvas = trombinoGetCanvas(inConfigContainer, inTrombinoAttribut); // Liste des canvas.
var canvasNumber = canvas.length; // Nombre de canvas.
// Index courant (dans la liste des canvas du tormbino).
var currentCanvas = rand ? Math.floor(Math.random()*canvasNumber) : parseInt(inConfigContainer.attr('currentCanvas'));
// http://api.jquery.com/descendant-selector/
var img = $('#' + canvas[currentCanvas] + ' img[canvas=yes]');
if (0 == img.length) { alert('trombinoEventHandler: Can not find image!'); return; }
// On supprime l'association sur l'évènement "click".
// Cette action est indispensable, sinon les gestionnaires d'évènement vont "s'empiler".
img.unbind('click');
// On change d'image.
// L'animation ci-dessous ne change pas la valeur de la propriété "display" de l'image.
// L'image ne "disparaît" pas du document. Il n'est pas nécessaire de "fixer" la taille du cadre.
img.animate({'opacity':0}, 500, function() { img.attr('src', images[currentImage]['img']).fadeIn(1000); }).animate({'opacity':1}, 500);
// Si un lien est défini pour cette image, alors on crée le lien.
if (undefined != images[currentImage]['href'])
{
img.css('cursor', 'pointer');
img.click(function(){ window.open(images[currentImage]['href']); return false; });
}
// On passe à l'image suivante, et au canvas suivant.
currentImage = currentImage+1 >= imagesNumber? 0: currentImage+1;
currentCanvas = currentCanvas+1 >= canvasNumber? 0: currentCanvas+1;
inConfigContainer.attr('currentImage', currentImage);
inConfigContainer.attr('currentCanvas', currentCanvas);
// On réarme l'horloge pour déclencher une nouvelle exécution de la fonction.
window.setTimeout( function() { trombinoEventHandler(inConfigContainer, inTrombinoAttribut, inPeriod); }, inPeriod, 'JavaScript' );
}
/**
* Cette fonction retourne un tableau qui contient les informations relatives à toutes les images d'un trombinoscope.
* @param inConfigContainer Éléments du DOM qui représente le trombinoscope () pour lequel on désire extraire la liste des images.
* @return La fonction retourne un tableau d'objets. Chaque objet présente les attributs suivants :
* o img: Lien vers la source de l'image.
* o href: Lien associé à un click sur l'image. Cet attribut peut prendre la valeur "undefined".
* o text: Du code HTML associé à l'image.
*/
function trombinoGetImages(inConfigContainer)
{
var images = new Array();
var list = inConfigContainer.children('li');
if (0 == list.length) { alert("trombinoGetImages: Can not find the list of images."); }
for (j=0; j) pour lequel on désire extraire la liste IDs des "canvas".
* @param inTrombinoAttribut inTrombinoAttribut Valeur de l'attribut qui sert à distinguer les trombinoscopes parmi tous les élements du document.
* Cette valeur sert de sélecteur JQuery pour l'exécution du greffon ($(function() { $('[trombi]').trombino(); }););
*/
function trombinoGetCanvas(inConfigContainer, inTrombinoAttribut)
{
var list = inConfigContainer.attr(inTrombinoAttribut);
return list.split(',');
}
})( jQuery );
Utilisation
Création de la zone d'affichage des images
Une image est affichée dans un "bloc DIV". Ce bloc est identifié par son nom, associé à l'attribut "id". Le code ci-dessous crée trois blocs alignés horizontalement. Chaque bloc est utilisé pour afficher une image.
Création du trombinoscope
On associe un trombinoscope à une liste de zones d'affichage (en l'occurrence "canvas1", "canvas2", "canvas3").
On définit la fréquence de remplacement d'une image (ici, on remplace une image par une autre toutes les 1 secondes.
On spécifie que l'on ne désire pas activer le "mode aléatoire". Dans le mode aléatoire, l'image à remplacer est choisie aléatoirement.
Chargement du Javascript et démarrage du trombinoscope
L'utilisation du greffon de trombinoscope nécessite le chargement de l'excellent greffon suivant :
https://github.com/farinspace/jquery.imgpreload
Ce greffon très simple permet d'appliquer des liens sur des zones du document.
Un lien sur un texte .
Un lien sur un (autre) texte .
Un lien sur une image:
Code du greffon
// Autheur: Denis BEURIVE
(function($)
{
/**
* Ce greffon applique à des éléments un lien. Ce lien est matérialisé par le changement du pointeur de souris.
* @example
*
*
*/
$.fn.linker = function()
{
if (0 == $(this).length) { return; }
// Quel est l'attribut qui représente les liens?
// CD questions sur l'URL: http://api.jquery.com/selector/
var linkAttribut = this.selector;
linkAttribut = linkAttribut.replace(/^\[/, '');
linkAttribut = linkAttribut.replace(/\]$/, '');
for (i=0; i<$(this).length; i++)
{
var f = function(inElement)
{
// Quel est le lien?
var link = inElement.attr(linkAttribut);
// On assigne à l'élément un pointeur de souris spécial.
inElement.css('cursor', 'pointer');
// On assigne à l'élément un lien.
inElement.click(function(){ window.open(link); return false; });
};
f($(this).eq(i))
}
}
})( jQuery );
Utilisation
Pour associer un lien à un élément du document, il suffit d'ajouter à l'élément un attribut dont la valeur pointe vers le lien désiré. Dans le code ci-dessous, nous avons choisi d'utiliser l'attribut "link". Le choix du nom de l'attribut est arbitraire. Ce nom sera utilisé lors de l'activation du code Javascript (le nom de l'attribut sera passé en argument au greffon).
Création de la zone d'affichage des liens
Ce greffon très simple permet d'appliquer des liens sur des zones du document.
Un lien sur un texte .
Un lien sur un (autre) texte .
Un lien sur une image:
Chargement du Javascript et démarrage
Il est courant d'associer plusieurs noms de domaines à un même site. Or, les clés d'utilisation de l'API Google sont liées à un seul nom de domaine. Le code ci-dessous permet une initialisation de l'API Google quel que soit le nom de domaine utilisé.
// Autheur: Denis BEURIVE
function initGoogeApi()
{
var googleApiKeys = new Array();
googleApiKeys['mon-site.com'] = 'ABQIAAAA-Qri0Xh9bwXT1FTb29hnMRRM07KJyyPVV7FBkBG4AsMiNOs3VRQQQY_hR0TmTyiQhYIg6YP134N10w';
googleApiKeys['mon-site.org'] = 'ABQIAAAA-Qri0Xh9bwXT1FTb29hnMRTWFMwVesQaxusA4sA7huFv8G0lCxT9b8HZEls8-pX8wQWZAcg5csoHiA';
googleApiKeys['mon-site.fr'] = 'ABQIAAAA-Qri0Xh9bwXT1FTb29hnMRQRlkWhloZKyd5SxdXfZqwQHZPOZBQuQUYs6qo7Mz6dT3DYvdBR9x_RZA';
googleApiKeys['mon-site.eu'] = 'ABQIAAAA-Qri0Xh9bwXT1FTb29hnMRSxSswci-l4mhYmTQjwnW1_UmMTyBTlSy0qLOuA9opABYwQ3RWOOnJbhg';
googleApiKeys['monsite.com'] = 'ABQIAAAA-Qri0Xh9bwXT1FTb29hnMRSZhpGKWNvLRUQ0m88BCek7GneUoRTC8MHfSFNvnXdt98h1f8Zp2cVMIQ';
googleApiKeys['monsite.org'] = 'ABQIAAAA-Qri0Xh9bwXT1FTb29hnMRTYp1NFijgaes9p8QLKwxVtJomx_BQwDbea3CR3w7zIXoaJcz0_LamGag';
googleApiKeys['monsite.fr'] = 'ABQIAAAA-Qri0Xh9bwXT1FTb29hnMRSCJtyl3oqxL4Ldn0-knz0wcxwr0BSRXMeBrdg5OUkgdegvfHy_CkREKg';
googleApiKeys['monsite.eu'] = 'ABQIAAAA-Qri0Xh9bwXT1FTb29hnMRS-7_cDrhSxnC37RAR2pqNkJ4VmrBRfmF3ywnY2QBchYHPgiS-QKH_5Gw';
googleApiKeys['monsite.net'] = 'ABQIAAAA-Qri0Xh9bwXT1FTb29hnMRTFcO89IHYOhIMGZHCXEZPt4iyOehQWoUMvoYaDloEHjPDxTuq4WCwcVw';
googleApiKeys['exemple.com'] = 'ABQIAAAA-Qri0Xh9bwXT1FTb29hnMRQS7Uob8cBMVCw7EVtke2s_l__xcxSP0C3PtJd9W4qRFd5qNAkVmI0XfA';
var loc = document.location.host;
document.write('\n');
}
Si vous développer un site communautaire, il peut être intéressant de déporter les calculs des âges de vos membres sur le client.
/**
* Cette fonction calcule l'âge à partie de la date de naissance.
* @author Denis BEURIVE
* @param inDob Data de naissance sous la forme "YYYY-MM-DD".
* @return La fonction retourne l'âge.
*/
function getAge(inDob)
{
var age = 12;
var today = new Date();
d = inDob.split("-");
var byr = parseInt(d[0]);
var nowyear = today.getFullYear();
var bmth = parseInt(d[1],10)-1; // radix 10!
var bdy = parseInt(d[2],10); // radix 10!
var dim = daysInMonth(bmth+1,byr);
var age = nowyear - byr;
var nowmonth = today.getMonth();
var nowday = today.getDate();
if (bmth > nowmonth) {age = age - 1} // next birthday not yet reached
else if (bmth == nowmonth && nowday < bdy) {age = age - 1}
return age;
}
function daysInMonth(month,year)
{
var dd = new Date(year, month, 0);
return dd.getDate();
}
Code du greffon
/**
* This file implements sliders.
* @author Denis BEURIVE
* @warning Ce code a fait l'objet d'un dépôt de droit d'auteur auprès de l'INPI.
*/
(function($)
{
/**
* This function is an event handler. It is executed each time the user requests a new slide.
* @param inEvent Event object, thrown by JQuery.
* This object contains the following properties:
* o panel: The element that represents the frame around the sliders.
* o sens: This property defined wether the user wants to show the previous or the next element.
* Possible value: "next" or "prev".
* o options: List of options for this slider.
* @Note The solution below has been implemented, but it is not used anymore, since the "mutex solution" is simpler.
* It is possible to deactivate the event handler, in the event handler:
* inEvent.data.button.unbind(inEvent);
*
* To reactivate it after the animation:
* var inButton = inEvent.data.button; // It is necessary to pass the element that represents the button.
* var terminationCallback = function() { inButton.bind('click', { panel:inPanel, direction:inDir, sens:inSens, decal:inDecal, button:inButton }, function(event) { slide(event); } ); };
* var options = { 'duration':'slow', 'complete':terminationCallback };
* slidingTable.animate(properties, options);
* slidingTable.effect("bounce", { times:1, direction:bounceDirection, distance:15 }, 300, terminationCallback);
*/
function sliderEventHandler(inEvent)
{
// The frame around the sliders.
var panel = inEvent.data.panel;
// Sens: 'next' (slide left or slide down) or 'prev' (slide right or slide up).
var sens = inEvent.data.sens;
// Options.
var options = inEvent.data.options;
// Shall we continue?
if (1 == parseInt(panel.attr('mutex'))) { return; }
panel.attr('mutex', '1');
// Configuration for the various effects.
var configs = {
'horizontal': {
direction: 'left', // Used for sliding effect
next: 'right', // Used for bouncing effect
prev: 'left' // Used for bouncing effect
},
'vertical': {
direction: 'top', // Used for sliding effect
next: 'down', // Used for bouncing effect
prev: 'up' // Used for bouncing effect
}
};
// Element that will slide.
var slidingWrapper = panel.children('div');
// Name of the CSS property to play with while sliding.
var slideDirection = configs[options['direction']]['direction'];
// Number of pixels to slide.
var decal = 'horizontal' == options['direction'] ? panel.width() : panel.height();
// DEBUG
// console.debug("Déplacement de " + decal + 'px' + ' sur ' + panel.width() + 'x' + panel.height());
// console.debug("Fenêtre glissante: " + slidingWrapper.width() + 'x' + slidingWrapper.height())
// If we reach the limit, in which direction do we bounce?
var bounceDirection = configs[options['direction']][sens];
// Function to execute while the animation is over.
var terminationCallback = function() { panel.attr('mutex', '0'); };
// Maximum allowed distance for the slide.
var max = 'horizontal' == options['direction'] ? slidingWrapper.attr('maxLeft') : slidingWrapper.attr('maxTop');
// Current distance.
var current = 'horizontal' == options['direction'] ? slidingWrapper.css('left') : slidingWrapper.css('top');
// DEBUG
// console.debug("Courant: " + current + ' / Max: ' + max);
// Perform the animation.
// At the end: re-bind the event to the button.
if (sens == 'next')
{
if (current != max)
{
var animationProperties = {};
var animationOptions = { 'duration':'slow', 'complete':terminationCallback };
animationProperties[slideDirection] = '-=' + decal + 'px';
slidingWrapper.animate(animationProperties, animationOptions);
} else { slidingWrapper.effect("bounce", { times:2, direction:bounceDirection, distance:15 }, 300, terminationCallback); }
}
else
{
if (current != '0px')
{
var animationProperties = {};
var animationOptions = { 'duration':'slow', 'complete':terminationCallback };
animationProperties[slideDirection] = '+=' + decal + 'px';
slidingWrapper.animate(animationProperties, animationOptions);
} else { slidingWrapper.effect("bounce", { times:2, direction:bounceDirection, distance:15 }, 300, terminationCallback); }
}
}
/**
* This function is the entry point for the current plugin.
* @note The slides are made of tables ().
* In case of an error, make sure to check the CSS properties for the table and for its columns.
* CSS properties to check are:
* + border-collapse (table only)
* + border-width (table and column)
* + border-style (table and column)
* + padding (table and column)
* + margin (table and column)
*/
$.fn.slider = function()
{
if (0 == $(this).length) { return; }
// Which attribute represents sliders?
var sliderAttribut = this.selector;
sliderAttribut = sliderAttribut.replace(/^\[/, '');
sliderAttribut = sliderAttribut.replace(/\]$/, '');
// Create all sliders returned by the JQuery's selector.
for (i=0; i<$(this).length; i++)
{
var f = function(panel)
{
// Name of the current slider.
var panelName = panel.attr(sliderAttribut);
// List of slides associated with the current slider.
var list = panel.children('ul');
var items = list.children('li');
// Set options.
var options = { direction: 'vertical' };
var direction = panel.attr('direction');
if (undefined != direction) { options['direction'] = direction; }
// Set overflow property, so that sliders dont't "escape".
panel.css('overflow', 'hidden');
var panelWidth = panel.width();
var panelHeight = panel.height();
// console.debug("Largeur de la zone d'affichage: " + panelWidth + 'px');
// console.debug("Hauteur de la zone d'affichage: " + panelHeight + 'px');
// Create sliding table.
// Note: The following CSS properties are very important.
var table = $('');
table.css('border-width', '0px');
table.css('border-style', 'none');
table.css('border-collapse', 'collapse');
table.css('padding', '0px');
table.css('margin', '0px');
table.css('width', panelWidth + 'px');
// Create all columns
var columns = new Array();
for (j=0; j' + items.eq(j).html() + ' ');
div.css('overflow', 'hidden');
div.css('width', panelWidth + 'px');
div.css('height', panelHeight + 'px');
div.css('border-width', '0px');
div.css('border-style', 'none');
div.css('padding', '0px');
div.css('margin', '0px');
// Note: The following CSS properties are very important.
var td = $(' ');
td.css('padding', '0px');
td.css('margin', '0px');
td.css('border-style', 'none');
td.css('border-width', '0px');
td.html(div);
// DEBUG
// console.debug('Largeur de la division interne: ' + div.width());
// console.debug('Largeur de la colonne interne: ' + td.width());
columns.push(td);
}
// Create the wrapper (for IE only!).
var wrapper = $('
');
wrapper.css('padding', '0px');
wrapper.css('margin', '0px');
wrapper.css('position', 'relative');
wrapper.css('top', '0px');
wrapper.css('left', '0px');
// Set local properties that will be used into the event handler.
wrapper.attr('maxLeft', ((items.length - 1) == 0 ? '' : '-') + panelWidth * (items.length - 1) + 'px');
wrapper.attr('maxTop', ((items.length - 1) == 0 ? '' : '-') + panelHeight * (items.length - 1) + 'px');
wrapper.attr('mutext', '0');
// We create row(s).
if (options['direction'] == 'horizontal')
{
var row = $(' ');
for (col in columns) { row.append(columns[col]); }
table.append(row);
wrapper.width(items.length * panelWidth);
wrapper.height(panelHeight);
}
else
{
for (col in columns) { table.append($(' ').append(columns[col])); }
wrapper.width(panelWidth);
wrapper.height(items.length * panelHeight);
}
wrapper.append(table);
// DEBUG
// console.debug("Largeur du tableau inséré dans la zone d'affichage: " + table.width() + 'px');
// console.debug("border-collapse: " + table.css('border-collapse'));
// console.debug("Largeur de l'englobant: " + wrapper.width() + 'px');
// We delete the original list, used to define the slides.
list.remove();
// We add the new table to the panel.
panel.append(wrapper);
// We assign the event handler on the "previous / next" elements.
$('#' + panelName + 'Next').bind('click', { panel:panel, options:options, sens:'next' }, function(event) { sliderEventHandler(event); } );
$('#' + panelName + 'Prev').bind('click', { panel:panel, options:options, sens:'prev' }, function(event) { sliderEventHandler(event); } );
}
f($(this).eq(i));
}
}
})( jQuery );
Démonstration
Note: Les liens "précédent" et "suivant" peuvent être remplacés par des images, des boutons... Ces liens peuvent être disposés n'importe où sur le document.
Utilisation
Création des zones de défilement
Les diapositives sont définies dans une liste non indexée.
Première diapositives
Seconde diapositives
Troisième diapositives
Les contrôles de défilement sont identifiés par leur ID, construit à partir de l'ID de la zone de défilement des diapositives. Si "IZ " est l'ID de la zone de défilement des diapositivesn alors :
ID du contrôle pour afficher les diapositives suivantes : "IZ Next"
ID du contrôle pour afficher les diapositives précédentes : "IZ Prev"
La direction de défilement est définie par la valeur de l'attribut "direction".
direction="horizontal": Défilement horizontal
direction="vertical": Défilement vertival
Première zone : défilement horizontal
Seconde zone : défilement vertical
Chargement du Javascript et démarrage
Note: Le sélecteur JQuery ("[slider]", en l'occurrence) est déterminé en fonction des noms des attributs associés aux zones de défilement des diapositives. Dans cet exemple, les deux zones de défilement se voient attribuer le même attribut ("slider", en l'occurrence). Ceci n'est pas une obligation. Il est possible d'attribuer des attributs différents. Dans ce cas, la méthode devra être appelée autant de fois qu'il y a d'attributs différents.
...
Le code ci-dessous illustre l’utilisation de Jquery pour le chargement de données et de code à exécuter.
Click me to load SCRIPT
Click me to load JSON
js_to_load_and_execute.js
function js_to_execute()
{
alert("Code has been loaded and executed!");
}
js_to_execute();
json_to_load_and_execute.js
{ "string" : "MyString", "js" : "function js_to_execute() { alert(\"Code from JSON has been loaded and executed!\"); } js_to_execute();" }