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


précédentsommairesuivant

III. Implémentation : 1ère é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
Sélectionnez
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
Sélectionnez
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
Sélectionnez
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
Sélectionnez
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ées 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 dans 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
Sélectionnez
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
Sélectionnez
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
Sélectionnez
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
Sélectionnez
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
Sélectionnez
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

A 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
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 coeur 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
Sélectionnez
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
Sélectionnez
.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
Sélectionnez
.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
Sélectionnez
.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
Sélectionnez
.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
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é.

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


précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

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 ni 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.