Conception de contrôles Web JavaScript avancés : Créer une table HTML éditable - HtmlEditTable v1.0

Nourdine FALOLA

Ce tutoriel est le premier d'une série consacrée au développement de contrôles Web avancés en JavaScript. Vous allez apprendre au cours de votre lecture à penser et concevoir des outils plus complexes au travers d'une étude de cas : la conception d'une table éditable. Vous pouvez participer à la discussion relative à cet article sur le site communautaire www.developpez.com. Commentez ♪

- Titre : Conception de contrôles Web JavaScript avancés : Créer une table HTML éditable - HtmlEditTable v1.0

- Auteur : Nourdine FALOLA

- Parution : 27 mai 2009

- 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 ® 2009 Nourdine FALOLA. 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. Définition des objectifs

- II. Prémisses

- II-A.

- II-B. A <table> !

- II-C. Édith la Môme

- II-D. Bulma et la Capsule Corp.

- II-E. Page de test

- II-F. Le masque de Zorro

- III. Implémentation : 1re étape - la table

- III-A.

- III-A-1. JS : HtmlEditTable(param)

- III-A-2. JS : HtmlEditTableHelper.CellInitialize(cell,value)

- III-A-3. JS : AppendTo(parent)

- III-A-4. JS : AllData()

- III-B. Asseyez-vous

- III-C. Avec style

- III-C-1. JS : HtmlEditTable(param)

- III-C-2. CSS : .HtmlEditTable

- IV. Implémentation : 2e étape - l'édition

- IV-A. Par où commencer ?

- IV-B. Comment vider le CELLier

- IV-B-1. JS : HtmlEditTableHelper.CellInitialize()

- IV-B-2. JS : HtmlEditTableHelper.DblClickHandler()

- IV-C. La maison d'édition : HtmlEdit

- IV-C-1. JS : HtmlEdit()

- IV-C-2. JS : AppendTo(parent)

- IV-C-3. CSS : .HtmlEdit

- IV-D. La fin est proche

- V. Implémentation : 3e étape - les données

- V-A. Qui es-tu ?

- V-B. Où es-tu ?

- V-C. Tous pour un, un pour tous

- V-D. Interface finale

- VI. Empaquetage du code JavaScript

- VII. Annexe : Masquer l'implémentation

- VII-A. HtmlEditTableHelper

- VII-B. Tools

- VIII. Remerciements

- IX. Toutes les publications

- IX-A. Espaces de noms (ou namespace) en JavaScript

- IX-B. Conception de contrôles Web avancés : Créer une table HTML éditable - HtmlEditTable v1.0

- IX-C. Conception de contrôles Web avancés : Créer une table HTML éditable - HtmlEditTable v2.0

- IX-D. Bibliothèque d'effets JavaScript : Smile FX

I - Définition des objectifs

La conception d'outils avancés dans un langage de programmation implique une connaissance également avancée du langage, aussi ce tutoriel s'adresse à un public averti. Le programmeur en herbe pourra cependant trouver ici un éventail de possibilités apporté par la programmation JavaScript pour la conception de pages web avec du contenu intéressant et non trivial. Les connaissances requises ici, outre la syntaxe et la programmation JavaScript en général, sont principalement :

- le DOM (Document Object Model) ;

- le DHTML ;

- la notation JSON ;

- la programmation objet ;

- les fonctions anonymes ;

- la programmation événementielle ;

- la compatibilité entre les principaux navigateurs.

J'insiste sur le fait que ce tutoriel est avant tout destiné à un public averti. Il est d'abord question de détailler le code d'une étude de cas et non pas d'élaborer un cahier des charges suivi de l'implémentation. Bien sûr, la conception de ce contrôle web sera progressive et expliquée. Cependant, le but n'est pas de détailler chaque ligne de code, mais d'abord le principe qui sous-tend l'implémentation du contrôle.

Tout programmeur web connaît la table HTML ou <table>. Il y a encore quelques années elle était utilisée à outrance pour la mise en page et est remplacée aujourd'hui à cette fin par les <div>, <ul> et autres, agrémentés de classes CSS. Elle n'en reste pas moins indispensable pour le rendu de données tabulaires.

Mais que pouvons-nous faire avec une simple table en dehors d'afficher des données ? Rien… Pourtant il serait, par exemple, très utile de pouvoir éditer les données, introduire des formules permettant de mettre à jour certaines données en fonction des autres, etc. Choses très utiles pour faire des statistiques, gérer des taches, des employés, des fournisseurs, des bulletins de notes ou je ne sais quoi dans un contexte personnel ou professionnel tout en ayant une interface conviviale et attrayante (exit les formulaires de papa, paix à leur âme). Bien évidemment, après avoir introduit des données dans cette table, il faut pouvoir les exploiter et par conséquent être capable de les récupérer facilement.

Je vous propose donc de concevoir une version plus évoluée de la table HTML. Elle sera réalisée en JavaScript pour la construction et le comportement, et en CSS pour le rendu. Nous allons la penser et la réaliser tout au long de ce tutoriel, et la doter des fonctionnalités de base que voici :

- génération automatique du contrôle ;

- édition de texte ;

- accès simple aux données.

Visuellement, voilà ce à quoi nous aimerions aboutir :

Objectif final

Pour illustrer notre objectif, voici une application possible de notre future table éditable : la gestion d'un bulletin de notes. Quand je parle de gestion ici, je parle uniquement de la partie cliente, c'est-à-dire affichage et modification dans la page des notes. La gestion côté serveur des données (base de données, xml, Ajax…) ne fait pas partie de notre étude, mais, si notre contrôle est bien conçu en ce qui concerne la restitution et l'insertion des données, il sera simple de l'utiliser de concert avec de l'Ajax ou autre. Sur l'illustration ci-dessus, vous pouvez voir la table éditable en haut, un bouton permettant la récupération des données de la table et l'affichage de toutes les valeurs du tableau ligne par ligne dans la zone de texte.

À terme, une série d'articles faisant suite à celui-ci devrait nous permettre d'implémenter un contrôle web proposant nombre de fonctionnalités utiles - tri de colonnes, navigation au clavier ou édition de données à l'aide d'autres contrôles qu'une zone de texte (checkbox, liste…) - et pourquoi pas d'aboutir à une grille web évoluée. Un couplage avec une technologie telle que l'Ajax serait une énorme valeur ajoutée et permettrait par le biais de notre grille d'impacter directement, et de façon interactive, une base de données.

II - Prémisses

II-A -

À la lecture de l'entrée en matière, vous l'aurez peut-être deviné - vous, programmeur ayant déjà une certaine expérience du JavaScript et de ses possibilités - que le contrôle que nous allons créer ici va être développé sous la forme d'une (pseudo) classe JavaScript.

Exprimons l'interface initiale dont nous aurons besoin. Pour cela, utilisons une définition de classe « à la C++ » comme base de départ de nos développements. Il s'agit là de l'interface publique de notre contrôle.

Interface publique de HtmlEditTable - v0.1

/*
public class HtmlEditTable {
public:
HtmlEditTable(object o);
void AppendTo(control parent);
Array AllData();
};
*/

Ce n'est pas du bonheur ça ? Interface initiale minimaliste, mais suffisante… Tellement simple qu'il n'est pas nécessaire a priori d'expliciter les fonctionnalités. Mais soyons fou, le but de ce tutoriel est tout de même de fournir des explications pas à pas et je ne dérogerais point à la règle, que nenni mon bon monsieur !

- HtmlEditTable : constructeur de la classe auquel on passe en paramètre un objet renseignant les propriétés de notre table éditable.

- AppendTo : méthode qui ajoute notre contrôle en tant que dernier nœud enfant d'un nœud par conséquent parent.

- AllData : méthode qui renvoie les données contenues dans la table sous forme d'un Array.

Cette interface nous permettra de réaliser deux des objectifs que nous nous sommes fixés, à savoir :

1. Génération automatique du contrôle ;

2. Accès simple aux données.

Eh oui, figurez-vous qu'en première approche cela suffit amplement. Il est judicieux de commencer petit, mais fonctionnel et bien pensé.

Remarquez au passage que nous venons de baptiser notre contrôle HtmlEditTable.

II-B - A <table> !

Jusqu'ici nous sommes tout de même restés dans l'abstrait. Effectivement nous allons créer une classe JavaScript, mais que va-t-elle nous générer ? Finalement qu'essayons-nous de faire, quel sera le rendu ?

Et bien je vous parlais en introduction de la toute basique table HTML. Cette table est le pilier de notre futur contrôle. En effet, une table éditable n'est autre qu'une table HTML dont les cellules permettront l'édition d'une façon que nous verrons bientôt. Donc, en première approximation, nous essayons au travers du JavaScript de générer une table HTML qui aura dans le DOM la tête suivante :

Squelette HTML

<table>
<caption>
</caption>
<thead>
<tr>
<td>
</td>
</tr>
</thead>
<tbody>
<tr>
<td>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>
</td>
</tr>
</tfoot>
</table>

Mais alors, comment rendre une table ordinaire éditable ?

II-C - Édith la Môme

Eh oui, l'homonyme de la Môme est la solution. Pour obtenir une cellule éditable nous allons utiliser un Edit, ou plus justement un <input type=« text »/>. L'idée étant de créer à la volée un Edit sur le double-clic d'une cellule, nous allons avoir besoin d'un Edit coopérant avec la table pour échanger des données. À dessein, nous implémenterons une classe JavaScript assurant la création d'un Edit répondant à nos besoins. Là encore, l'interface publique que l'on peut envisager est très simple.

Interface publique de HtmlEdit

/*
public class HtmlEdit {
public:
HtmlEdit();
void AppendTo(control parent);
};
*/

Explicitons-la :

- HtmlEdit : constructeur sans paramètre de la classe ;

- AppendTo : méthode qui ajoute le contrôle d'édition en tant que nœud enfant du contrôle parent. Dans notre cas, le parent sera une cellule de la table.

II-D - Bulma et la Capsule Corp.

Ce contrôle d'édition, que nous venons de baptiser HtmlEdit, est spécifique à notre table. Il serait donc pertinent d'en limiter l'accès à l'implémentation de cette dernière. Si nous reprenons la définition précédente de notre classe HtmlEditTable cela nous donne :

Interface publique de HtmlEditTable - v0.2

/*
public class HtmlEditTable {
public:
HtmlEditTable(object o);
void AppendTo(control parent);
Array AllData();

private:
class HtmlEdit {
public:
HtmlEdit();
void AppendTo(control parent);
};
};
*/

L'encapsulation est un des principes fondamentaux de la programmation orientée objet. Même si le JavaScript ne permet pas à proprement parler de faire de la programmation objet, il en permet une certaine émulation et nous permet d'encapsuler des fonctionnalités dans la limite où l'utilisateur ne se découvre pas une âme de super guerrier de l'espace et n'envoie pas une vague déferlante sur les barrières que vous lui imposez. Je vous conseille la lecture du tutoriel « Espaces de noms en JavaScript » si vous voulez en savoir plus sur l'encapsulation de données et de fonctionnalités en JavaScript.

II-E - Page de test

Eh oui, nous pouvons d'ores et déjà mettre en place une page de test puisque nous avons notre interface minimale. Cela nous permettra d'expliciter les informations que nous voulons passer à notre objet et que l'on devra traiter lors de l'implémentation de la classe HtmlEditTable.

Voici une page de test très simple dans laquelle on inclut les scripts et les styles nécessaires : HtmlEditTable.js et HtmlEditTable.css.

HtmlEditTable.js contiendra tous les scripts de l'implémentation de la HtmlEditTable.

HtmlEditTable.css contiendra tous les styles de la HtmlEditTable. Je n'explique pas ici l'import de CSS et ses avantages face au link classique. Je vous renvoie à votre moteur de recherche préféré pour obtenir de plus amples informations sur la question.

Dans le corps du document, un script nous permet d'instancier et d'ajouter au document une instance de la classe HtmlEditTable. On passe au constructeur toutes les informations utiles à la création de la table, à savoir :

- le nombre de colonnes (X) ou la liste des noms de colonnes (Xn) ;

- le nombre de lignes (Y) ou la liste des noms de lignes (Yn) ;

- les données (data) ;

- le titre de la table (caption) ;

- la liste des en-têtes de colonnes (head).

Ces données sont passées au constructeur sous la forme d'un objet (en notation JSON). L'ajout à la page de notre contrôle est assuré par l'appel de la fonction AppendTo(). Le contrôle sera alors ajouté en queue de la liste des enfants du corps du document (des objets, ajoutés par la suite au document, iront se positionner après notre contrôle).

Page de test - HtmlEditTable.html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<script type="text/javascript" src="HtmlEditTable.js">
</script>
<style type="text/css">
@import url(HtmlEditTable.css);
</style>
</head>
<body>
<script>
var table = new HtmlEditTable({
"X":8,
"Y":5,
"data": [
"15/09/2008", "12/20", "15/20", "9.5/20", "17/20", "2/20", "23/30", "95/100",
"02/10/2008", "15.5/20", "13.5/20", "7/20", "16/20", "2/20", "19/30", "96/100",
"25/10/2008", "11.5/20", "13/20", "10/20", "16/20", "2/20", "22/30", "97/100",
"09/11/2008", "18/20", "16/20", "10.5/20", "18.5/20", "2/20", "25/30", "98/100",
"29/11/2008", "17/20", "17/20", "9/20", "19/20", "2/20", "26/30", "99.5/100"
],
"caption":"Relevé de note de Mr. X - 1er trimestre 2009",
"head": ["Date","Français","Anglais","Maths", "Physique", "Histoire", "Géographie", "E.P.S."]
});
table.AppendTo(document.body);
</script>
<br/>
<input type="button" value="Export"
onclick="document.getElementById('zone').value = table.AllData().join(',')"/>
<br/>
<textarea id="zone" style="width:45em;height:10em;"></textarea>
</body>
</html>

Un bouton et une zone de texte finalisent notre page de texte. Le clic sur le bouton aura pour conséquence l'écriture dans la zone de texte de toutes les données contenues dans la table. La récupération des données sous forme d'un Array est assurée par la fonction AllData().

Vous voyez, avec cette page de test, que nous avons dès à présent utilisé la totalité de l'interface publique précédemment définie de notre classe.

II-F - Le masque de Zorro

Lorsque l'on conçoit une classe, une bibliothèque, etc., il est important de distinguer l'interface publique de l'implémentation. Vous devez bien différencier les fonctionnalités qui seront exhibées de celles qui devront rester internes à votre code. En JavaScript, la notion de privé et public n'existe pas, si ce n'est au travers d'astuces diverses et variées.

Dans le cadre de notre étude, afin de garder notre implémentation privée, nous allons introduire deux classes qui renfermeront les fonctions utilitaires nécessaires à nos développements. La classe Tools exhibera des fonctionnalités générales et compatibles avec tous les navigateurs modernes et la classe HtmlEditTableHelper exhibera des fonctionnalités spécifiques à l'implémentation de notre table éditable. À ce stade de notre implémentation, ces classes sont accessibles par les utilisateurs de notre code. Nous verrons plus tard comment masquer ces classes. Dans un premier temps, elles serviront à ne pas encombrer l'interface de notre contrôle avec des fonctionnalités propres à son implémentation.

var HtmlEditTableHelper = {
// fonctions spécifiques au développement d'une table éditable
};

var Tools = {
// fonctions générales et compatibles avec les navigateurs modernes
};

J'ai abusivement défini Tools et HtmlEditTableHelper comme étant des classes. Il s'agit en réalité d'objets définissant des propriétés. Ces propriétés sont des fonctions. Ce qui fait de ces objets l'équivalent de classes possédant des fonctions statiques.

III - Implémentation : 1re étape - la table

III-A -

Tous les scripts écrits à présent font partie du fichier HtmlEditTable.js.

La première chose à faire est d'écrire une classe JavaScript capable de générer une table HTML paramétrable suivant nos besoins avec un titre, des en-têtes de colonnes et des cellules.

Nous avons défini dans les prémisses une interface publique simple qui nous permettra de construire notre objet, de l'ajouter au document et de récupérer les données qu'il contient. Passons maintenant à l'implémentation de cette interface.

III-A-1 - JS : HtmlEditTable(param)

Le script suivant définit le constructeur de la classe HtmlEditTable. Ce constructeur attend un paramètre tel que nous l'avons explicité dans la page de test.

Constructeur HtmlEditTable - v0.1

var HtmlEditTable = function(){
this.control = document.createElement("table");
this.control.cellSpacing = 0;
this.control.cellPadding = 0;

if (arguments.length > 0){
this.Build(arguments[0]);
}
};

Dans ce constructeur, une table HTML est créée. Il s'agit du membre control de la classe HtmlEditTable. Certaines de ses propriétés sont assignées. La fonction Build() se charge d'achever la construction de la table.

Maintenant que nous avons défini le constructeur, nous pouvons ajouter des fonctions à notre classe via son prototype de la façon suivante :

Définition d'un prototype

HtmlEditTable.prototype = {
// Fonctions définies avec la notation JSON
Fonc1 : function([param1 [,param2 [,...]]]){
// code
},
Fonc2 : function([param1 [,param2 [,...]]]){
// code
},
...
} ;

La fonction Build s'occupe de construire notre tableau. Elle attend un paramètre définissant les attributs optionnels suivants :

- Xn : la liste des noms de colonnes ;

- Yn : la liste des noms de lignes ;

- X : le nombre de colonnes ;

- Y : le nombre de lignes ;

- head : la liste des en-têtes de colonnes (de longueur X ou Xn.length) ;

- caption : le titre de notre table ;

- data : la liste des données à insérer dans notre table.

HtmlEditTable.prototype.Build

Build: function(){
if (arguments.length > 0){
this.Clean();

var o = arguments[0];

this.Dimensions(o);

if (o.Xn){
this.columns = o.Xn;
}

if (o.Yn){
this.lines = o.Yn;
}

if (o.head){
this.headers = o.head;
}

if (o.caption){
this.caption = o.caption;
}

if (o.data){
this.Populate(o.data);
}
else {
this.Populate();
}
}
},

Tout d'abord Build() vérifie qu'il y a bien un paramètre en entrée et si c'est le cas commence par faire une remise à zéro par l'appel de la fonction Clean().

Ensuite le paramètre d'entrée est analysé et les données qu'il contient utilisées pour initialiser les membres de notre classe. La fonction Dimension() permet de fixer les dimensions de la table et les noms de lignes et de colonnes par défaut. Pour cela, le paramètre o doit définir au moins l'une des propriétés X et Y.

Pour finir, l'appel à la fonction Populate() va permettre de construire véritablement notre table avec, optionnellement, un nœud titre, un nœud thead et/ou des nœuds lignes et cellules.

Commençons par la fonction Clean :

HtmlEditTable.prototype.Clean

Clean: function(){
Tools.Purge(this.control);
this.columns = [];
this.lines = [];
this.headers = [];
this.caption = "";
},

La fonction Clean() (ré)initialise le contrôle. On purge la table de ses enfants (en-tête, corps, pied, lignes, cellules…) avec la fonction Tools.Purge() dont l'implémentation est donnée en annexe. Ensuite, on initialise les attributs columns, lines et headers de la classe HtmlEditTable. Que sont ces attributs ?

L'attribut headers est simplement la liste des en-têtes de notre tableau et l'attribut caption est le titre de la table.

Afin de pouvoir à l'avenir récupérer une donnée dans une cellule particulière, il faudra repérer cette cellule suivant sa ligne et sa colonne. On peut se dire que le repère est simplement la paire (n° colonne, n° ligne). Mais dans des contextes plus structurés, comme l'exemple de la gestion d'un bulletin de notes, on pourra vouloir repérer une donnée en fonction de la matière enseignée. C'est pourquoi nous introduirons la possibilité (et même l'obligation) de nommer lignes et colonnes. Dans ce but, columns est la liste des noms des colonnes et lines celle des lignes. Dans le cas où aucun nom de ligne ou de colonne n'est donné, le nom donné par défaut sera l'expression littérale de l'index de la ligne ou de la colonne.

Passons maintenant à la fonction Dimension() :

HtmlEditTable.prototype.Dimension

Dimensions: function(){
if (arguments.length > 0){
this.columns = [];
this.lines = [];

if (arguments[0].X){
for (var i=0, imax=arguments[0].X; i<imax; i++){
this.columns.push("" + i);
}
}
if (arguments[0].Y){
for (var i=0, imax=arguments[0].Y; i<imax; i++){
this.lines.push("" + i);
}
}
}
return { "x": this.columns.length, "y": this.lines.length };
},

La fonction Dimension() permet de dimensionner la table si l'objet passé à la fonction définit au moins l'une des propriétés X et Y, respectivement le nombre de colonnes et de lignes de la table. Quoi qu'il en soit, en sortie on récupère un objet nous donnant la taille de la table.

Comment s'opère ce dimensionnement ? Par l'intermédiaire de columns et lines que nous avons vus précédemment. Rappelez-vous, je vous ai dit il y a peu que les colonnes et lignes de la table seraient nommées et s'il n'y a pas de noms explicites il faudrait en déterminer par défaut. Et bien Dimension() réalise cela de la manière suivante :

- initialisation de columns et lines ;

- si le paramètre définit un entier X de colonnes, le nom de colonne par défaut est l'indice de la colonne de base 0 ;

- si le paramètre définit un entier Y de lignes, le nom de ligne par défaut est l'indice de la ligne de base 0.

Maintenant que nous avons toutes les données nécessaires au remplissage de notre table et bien allons-y gaiement. Populate() est là pour achever le travail :

HtmlEditTable.prototype.Populate

Populate: function(){
if (this.caption){
this.control.createCaption().appendChild(document.createTextNode(this.caption));
}

if (this.lines.length > 0){
for (var i=0, Y=this.lines.length; i<Y; i++){
var row = this.control.insertRow(i);

if (this.columns.length > 0){
for (var j=0, X=this.columns.length; j<X; j++){
var cell = row.insertCell(j);
if (arguments.length > 0){
HtmlEditTableHelper.CellInitialize(cell, arguments[0][j+i*X]);
}
}
}
}
}

if (this.headers.length == this.columns.length){
var tHead = this.control.createTHead();
var row = tHead.insertRow(0);
for (var i=0, imax=this.headers.length; i<imax; i++){
var cell = row.insertCell(i);
cell.appendChild(document.createTextNode(this.headers[i]));
}
}
}

L'objet table existe, il s'agit de control. Populate() se charge de remplir la table et de lui ajouter un titre si celui-ci est défini (caption), ainsi que des lignes et cellules (respectivement en nombre Y et X) et de remplir chaque cellule avec la donnée adéquate si elle existe et, de la même manière, de construire les en-têtes de colonne, s'ils sont définis.

La fonction Populate() attend comme paramètre un Array contenant les données à insérer dans la table. Si aucun paramètre n'est passé à la fonction, alors la table est construite avec des cellules vides. Comme nous le verrons bientôt, la fonction permet d'initialiser une cellule du corps de notre table. Cette fonctionnalité est spécifique à notre implémentation et n'a pas à être exhibée à l'utilisateur, c'est pourquoi nous l'intégrons à la classe HtmlEditTableHelper.

Il est plus efficace - comprenez « plus performant » - de créer et ajouter un nœud texte à une cellule, que d'utiliser la propriété innerHTML et de lui affecter un texte. Sur de très petites tables, la différence ne se fait pas sentir. Mais sur des tables de plusieurs centaines de cellules et/ou lorsque vous avez plusieurs tables sur votre page, l'emploi de innerHTML dégrade fortement les performances de votre code. De même que l'on a recourt au DOM pour créer et affecter le nœud texte, on y recourt également pour la lecture au travers de la propriété data du nœud texte.

III-A-2 - JS : HtmlEditTableHelper.CellInitialize(cell,value)

La fonction HtmlEditTableHelper.CellInitialize() utilisée dans la fonction HtmlEditTable.prototype.Populate() se charge de créer un nœud texte dans une cellule et de lui affecter une valeur que, par abus de langage, nous désignons comme la valeur contenue dans la cellule.

HtmlEditTableHelper.CellInitialize - v0.1

CellInitialize: function(cell, value){
if (typeof value != "undefined"){
cell.appendChild(document.createTextNode(value));
}
}

III-A-3 - JS : AppendTo(parent)

Rien de plus simple que d'ajouter notre contrôle au document. Il suffit de faire un appendChild() sur l'élément auquel on veut ajouter notre table comme enfant.

HtmlEditTable.prototype.AppendTo

AppendTo: function(parent){
parent.appendChild(this.control);
},

III-A-4 - JS : AllData()

La fonction AllData() nous permet de récupérer les données contenues dans la table. Pour se faire, il suffit de parcourir les cellules du corps de la table, récupérer la valeur qu'elles contiennent et la sauvegarder dans un Array retourné par la fonction.

HtmlEditTable.prototype.AllData

AllData: function(){
var data = [];
var rows = this.control.getElementsByTagName("tbody")[0].rows;
for (var y=0, ymax=rows.length; y<ymax; y++){
var cells = rows[y].cells;
for (var x=0, xmax=cells.length; x<xmax; x++){
data.push(cells[x].firstChild.data);
}
}
return data;
}

III-B - Asseyez-vous

À ce stade de notre développement, vous pouvez lancer la page de test avec votre navigateur préféré (et même mieux, avec plusieurs navigateurs différents pour tester la compatibilité) et constater le résultat : voir la démo - télécharger les sources

Une table simple

Oui, vous pouvez vous lâcher et crier haut et fort « Dieu que c'est laid ! ». En l'état actuel des choses, nous avons un affichage brut de notre table et nos yeux sont à l'agonie (comment ça j'exagère ?). Mais si nous nous concentrons d'abord sur le cœur de notre objectif, à savoir générer une table éditable, force est de constater que nous sommes déjà à mi-chemin : savoir générer une table ! Et nous savons également récupérer les données. Si vous cliquez sur le bouton d'export, les données s'affichent dans la zone de texte.

Maintenant que nous avons franchi cette étape et ce premier affichage, nous allons nous occuper du style avant de passer à l'édition.

III-C - Avec style

III-C-1 - JS : HtmlEditTable(param)

Côté JavaScript, affectons simplement une classe CSS à la table dans le constructeur :

Constructeur HtmlEditTable - v1.0

var HtmlEditTable = function(){
this.control = document.createElement("table");
this.control.cellSpacing = 0;
this.control.cellPadding = 0;
this.control.className = "HtmlEditTable";

if (arguments.length > 0){
this.Build(arguments[0]);
}
};

Il ne nous reste alors qu'à définir la classe CSS .HtmlEditTable.

III-C-2 - CSS : .HtmlEditTable

Dans la page de test HtmlEditTable.html, nous avons écrit une directive d'import d'un fichier HtmlEditTable.css. Créons maintenant ce fichier et définissons la classe .HtmlEditTable, classe qui rappelons-le est appliquée à une table :

.HtmlEditTable

.HtmlEditTable
{
table-layout:fixed; /* colonnes de tailles égales */
background-color:#FFF; /* couleur de fond = blanc */
border-right:1px solid #999; /* bordure droite de la table */
border-bottom:1px solid #999; /* bordure droite de la table */
width:40em; /* largeur en unité relative */
}

Nous pouvons également ajouter un style sur les cellules de données (cellules du tbody) :

.HtmlEditTable tbody td

.HtmlEditTable tbody td
{
font-family: Courier; /* police */
border-left:1px solid #999; /* bordure gauche */
border-top:1px solid #999; /* bordure supérieure */
padding-right:2px;
overflow:hidden; /* pour IE */
text-overflow:ellipsis; /* pour IE */
vertical-align: middle; /* alignement vertical */
white-space:nowrap; /* interdiction d'aller à la ligne */
text-align:right; /* alignement horizontal */
font-size: 0.7em; /* taille de police relative */
background-color: RGB(251,250,231); /* couleur de fond */
}

Les propriétés overflow et text-overflow positionnées à hidden et ellipsis permettent à IE de tronquer un texte trop long dans une cellule en ajoutant des points de suspension (…) à la fin de la partie visible du texte.

De la même façon que pour les cellules du tbody, ajoutons un style pour les cellules du thead :

.HtmlEditTable thead td

.HtmlEditTable thead td
{
font-family: Verdana; /* police */
background-image : url(tab-header.gif);
height:23px;
color:#FFFFFF;
font-weight: bold; /* fonte grasse du texte */
text-align:center; /* alignement horizontal */
font-size: 0.7em; /* taille de police relative */
}

Et pour terminer, le titre de la table :

.HtmlEditTable caption

.HtmlEditTable caption
{
text-align: left; /* alignement horizontal */
text-decoration: underline; /* soulignement */
font-weight: bold; /* fonte grasse */
font-size: 0.8em; /* taille de police relative */
}

Nous obtenons maintenant une table qui ressemble en tout point à notre objectif de départ, hormis le fait qu'elle n'est pas encore éditable : voir la démo - télécharger les sources

Une table simple et stylée

Discutons un peu sur quelques points de notre CSS.

D'abord pourquoi avoir défini les bordures droite et inférieure sur la table, et les bordures supérieures et gauches sur les cellules du tbody et du thead ?

- Si vous définissez les 4 bordures sur la table, vous n'avez pas un découpage par cellule, or c'est ce que je souhaitais obtenir. Donc les bords doivent au moins être en partie définis sur les cellules.

- On peut donner la valeur collapse à la propriété border-collapse et positionner les 4 bordures sur les cellules du tbody et du thead. Mais dans ce cas vous observerez que le rendu est bon sur le tbody (fusion des bordures intercellulaires) mais pas pour le thead (pas de fusion…). Embêtant…

J'ai opté sur le positionnement des bordures droites et inférieures sur la table, et simuler la fusion des bordures en ne positionnant que les bordures gauches et supérieures sur les cellules (tbody et thead). Ainsi le rendu est celui espéré.

Concernant les dimensions (largeur, taille de police…), j'ai utilisé l'unité relative em. Brièvement, sachez que cette unité est relative à la taille de police de l'élément sauf si la propriété font-size est définie sur l'élément parent, auquel cas l'unité est relative à la taille de police de l'élément parent. Cette unité n'est pas forcément aisée à manipuler au départ, mais elle permet de faire en sorte que les éléments se redimensionnent correctement si la taille du texte de votre navigateur change. Je vous conseille d'effectuer une petite recherche sur Internet pour vous familiariser avec cette unité.

Évidemment les styles peuvent être modifiés à volonté suivant votre bon vouloir.

IV - Implémentation : 2e étape - l'édition

IV-A - Par où commencer ?

Au stade où nous en sommes, ajouter l'édition à notre table va être très simple. Rappelez-vous de la section 2.4 dans laquelle nous posons l'interface minimale d'un contrôle HtmlEdit qui sera la brique élémentaire de notre fonctionnalité.

Comment procéder ? L'idée est de pouvoir remplacer à la volée une cellule par un input d'édition. Mais, à bien y réfléchir, cela n'est pas possible, car une cellule d'une table ne peut être remplacée. Par contre le contenu de la cellule, lui, peut-être remplacé ! Donc notre but à présent est de parvenir à substituer un input d'édition au contenu d'une cellule lorsque l'on veut éditer cette dernière, puis, à la fin de l'édition, de substituer la nouvelle valeur à l'input d'édition (abracadabra, en quelque sorte).

IV-B - Comment vider le CELLier

IV-B-1 - JS : HtmlEditTableHelper.CellInitialize()

Posons que notre contrôle HtmlEdit existe déjà et intégrons-le au HtmlEditTable. Tout d'abord il faut que l'événement double-clic sur une cellule provoque l'édition de cette dernière. Pour cela, modifions la fonction HtmlEditTableHelper.CellInitialize() pour appeler un callback HtmlEditTableHelper.DblClickHandler() qui réalisera notre souhait. À la création d'une cellule :

HtmlEditTableHelper.CellInitialize - v1.0

CellInitialize: function(cell, value){
cell.ondblclick = HtmlEditTableHelper.DblClickHandler;
if (typeof value != "undefined"){
cell.appendChild(document.createTextNode(value));
}
}

Le callback HtmlEditTableHelper.DblClickHandler() est spécifique à notre implémentation, c'est pourquoi nous l'intégrons à la classe HtmlEditTableHelper.

IV-B-2 - JS : HtmlEditTableHelper.DblClickHandler()

Le callback HtmlEditTableHelper.DblClickHandler() se charge de placer un contrôle HtmlEdit dans la cellule :

HtmlEditTableHelper.DblClickHandler

DblClickHandler: function(e){
var src = Tools.Node(Tools.Target(e), "TD");
if (!src){
Tools.Event(e).returnValue = false;
return false;
}
var htmlEdit = new HtmlEdit(src.firstChild.data);
htmlEdit.AppendTo(src);
src.ondblclick = null;
}

D'abord on isole la cellule. Quand vous double-cliquez sur la cellule, c'est potentiellement son contenu qui déclenchera l'événement, donc la source de l'événement n'est pas forcément la cellule. La fonction utilitaire Tools.Target() nous renvoie la source de l'événement. La fonction parcourt les nœuds parents de l'élément passé en argument jusqu'à tomber sur le premier dont le nodeName est celui passé en argument. Par précaution, prévoyons une porte de sortie au cas où la fonction est mal utilisée et que nous nous retrouvons sans cellule dans la généalogie de notre source (dans ce cas on retourne false). En cas de succès de la recherche, on crée un HtmlEdit et on lui passe le contenu textuel de la cellule. On supprime le callback sur le double-clic et on ajoute le HtmlEdit à la cellule.

Pour l'instant la classe HtmlEdit n'est pas implémentée. Aussi, si vous testez le programme en l'état, vous obtiendrez une erreur. Par contre, si vous commentez les lignes concernant le HtmlEdit dans la fonction HtmlEditTableHelper.DblClickHandler(), vous constaterez que les cellules se vident lorsque vous double-cliquez dessus. Au passage, sous IE vous aurez un bug d'affichage sur les bordures des cellules vidées, mais nous verrons cela plus tard.

IV-C - La maison d'édition : HtmlEdit

Et bien nous y voilà. Nous allons maintenant mettre en œuvre le contrôle HtmlEdit grâce auquel notre table deviendra éditable.

IV-C-1 - JS : HtmlEdit()

Décodons la cinématique d'édition d'une cellule. Nous avons vu plus tôt que, lorsque l'on double-clique sur une cellule, cette dernière est vidée de son contenu textuel et à la place se crée un edit via le contrôle HtmlEdit. Le comportement classique d'une cellule éditable est que l'édition s'arrête lorsque la cellule (i.e. l'input dans la cellule) perd le focus ou lorsque la touche Entrée est pressée.

Constructeur HtmlEdit

var HtmlEdit = function(value){
var loseFocus = function(e){
var src = Tools.Target(e);
var cell = Tools.Node(src, "TD");

Tools.Purge(cell);
HtmlEditTableHelper.CellInitialize(cell, src.value);
};

this.control = document.createElement("input");
this.control.type = "text";
this.control.className = "HtmlEdit";
this.control.onblur = loseFocus;
this.control.onkeydown = function(e){
if (Tools.KeyCode(e) == 13){
loseFocus(e);
}
};

this.control.value = value;
};

Intéressons-nous d'abord à la fin de cette fonction. La classe HtmlEdit possède un attribut control qui n'est autre qu'un input de type text (un edit en somme). Nous lui assignons une classe CSS ainsi que des callback sur les événements blur (perte du focus) et keydown (touche enfoncée lorsque le focus est sur l'input). L'événement blur déclenche le callback loseFocus(). L'événement keydown déclenche une fonction anonyme dans laquelle loseFocus() est appelé si la touche pressée a le code 13 (touche Entrée). La fonction utilitaire assure la compatibilité sur tous les navigateurs pour la détermination du code de la touche pressée à partir de l'objet Event.

Le cœur du comportement du HtmlEdit réside dans le callback loseFocus() défini au début du constructeur de la classe. Au passage, il s'agit là d'une fonction définie localement au constructeur (voir le tutoriel « Espaces de noms en JavaScript » pour de plus amples informations).

loseFocus() est appelé lorsque la touche Entrée est pressée, lorsque l'input a le focus et aussi lorsque cet input perd le focus. Donc l'élément cible src de ce callback est l'input et le nœud parent de l'input est la cellule. On purge la cellule et on l'initialise via la fonction HtmlEditTableHelper.CellInitialize() que l'on connaît. Voilà comment la cellule passe du mode édition au mode lecture seule.

IV-C-2 - JS : AppendTo(parent)

Il nous reste à implémenter la fonction AppendTo() qui nous permet d'ajouter l'input à la cellule que nous voulons éditer. Cette fonction prend en paramètre le parent de l'input - la cellule.

HtmlEdit.prototype.AppendTo

HtmlEdit.prototype = {
AppendTo: function(parent){
if (document.all){
this.control.style.height = parent.clientHeight - 2*parent.clientTop + "px";
this.control.style.width = parent.clientWidth - 2*parent.clientLeft + "px";
}
else{
this.control.style.height = parent.offsetHeight - 2*parent.clientTop + "px";
this.control.style.width = parent.offsetWidth - 2*parent.clientLeft + "px";
}

Tools.Purge(parent);
parent.appendChild(this.control);

this.control.select();
this.control.focus();
}
};

On dimensionne ensuite l'input pour qu'il prenne tout l'espace disponible dans la cellule. Ensuite on purge la cellule et on y ajoute l'input. Puis pour terminer, on sélectionne le texte de l'input et on lui donne le focus.

En fait, pour la taille de l'input, on ne se contente pas d'un simple 100 % en largeur et en hauteur. Nous devons avoir recours à un subterfuge du fait qu'il est possible que la cellule ait une bordure, un padding… Et pour arranger les choses, la gestion des dimensions est différente suivant que vous utilisiez Internet Explorer ou un autre navigateur. La hauteur est calculée comme étant le clientHeight (offsetHeight pour Internet Explorer) du parent diminué, le cas échéant, de la taille des bordures inférieure et supérieure. La largeur est calculée de la même façon en fonction de clientWidth ou offsetWidth et des bordures gauche et droite.

IV-C-3 - CSS : .HtmlEdit

Pour que le dimensionnement précédent fonctionne, il nous faut supprimer les bordures, le padding et le margin de notre input.

Il est préférable que la taille de la police soit la même que celle de la cellule, soit 1em, ce qui correspond à 1 fois la taille de la police de la cellule. La taille de l'espace d'affichage du texte de l'input est également fixée à 1em pour être sûr que le texte s'affiche en entier.

Finalement, définissons le style suivant pour notre input :

.HtmlEdit

.HtmlEdit
{
border: 0px;
font-size: 1em;
font-family: Courier;
padding: 0px;
text-align:right;
margin:0px;
line-height:1em;
background-color:#F0F0F0;
}

IV-D - La fin est proche

Notre page de test vous permet de voir le résultat. Vous pouvez constater que nous avons atteint notre objectif de départ et que nous pouvons récupérer facilement les données entrées dans la table : voir la démo - télécharger les sources.

Que peut-on faire de plus ? Il serait bien utile d'avoir quelques fonctionnalités supplémentaires telles que : obtenir la liste des colonnes, des lignes, la donnée d'une cellule particulière, etc.

V - Implémentation : 3e étape - les données

Pour terminer notre implémentation, voici quelques fonctions utiles pour étoffer notre HtmlEditTable.

V-A - Qui es-tu ?

Il est intéressant pour l'utilisateur de notre contrôle de pouvoir accéder aux noms des lignes et colonnes de la table.

HtmlEditTable.prototype.LineNames

LineNames: function(){
if (arguments.length > 0){
this.lines = lines;
}
return this.lines;
},

HtmlEditTable.prototype.ColumnNames

ColumnNames: function(){
if (arguments.length > 0){
this.columns = columns;
}
return this.columns;
},

Suivant que vous passiez ou non en argument un Array contenant les noms des lignes ou colonnes, vous affectez ou accédez à l'Array correspondant du HtmlEditTable. Ces fonctions ont le rôle d'accesseur et de mutateur sur les attributs lines et columns de notre contrôle.

V-B - Où es-tu ?

Tout comme les noms des lignes et colonnes, il est très utile de pouvoir accéder aux données d'une ligne ou d'une colonne entière. C'est le rôle des fonctions Line() et Column(). La fonction Line() attend en paramètre un objet définissant les propriétés name, nom de la ligne, et data, Array contenant les données à affecter à la ligne. La fonction Column() attend en paramètre un objet définissant les propriétés name et data, ainsi que la propriété head, titre de la colonne.

HtmlEditTable.prototype.Lines

Line: function(){
if (arguments.length == 0
|| typeof arguments[0].name == "undefined"){
return undefined;
}

var data = [];
var y = Tools.IndexOf(this.lines, arguments[0].name);

if (y > -1){
var cells = this.control.getElementsByTagName("tbody")[0].rows[y].cells;

if (typeof arguments[0].data != "undefined"){
data = arguments[0].data;
for (var i=0, imax=data.length; i<imax; i++){
cells[i].firstChild.data = data[i];
}
}
else{
for (var x=0, xmax=cells.length; x<xmax; x++){
data.push(cells[x].firstChild.data);
}
}
}
else if (typeof arguments[0].data != "undefined"){
this.lines.push(arguments[0].name);
data = arguments[0].data;

var row = this.control.insertRow(-1);
for (var j=0, jmax=this.columns.length; j<jmax; j++){
var cell = row.insertCell(-1);
HtmlEditTableHelper.CellInitialize(cell, data[j]);
}
}

return data;
},

HtmlEditTable.prototype.Columns

Column: function(){
if (arguments.length == 0
|| typeof arguments[0].name == "undefined"){
return undefined;
}

var data = [];
var rows = this.control.getElementsByTagName("tbody")[0].rows;
var x = Tools.IndexOf(this.columns, arguments[0].name);

if (x > -1){
if (typeof arguments[0].data != "undefined"){
data = arguments[0].data;
for (var i, imax=data.length; i<imax; i++){
rows[i].cells[x].firstChild.data = data[i];
}
}
else{
this.lines.push(arguments[0].name);
for (var y=0, ymax=rows.length; y<ymax; y++){
data.push(rows[y].cells[x].firstChild.data);
}
}
}
else if (typeof arguments[0].data != "undefined"){
this.columns.push(arguments[0].name);
data = arguments[0].data;

for (var y=0, ymax=rows.length; y<ymax; y++){
var cell = rows[y].insertCell(-1);
HtmlEditTableHelper.CellInitialize(cell, data[y]);
}

var thead = this.control.getElementsByTagName("thead");
if (thead.length > 0){
thead = thead[0];
var title = typeof arguments[0].head != "undefined" ? arguments[0].head : "";
var cell = thead.rows[0].insertCell(-1);
cell.appendChild(document.createTextNode(title));
}
}

return data;
},

Ces fonctions ont également le rôle d'accesseur et de mutateur. Accesseur si l'unique paramètre passé aux fonctions est le nom de la ligne ou de la colonne à laquelle on désire accéder. Mutateur si, en plus du nom de la ligne ou colonne, on passe un Array contenant les données que l'on veut affecter à ladite ligne ou colonne.

Dans le cas où la ligne ou la colonne existe et que la propriété data du paramètre est définie, les données que la ligne ou la colonne contient sont remplacées par celles passées en paramètre. Si la ligne ou la colonne n'existe pas, elle est créée et initialisée. Pour la colonne, si le paramètre définit également la propriété head, alors l'en-tête de la colonne prendra cette valeur.

V-C - Tous pour un, un pour tous

Nous pouvons maintenant accéder à la totalité des données via la fonction AllData(), à une ligne ou une colonne via les fonctions Line() et Column(). Il ne nous manque plus que pouvoir accéder à la donnée d'une cellule particulière. C'est ce que nous permet la fonction Data().

HtmlEditTable.prototype.Data

Data: function(column, line, data){
if (arguments.length < 2){
return undefined;
}
var x, y;
x = Tools.IndexOf(this.columns, arguments[0]);
if (x > -1){
y = Tools.IndexOf(this.lines, arguments[1]);
if (y > -1){
if (arguments.length > 2){
this.control.rows[y].cells[x].appendChild(document.createTextNode(arguments[2]));
}
return this.control.rows[y].cells[x].firstChild.data;
}
}
return undefined;
}

Encore une fois, cette fonction est à la fois accesseur et mutateur. Accesseur si on lui passe en paramètre les noms de la colonne et de la ligne qui permettront d'identifier la cellule cherchée. Mutateur si, en plus des paramètres précédents, on passe la valeur à affecter à la cellule.

V-D - Interface finale

À l'issue de l'ajout des fonctionnalités précédentes, voici l'interface finale de notre contrôle mise à disposition de l'utilisateur :

Interface publique de HtmlEditTable - v1.0

/*

public class HtmlEditTable {

public:

HtmlEditTable(object o);
void AppendTo(control parent);
Array LineNames();
Array LineNames(Array t);
Array ColumnNames();
Array ColumnNames(Array t);
object Dimensions();
object Dimensions(object d);
Array Line();
Array Line(object o);
Array Column();
Array Column(object o);
void Clean();
void Populate(Array data);
void Build(object o);
T Data(string col, string row);
T Data(string col, string row, T data);
Array AllData();
};

*/

voir la démo - télécharger les sources

VI - Empaquetage du code JavaScript

Avant de conclure ce tutoriel, il serait intéressant d'appliquer quelques principes dispensés dans le tutoriel « Espaces de noms en JavaScript » (disponible sur le site https://falola.developpez.com). Afin de limiter les conflits de noms et de bien montrer à l'utilisateur que l'utilisation de certaines fonctionnalités (fonctions et variables) doit rester locale à l'implémentation de notre contrôle, nous pouvons (et même devons) empaqueter ladite implémentation. Pour ce faire, rien de plus simple : toute l'implémentation est déplacée dans une fonction anonyme directement exécutée. Tout, sauf la déclaration de la variable HtmlEditTable. Cela donne :

Empaquetage de l'implémentation

var HtmlEditTable = undefined;

(function(){
HtmlEditTable = function(){
...
};

...

})();

Grâce à cette astuce, nous venons de masquer les classes (ou objets) Tools et HtmlEditTableTable. Elles ne sont plus accessibles par l'utilisateur!

Je vous renvoie au tutoriel cité précédemment pour de plus amples informations.

voir la démo - télécharger les sources

VII - Annexe : Masquer l'implémentation

VII-A - HtmlEditTableHelper

Au fil de ce tutoriel, nous avons utilisé un certain nombre de fonctions utilitaires, spécifiques à notre implémentation, qu'on ne veut pas exhiber dans l'interface de notre contrôle. Nous les avons regroupés dans l'objet HtmlEditTableHelper. Nous les avons déjà explicitées, aussi voici uniquement pour rappel la version finale de cet objet.

HtmlEditTableHelper

var HtmlEditTableHelper = {
CellInitialize: function (cell, value){
if (cell){
cell.ondblclick = HtmlEditTableHelper.DblClickHandler;
if (typeof value != "undefined"){
cell.appendChild(document.createTextNode(value));
}
}
},

DblClickHandler: function(e){
var src = Tools.Node(Tools.Target(e), "TD");
if (!src){
Tools.Event(e).returnValue = false;
return false;
}
var htmlEdit = new HtmlEdit(src.firstChild.data);
htmlEdit.AppendTo(src);
src.ondblclick = null;
}
};

VII-B - Tools

Au fil de ce tutoriel nous avons utilisé un certain nombre de fonctions utilitaires génériques et compatibles avec tous les navigateurs récents. Elles ont été regroupées dans l'objet Tools que voici :

HtmlEditTableHelper

var Tools = {
Purge: function(node){
while (node && node.hasChildNodes()){
var child = node.firstChild;
Tools.Purge(child);
var attr = child.attributes;
if (attr) {
var n;
var l = attr.length;
for (var i=0; i<l; i++){
n = attr[i].name;
if (typeof child[n] === 'function') {
child[n] = null;
}
}
}
child = null;
node.removeChild(node.firstChild);
}
},

Node: function(o, nodeName){
while(o && o.nodeName != nodeName.toUpperCase()){
o = o.parentNode;
}
if (o){
return o;
}
return undefined;
},

Event: function(e){
return window.event || e;
},

Target: function(e){
e = Tools.Event(e);
return e.srcElement || e.target;
},

KeyCode: function(e){
e = Tools.Event(e);
return e.keyCode || e.which;
},

IndexOf: function(array, value){
for (var i=0, imax=array.length; i<imax; i++){
if (i in array && array[i] === value){
return i;
}
}
return -1;
}
};

- La fonction Tools.Purge() supprime tous les nœuds enfants du nœud passé en paramètre. Cette suppression est précédée de la mise à NULL de toutes les propriétés de type fonction de ces nœuds pour éviter des fuites de mémoire (principalement sous Internet Explorer).

- La fonction Tools.Node() permet de parcourir l'arborescence du nœud passé en paramètre jusqu'au premier élément parent de type nodeName.

- La fonction Tools.Event() assure la compatibilité entre les principaux navigateurs quant à la récupération de l'événement déclenché.

- La fonction Tools.Target() assure la compatibilité entre les navigateurs quant à la récupération de la cible de l'événement déclenché.

- La fonction Tools.KeyCode() assure la compatibilité entre les navigateurs quant à la récupération du code de la touche pressée lors d'un événement.

- La fonction Tools.IndexOf() renvoie l'index de la valeur value dans le tableau array, ou -1 si value n'est pas contenue par array.

VIII - Remerciements

Je remercie le_chomeur pour ses conseils et ses optimisations, Double_U pour ses remarques et suggestions, Patrick Ottavi (aka farscape) et Didier Mouronval (aka Bovino) pour leur relecture attentive.

IX - Toutes les publications

IX-A - Espaces de noms (ou namespace) en JavaScript

Cet article s'adresse à une population de développeurs déjà familière avec la problématique du développement Web et le langage JavaScript. Dans cet article, vous apprendrez comment packager vos bibliothèques en simulant en JavaScript ce que l'on appelle un espace de noms (« namespace » en anglais). Vous pouvez participer à la discussion relative à cet article sur le site communautaire www.developpez.com. Commentez

lire l'article

IX-B - Conception de contrôles Web avancés : Créer une table HTML éditable - HtmlEditTable v1.0

Ce tutoriel est le premier d'une série consacrée au développement de contrôles Web avancés en JavaScript. Vous allez apprendre au cours de votre lecture à penser et concevoir des outils plus complexes au travers d'une étude de cas : la conception d'une table éditable. Vous pouvez participer à la discussion relative à cet article sur le site communautaire www.developpez.com. Commentez

lire l'article

IX-C - Conception de contrôles Web avancés : Créer une table HTML éditable - HtmlEditTable v2.0

Dans un précédent tutoriel, nous avons conçu une table HTML éditable en JavaScript : HtmlEditTable v1.0. Pour la v2.0, nous allons réviser un peu le code et ajouter les fonctionnalités suivantes :

- création d'un HtmlEditTable à partir d'une table HTML existante

- navigation au clavier

Vous pouvez participer à la discussion relative à cet article sur le site communautaire www.developpez.com. Commentez

lire l'article

IX-D - Bibliothèque d'effets JavaScript : Smile FX

En cours…

voir la démo