Dans le respect du Web sémantique, les tables HTML sont supposées contenir des données tabulaires. Or souvent, il peut être intéressant de vouloir organiser ces données en les triant.
J'ai écrit une petite fonction JavaScript permettant de trier une table HTML dynamiquement.
Cette fonction ne s'occupe pas de la structure réelle de la table, c'est à dire que cela fonctionne que vous ayez une table complète (tbody, thead, tfoot) ou non.
Elle récupère les balises tr et en fonction de leur contenu (td ou th) rendent ces champs soit cliquables soit triables.
L'utilisation est relativement simple : une fois la table disponible (c'est à dire ajoutée au DOM), vous pouvez appeler :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
new Triable(element, options);
Les deux paramètres sont optionnels (mais si vous précisez les options, vous devez préciser aussi l'élément).
element (par défaut, la première table rencontrée) correspond à la table.
option est un objet avec pour l'instant 6 options possibles :
  • exclure : nom de classe (défaut aucune) > Empêcher de trier selon une colonne (toutes les données ne sont pas triables ). Sa valeur est un nom de classe à donner aux th non triables.
  • numstrict : booléen (vrai par défaut) > Les numériques peuvent ou non contenir d'autres caractères (devise par exemple) il faut savoir que le tri est typé pour permettre un tri cohérent avec les numériques.
  • classeTH : nom de classe (défaut aucune) > Correspond à la classe à donner au tr contenant les th cliquables. Vous pouvez avoir différentes lignes de th dans la table et souhaiter limiter les lignes cliquables.
  • classeNum : nom de classe (défaut numTri) > nom de classe à donner à un th pour forcer le tri numérique
  • classeCh : nom de classe (défaut txtTri) > nom de classe à donner à un th pour forcer le tri texte
  • classeDate : nom de classe (défaut dateTri) > nom de classe à donner à un th pour forcer le tri par date


Par défaut, le format de date est automatiquement reconnu sous la forme [J]J/[M]M/[AA]AA. Le jour et le mois peuvent être sur 1 ou deux chiffres l'année sur 2 ou 4 chiffres (attention, la longueur de l'année doit être identique sur tous les champs pour ne pas perturber le tri : 09 n'est pas la même année que 2009 ). Donc si une table contient des données reconnues au format date et que le th correspondant ne force pas un type de tri, alors le tri sur l'ensemble de la colonne se fera par date.

Voilà, c'est à peu près tout.
Un exemple est en ligne.

Voici le code :
Code js : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
var Triable = function(elt, options){
	// 	Options :
	//		exclure : (chaine) nom de classe pour exclure des colonnes du tri ou pour empêcher une ligne th d'être cliquable (dans ce cas, c'est le comportement inverse de classeTH. Défaut : rien
	//		numstrict : (booléen) les numériques peuvent contenir des caractères alpha (par exemple des unités) après les chiffres. Défaut : true
	//		classeTH : (chaine) la classe des lignes th cliquables. Par défaut, ils le sont tous (sauf avec la classe eclure. !!! La classe s'applique au tr !!! Défaut : rien
	//		classeNum : (chaine) nom de classe pour forcer un tri numérique. Défaut : numTri
	//		classeCh : (chaine) nom de classe pour forcer un tri textuel. Défaut : txtTri
	//		classeDate : (chaine) nom de classe pour forcer un tri par date (format : 'JJ/MM/AAAA'). Défaut : dateTri
	this.elt = elt || document.getElementsByTagName('table')[0];
	options = options || {};
	this.exclure = options.exclure || false;
	this.numstrict = options.numstrict || true;
	this.classeTH = options.classeTH || '';
	this.classeNum = options.classeNum || 'numTri';
	this.classeCh = options.classeCh || 'txtTri';
	this.classeDate = options.classeDate || 'dateTri';
	this.base = this.elt.getElementsByTagName('tr');
	this.separateur = '_';
	this.getSeparateur();
	var i = this.base.length, j;
	this.en_cours = 0;
	this.valeurs = [];
	this.affichage = [];
	this.typeTri = [];
	var that, lavaleur, baseLigne;
	while(i){
		i--;
		if(this.base[i].getElementsByTagName('th').length>0 && this.base[i].className.match(this.classeTH)){
			baseLigne = this.base[i].getElementsByTagName('th');
			j = baseLigne.length;
			while(j){
				j--;
				if (baseLigne[j].className.match(this.exclure) || this.base[i].className.match(this.exclure)) {
					continue;
				}
				if(!this.typeTri[j]){
					this.typeTri[j] = baseLigne[j].className.match(this.classeCh) || baseLigne[j].className.match(this.classeNum) || '';
				}
				baseLigne[j].ref = j;
				baseLigne[j].style.cursor = 'pointer';
				that = this;
				this.addEvent(baseLigne[j], 'click', function(e){
					var origine = e.target?e.target:window.event.srcElement;
					if (origine.ref == that.en_cours) {
						that.retourne();
					}
					else {
						that.multisort(origine.ref, origine.className);
						that.en_cours = origine.ref;
					}
					that.affiche();
				});
			}
		}
		else if(this.base[i].getElementsByTagName('td').length>0){
			j = this.base[i].getElementsByTagName('td').length;
			var tabValeurs = [];
			while(j){
				j--;
				if(!this.typeTri[j] && this.base[i].getElementsByTagName('td')[j].innerHTML.replace(/^[\s\n\t]+[.]+[\s\n\t]+$/g,'').match(/^(\d{1,2})\/(\d{1,2})\/(\d{2})(\d{2})?$/)){
					this.typeTri[j] = this.classeDate;
				}
				tabValeurs.push(this.base[i].getElementsByTagName('td')[j].innerHTML.replace(/^[\s\n\t]+[.]+[\s\n\t]+$/g,''));
			}
			this.valeurs.push(tabValeurs.reverse().join(this.separateur));
			this.affichage.push(i);
		}
	}
	this.multisort(this.en_cours);
	this.affiche();
}
Triable.prototype.addEvent = function(element, type, callback){
	if (element.attachEvent){
		element.attachEvent("on" + type, callback);
	}
	else{
		element.addEventListener(type, callback, false);
	}
}
Triable.prototype.gestClic = function(){
	if(this.ref == that.en_cours){
		that.retourne();
	}
	else{
		that.multisort(this.ref);
	}
	that.affiche();
}
Triable.prototype.multisort = function(position){
	var separateur = this.separateur;
	switch (this.typeTri[position].toString()) {
		case this.classeCh:
			this.valeurs.sort(function(b, a){
				a = a.split(separateur)[position].toLowerCase();
				b = b.split(separateur)[position].toLowerCase();
				return a == b ? 0 : a < b ? -1 : 1;
			});
			break;
		case this.classeNum:
			this.valeurs.sort(function(b, a){
				a = parseFloat(a.split(separateur)[position].replace(/[^\d\.]*/g, '')) || 0;
				b = parseFloat(b.split(separateur)[position].replace(/[^\d\.]*/g, '')) || 0;
				return a == b ? 0 : a < b ? -1 : 1;
			});
			break;
		case this.classeDate:
			this.valeurs.sort(function(b, a){
                a = new Date(parseInt(a.split('/')[2],10), parseInt(a.split('/')[1],10) - 1, parseInt(a.split('/')[0],10));
                b = new Date(parseInt(b.split('/')[2],10), parseInt(b.split('/')[1],10) - 1, parseInt(b.split('/')[0],10));
                return a == b ? 0 : a < b ? -1 : 1;
            });
            break;
		default:
			this.valeurs.sort(function(b, a){
				a = a.split(separateur)[position].toLowerCase();
				b = b.split(separateur)[position].toLowerCase();
				if (this.numstrict) {
					a = (parseFloat(a) == a) ? parseFloat(a) : a;
					b = (parseFloat(b) == b) ? parseFloat(b) : b;
				}
				else 
					if (!isNaN(parseFloat(a)) && !isNaN(parseFloat(b))) {
						a = parseFloat(a);
						b = parseFloat(b);
					}
				if (a === b) {
					return 0;
				}
				if (typeof a === typeof b) {
					return a < b ? -1 : 1;
				}
				return typeof a < typeof b ? -1 : 1;
			});
	}
}
Triable.prototype.retourne = function(){
	this.valeurs.reverse();
}
Triable.prototype.affiche = function(){
	for(var i=0; i<this.affichage.length; i++){
		for(var j=0; j<this.base[this.affichage[i]].getElementsByTagName('td').length; j++){
			this.base[this.affichage[i]].getElementsByTagName('td')[j].innerHTML = this.valeurs[i].split(this.separateur)[j];
		}
	}
}
Triable.prototype.getSeparateur = function(){
	var lesElements = this.elt.getElementsByTagName('td');
	var nbElements = lesElements.length;
	while(nbElements){
		nbElements--;
		while(lesElements[nbElements].innerHTML.match(this.separateur)){
			this.separateur += '_';
		}
	}
}
Exemples d'appels :
Code html : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
<body onload="new Triable()">
<body onload="new Triable(document.getElementById('matable'))">
<body onload="new Triable(document.getElementById('matable'), {exclure:'no_sort',numstrict:true,classeTH:'sortable'})"

EDIT
Améliorations envisagées :
* Ajouter un style CSS pour indiquer quelle colonne est triée et dans quel sens.