PageSourceSearch

https://my.sequoiasoft.com/akuiteo.clients/js/akuiteo/lib/aktable.js?build=null

js sequoiasoft.com collected 2026-10-08 20:29:25 UTC 37,508 bytes, 1,236 lines download raw bytes

1/*
2 * AKTable : Composant tableau pour Akuiteo
3 */
4
5var AKTable = Backbone.View.extend({
6	// Classe privée décrivant les colonnes
7	ColumnDescription : Backbone.Model.extend({
8		property : '',
9		title : '',
10		visible : true,
11		sortable : true,
12		resizable : true,
13		initVisible : true,
14		size : -1,
15		htmlContent:null,
16		templateHtmlContent:null,
17		exportable : true
18	}),
19
20	/** 
21	 * Constructeur 
22	 */
23	constructor : function(parentTable, rowTemplate, groupedRowTemplate) {
24		this._initVariables();
25		this.$el = parentTable;
26		this.tableId = parentTable.attr('id');
27		this.rowTemplate = rowTemplate.html().replace("<![CDATA[", "").replace("]]>", "").replace(/\n|\r|\t/gi,'');
28		
29		if (groupedRowTemplate) {
30			this.groupedRowTemplate = groupedRowTemplate.html().replace("<![CDATA[", "").replace("]]>", "").replace(/\n|\r|\t/gi,'');	
31		} else {
32			this.groupedRowTemplate = "";
33		}
34		
35		this.originalRowTemplate = this.rowTemplate;
36		this._loadParams();
37
38		var parent = this;
39		this.selector = new AKMultiSelector();
40		this.selector.init({
41			title:keywords['aktable.selection.title'],
42			prefix:parentTable.attr('id') + "_param_",
43			okAction : parent._changeOrder,
44			defaultAction : parent._resetOrder,
45			parent:parent
46		});
47		
48		$(window).resize(function() {
49			parent._recreateSliders();
50		});
51		
52		$(window).on('load', function() {
53			parent._recreateSliders();
54		});
55		
56		this.$el.parent().on("scroll", function(event) {
57			parent._recreateSliders();
58		});
59		
60	},
61
62	/**
63	 * Initialise les variables 
64	 */
65	_initVariables : function() {
66		this.url = "", // URL indiquant le fournisseur de données
67		this.params = null; // Paramètres supplémentaires dans l'URL
68		this.page = 1; // Page courante
69		this.rownum = -1; // Nombre de lignes par page (-1=auto)
70		this.autoRownum = -1; // Nombre de lignes affichées
71		this.currentSort = ""; // Tri par défaut
72		this.numberOfPages = 1; // Nombre total de pages
73		this.numberOfRecords = 0; // Nombre total d'enregistrements
74		this.groupByProperties = null; // Propriétés de regroupement
75		this.selectionType = "none"; // Type de sélection (none, single, select)
76		this.data = null; // Données
77		this.rowTemplate = null; // Template pour l'affichage des lignes
78		this.originalRowTemplate = null; // Template original pour l'affichage des lignes (utilisé dans la réorganisation)
79		this.groupedRowTemplate = null; // Template pour les lignes groupées
80		this.columns = new Array(); // Colonnes
81		this.originalColumns = new Array(); // Colonnes à l'initialisation du tableau, utile pour la réorganisation
82		this.rendered = false;
83		this.selector = null;
84		this.originalHeader = null;
85		this.tableId = null;
86		this.multipleTableSort = false;
87		this.hasFooter = true;		
88		this.categoriesExpanded = [];
89	},
90
91	/**
92	 * (privée) Chargement des paramètres de la table
93	 */
94	_loadParams : function() {
95		if (!this.$el.attr('data-url')) {
96			jQuery.error('Attribut data-url non défini');
97		} else {
98			this.url = this.$el.attr('data-url');
99		}
100		
101		if (this.$el.attr('data-rownum')) {
102			this.rownum = this.$el.attr('data-rownum');
103		}
104		
105		if (this.$el.attr('data-default-sort')) {
106			this.currentSort = this.$el.attr('data-default-sort');
107		}
108		
109		if (this.$el.attr('data-selection-type')) {
110			this.selectionType = this.$el.attr('data-selection-type');
111		}
112
113		if (this.$el.attr('data-no-footer')) {
114			this.hasFooter = false;
115		}
116		
117		if (this.$el.attr('data-class')) {
118			var cssClass = this.$el.attr('data-class');
119			if (cssClass !== 'none') {
120				this.$el.addClass(cssClass);
121			}
122		} else {
123			this.$el.addClass('aktable');
124		}
125		
126		// Construit les descriptions de colonnes
127		var parent = this;
128		var realColumnIndex = 0;
129		this.$el.find("tr:first th").each(function(index) {
130			var cd = new parent.ColumnDescription();
131			var currentTH = $(this);
132
133			cd.title = currentTH.html();
134
135			if (!currentTH.attr('data-property')) {
136				jQuery.error('Attribut data-property non défini pour la colonne #' + index);
137			} else {
138				cd.property = currentTH.attr('data-property');
139			}
140			
141			if (currentTH.attr('data-visible')) {
142				if (currentTH.attr('data-visible') === "false") {
143					// On supprime la colonne
144					currentTH.remove();
145					parent.rowTemplate = parent._removeColumn(parent.rowTemplate, realColumnIndex);
146					parent.originalRowTemplate = parent._removeColumn(parent.originalRowTemplate, realColumnIndex);
147					return;
148				}
149			}
150			
151			if (currentTH.attr('data-sortable')) {
152				cd.sortable = currentTH.attr('data-sortable') === "true";
153			}
154			
155			if (currentTH.attr('data-resizable')) {
156				cd.resizable = currentTH.attr('data-resizable') === "true";
157			}
158
159			if (currentTH.attr('data-defaut-visible')) {
160				cd.visible = currentTH.attr('data-defaut-visible') === "true";
161			}
162			
163			if (currentTH.attr('data-exportable')) {
164				cd.exportable = currentTH.attr('data-exportable') === "true";
165			}
166			
167			parent.columns.push(cd);
168			realColumnIndex++;
169		});
170		
171		this.originalColumns = this.columns.slice(0); // C'est un "clone" du tableau
172		this.originalHeader = this.$el.find('thead tr:first')[0].outerHTML;
173	},
174
175	/**
176	 * (privée) recréation des sliders
177	 */
178	_recreateSliders : function() {
179		if (!this.hasFooter) {
180			return;
181		}		
182		// On détruit les sliders, et on les re-créé
183		var _self = this;
184		$('.aktable-resize-drag-handle').remove();
185		$.each(_self.columns,function(index,column) {
186			_self._initResizablePropertyForColumn(index, column.resizable);
187		});
188		
189		// Ensuite on réinitialise la position des sliders
190		var table = this.$el;
191		var parentWidth = table.parent().width();
192		var newSliderPosition = table.position().left;
193		table.find(' TR:first TH').each(function(index) {
194			var th = $(this);
195			newSliderPosition += th.outerWidth();
196			if (newSliderPosition>parentWidth) {
197				$("#slider" + _self.tableId + "_id" + index).remove();
198				return;
199			}
200			$("#slider" + _self.tableId + "_id" + index).css({
201				left : newSliderPosition,
202				height : th.outerHeight() + 'px'
203			});
204		});	
205
206		
207	},
208	
209	/**
210	 * (privée) Suppression d'une colonne
211	 */
212	_removeColumn:function (content, i) {
213		var beginIndex = content.indexOf('<tr');
214		var endIndex = content.indexOf('<td');
215		
216		var trDescription = content.substring(beginIndex,endIndex);
217		var templateWithoutTR = content.substring(endIndex).replace(/<\/tr>/g,'');
218		
219		// Extrait les balises TD
220		var regexp = /<td\b[^>]*>([\s\S]*?)<\/td>/gm;
221		var arrayOfTD = new Array();
222	    while(match = regexp.exec(templateWithoutTR)) {
223	    	arrayOfTD.push(match[0]);
224	    }
225	    
226	    // Supprime l'élément d'index i
227	    arrayOfTD.splice(i,1);
228	    
229		return trDescription + arrayOfTD.join('') + '</tr>';
230	},
231
232	/**
233	 * (privée) Création du séparateur pour le redimensionnement des colonnes
234	 */
235	_initResizablePropertyForColumn : function(i, resizable) {
236		var parent = this;
237		var table = parent.$el;
238		var separator = $('<div class="aktable-resize-drag-handle" id="slider'+ this.tableId + "_id" + i + '"></div>')
239				.insertBefore(table).data('myindex', i);
240
241		if (resizable) {
242			separator.css('cursor', 'col-resize');
243			var oldPos;
244			separator.draggable({
245				axis : "x",
246				start : function(event,ui) {
247					$(this).toggleClass("dragged");
248					$(this).css({height : $('#' + parent.tableId).height()+ 'px'});
249					oldPos = ui.position.left;
250				},
251				stop : function(event, ui) {
252					$(this).toggleClass("dragged");
253					var newPos = ui.position.left;
254					var index = $(this).data("myindex");
255					parent._resetTableSizes(parent, newPos - oldPos, index);
256				}
257			});
258		} else {
259			separator.css('cursor', 'auto');
260		}
261	},
262
263
264	/**
265	 * (privée) Chargement des paramètres de la table
266	 */
267	_resetTableSizes : function(parent, change, columnIndex) {
268		var table = parent.$el; 
269		var currentColumnWidth = $('#' + this.tableId + ' tr th').get(columnIndex).offsetWidth;
270		var newWidth = (currentColumnWidth + change) + 'px';
271
272		var tableWidth = $(table).width();
273		table.css('width',(tableWidth+change)+ 'px');
274		
275		$('#' + this.tableId + ' tr').each(function() {
276			$(this).find('td').eq(columnIndex).css('width', newWidth);
277			$(this).find('th').eq(columnIndex).css('width', newWidth);
278		});
279		
280		parent.columns[columnIndex].size = currentColumnWidth + change;
281		
282		parent._recreateSliders();
283		parent._storeInCookies();
284		table.trigger('resize');
285	},
286
287	/**
288	 * (privée) "Décore" l'entête du tableau (ajoute le tri sur clic et les indicateurs de tri)
289	 */
290	_decorateHeaders : function() {
291		if (this.rendered) {
292			return;
293		}
294		// Ajout d'un listener sur les colonnes triables
295		var parent = this;
296		this.$el.find(' TR:first TH').each(function(index) {
297			var column = parent.columns[index];
298			if (column.sortable) {
299				var th = $(this);
300				th.addClass('aktable-sortable');
301				th.click(function() {
302					var isAsc = false, isDesc = false, sortIndex = -1;
303					
304					if (parent.multipleTableSort) {
305						for ( var i = 1; i < 5; i++) {
306							// Cherche dans les enfants si ce n'est pas déjà trié !
307							if (th.children('.aktable-asc' + i).length>0) {
308								th.children('.aktable-asc' + i).remove();
309								sortIndex = i;
310								isAsc = true;
311								break;
312							}
313							if (th.children('.aktable-desc' + i).length>0) {
314								th.children('.aktable-desc' + i).remove();
315								isDesc = true;
316								break;
317							}
318						}
319					} else {
320						if (th.children('.aktable-asc' ).length>0) {
321							th.children('.aktable-asc' ).remove();
322							sortIndex = 0;
323							isAsc = true;
324						}
325						if (th.children('.aktable-desc').length>0) {
326							th.children('.aktable-desc').remove();
327							isDesc = true;
328						}						
329					}
330
331
332					if (isAsc) {
333						// Descendant
334						if (parent.multipleTableSort) {
335							$(th).append('<span class="aktable-desc'+ sortIndex +'"></span>');
336						} else {
337							$(th).append('<span class="aktable-desc"></span>');
338						}
339						parent._changeSort(column.property, 'desc');
340					} else if (isDesc) {
341						// Vide
342						parent._changeSort(column.property, '');
343					} else {
344						// Ascendant
345						var maxSortIndex = parent.currentSort == ''?1:(parent.currentSort.split(';').length + 1);
346
347						if (parent.multipleTableSort && maxSortIndex == 5) {
348							return;
349						}
350
351						if (!parent.multipleTableSort) {
352							maxSortIndex = '';
353							parent.currentSort = '';
354						}
355						
356						$(th).append('<span class="aktable-asc'+ maxSortIndex +'"></span>');
357						parent._changeSort(column.property, 'asc');
358					}
359					parent.goToPage(1);
360				});
361			}
362
363		});
364		
365		this._addSortIndicator();
366	},
367
368	/**
369	 * (privée) Change les colonnes triées
370	 * @param prop propriété
371	 * @param direction (asc ou desc)
372	 */
373	_changeSort : function(prop, direction) {
374		
375		var elements = this.currentSort.split(';');
376		var output = [];
377		var found = false;
378		// Reconstruit la chaine currentSort
379		for ( var i = 0; i < elements.length; i++) {
380			var temp = elements[i].split(',');
381			var currentProp = temp[0];
382			if (direction == '') {
383				if (currentProp != prop) {
384					output.push(elements[i]);
385				}
386			} else {
387				if (currentProp == prop) {
388					output.push(currentProp + ',' + direction);
389					found = true;
390				} else if (elements[i] != '') {
391					output.push(elements[i]);
392				}
393			}
394		}
395		
396		if (direction != '' && !found) {
397			output.push(prop + ',' + direction);
398		}
399		
400		this.currentSort = output.join(';');
401
402		// Redessine les indicateurs de tri
403		this._addSortIndicator();
404	},
405	
406	/**
407	 * (privée) Ajoute un indicateur de tri
408	 */
409	_addSortIndicator:function() {
410		if (this.currentSort == '') {
411			return;
412		}
413		
414		var elements = this.currentSort.split(';');
415
416		// Efface les anciens indicateurs de tri
417		for ( var colIndex = 0; colIndex < this.columns.length; colIndex++) {
418			var currentCol = this.columns[colIndex];
419			var th = this.$el.find(' TR:first TH')[colIndex];
420			if (this.multipleTableSort) {
421				for (var j=1;j<5;j++) {
422					$(th).children('.aktable-asc' + j).remove();
423					$(th).children('.aktable-desc' + j).remove();
424				}
425			} else {
426				$(th).children('.aktable-asc').remove();
427				$(th).children('.aktable-desc').remove();				
428			}
429		}
430		
431		// Et "dessine" les nouveaux
432		for ( var i = 0; i < elements.length; i++) {
433			var temp = elements[i].split(',');
434			var currentProp = temp[0], currentDirection = temp[1];
435			this.$el.find(' TR:first TH').each(function(index) {
436				var currentTH = $(this);
437				if (currentTH.attr('data-property') == currentProp) {
438					if (this.multipleTableSort) {		
439						if (currentDirection == 'asc') {
440							currentTH.append('<span class="aktable-asc'+ (i+1) +'"></span>');
441						} else {
442							currentTH.append('<span class="aktable-desc'+ (i+1) +'"></span>');
443						}							
444					} else {
445						if (currentDirection == 'asc') {
446							currentTH.append('<span class="aktable-asc"></span>');
447						} else {
448							currentTH.append('<span class="aktable-desc"></span>');
449						}							
450					}
451				}						
452			});
453		}
454	},
455
456	/**
457	 * (privée) Génération du contenu
458	 */
459	_generateContents : function() {
460		var tbody = this.$el.find('tbody');
461		if (tbody.length != 0) {
462			tbody.remove();
463		}
464
465		this.$el.append("<tbody></tbody>");
466
467		var content = "";
468		if (this.data != null && this.data.length > 0) {
469			for ( var i = 0; i < this.data.length; i++) {
470				content += this._generateRow(this.data[i]);
471			}
472		} else {
473			// Aucune donnée trouvée
474			content += '<tr><td colspan="'+ this.columns.length +'" style="text-align:center">';
475			content += '<br/><br/><br/><br/><br/>';
476			content += '<b>
476' + keywords['aktable.nodatafound'] +'</b>';
477			content += '<br/><br/><br/><br/><br/>';
478			content += '</td></tr>';	
479		}
480
481		this.$el.find('tbody').append(content);
482		if (this.data != null && this.data.length > 0) {
483			for ( var i = 0; i < this.data.length; i++) {
484				this._decorateExpandCollapseIcon(this.data[i]);
485			}
486			
487			if (this.rownum == -1) {
488				// Tronque
489				if (this.autoRownum > -1 && this.page==this.numberOfPages) {
490					// On est sur la dernière page
491					return;
492				}				
493				var wholeHeight = $(window).height() - 70; // 70=marge pour le pied de tableau 
494				var rows = this.$el.find("tr:gt(0)");
495				var parent = this;
496				this.autoRownum = -1;
497				rows.each(function (index,element) {
498					var e = $(element);
499					var yPosition = getScreenCordinates(e).y;
500					if (yPosition + e.height() > wholeHeight) {
501						e.remove();
502					}
503				});
504				
505				this.autoRownum = this.$el.find("tr:gt(0)").length;
506			}
507			
508		}
509		
510	},
511
512	/**
513	 * (privée) Génère une ligne du tableau 
514	 * @param current donnée affichée
515	 */
516	_generateRow:function(current) {
517		if (current.type=='BEAN') {
518			var compiled = _.template(this.rowTemplate, current.data);
519			return this._addIdentifier(compiled, current, false);
520		} 
521
522		// Catégorie
523		var content = "";
524		var parent = this;
525		
526		// Une ligne de groupe
527		var compiled = _.template(this.groupedRowTemplate, current); 
528		content += this._addIdentifier(compiled, current, true);
529
530		// Les enfants...
531		$.each(current.children, function(key, child) {
532			content += parent._generateRow(child);
533		});
534		
535		return content;
536	},
537	
538	/**
539	 * (privée) Ajoute un identifiant à une ligne  
540	 * @param html portion de code à décorer
541	 * @param row ligne courante
542	 * @param category si true, on a affaire à une catégorie
543	 */
544	_addIdentifier:function(html, row, category) {
545		// On rajoute un identifiant
546		var id = row.id;
547		var wholeLine = $('<table>' + html + '</table>');
548		wholeLine.find(' TR:first').attr('id',this.tableId + (category?"_cat_":"_row_") + id);
549		if (category) {
550			var iconId =  this.tableId + "_cat_" + id + "_icon";
551			var sClass = row.expanded ? 'aktable-collapse' : 'aktable-expand';
552			wholeLine.find(' TR:first TD:first'). 
553				prepend('<span class="' + sClass + '" style="width:9px;padding-left:10px" id="'+ iconId +'">&nbsp;</span>');
554		} else {
555			if (!row.expanded) {
556				wholeLine.find(' TR:first').hide()
557			}
558		}
559		return wholeLine.html().replace(/<tbody>|<\/tbody>/gi,'');
560	},
561	
562	/**
563	 * Décore l'icône +/- d'une catégorie
564	 * @param current donnée
565	 */
566	_decorateExpandCollapseIcon:function(current) {
567		if (current.type=='BEAN') {
568			return;
569		}
570		var parent = this;
571		var id = this.tableId + "_cat_" + current.id + "_icon";
572		var button = $('span[id="'+id+'"]'); // On utilise cette syntaxe
573		if (button.length != 0) {
574			button.parent().on('click', function() {
575				if (button.hasClass('aktable-collapse')) {
576					// Collapse
577					button.removeClass('aktable-collapse');
578					button.addClass('aktable-expand');
579					parent._changeVisibility(parent, current,false,false);
580					parent._addOrRemoveCategoryExpanded(current.id, false);
581				} else {
582					// Expand
583					button.removeClass('aktable-expand');
584					button.addClass('aktable-collapse');
585					parent._changeVisibility(parent, current,true,false);
586					parent._addOrRemoveCategoryExpanded(current.id, true);
587				}
588				parent._recreateSliders();	
589			});
590		}
591		
592		
593		// Les enfants...
594		$.each(current.children, function(key, child) {
595			parent._decorateExpandCollapseIcon(child);
596		});
597	},
598	
599	_addOrRemoveCategoryExpanded: function(categoryId, addCategory) {
600		if (addCategory) {
601			this.categoriesExpanded.push(categoryId);
602		} else {
603			var idx = this.categoriesExpanded.indexOf(categoryId);
604			if (idx !== -1) {
605				this.categoriesExpanded.splice(idx, 1);
606			}
607		}
608	},
609	
610	/**
611	 * Change la visibilité d'une catégorie et de ses enfants
612	 * @param _self objet aktable
613	 * @param current donnée courante
614	 * @param show si true, on affiche les données, si false on les cache
615	 * @param includeCurrentCategory est-ce qu'on affiche/cache la catégorie ? Dans tous les cas, on 
616	 */
617	_changeVisibility:function(_self, current, show, includeCurrentCategory) {
618		if (current.type=='CATEGORY') {
619			if (includeCurrentCategory) {
620				var id = _self.tableId + "_cat_" + current.id;
621				if (show) {
622					$('tr[id="'+id+'"]').show();
623				} else {
624					$('tr[id="'+id+'"]').hide();
625				}
626			}
627
628			//var button = $('span[id="'+id+'_icon"]');
629			if (!show || !includeCurrentCategory)  {
630				$.each(current.children, function(key, child) {
631					_self._changeVisibility(_self,child,show,true);
632				});
633			}
634			
635		} else {
636			// Bean
637			var id = _self.tableId + "_row_" + current.id;
638			if (show) {
639				$('tr[id="'+id+'"]').show();
640			} else {
641				$('tr[id="'+id+'"]').hide();
642			}			
643		}
644	},
645	
646	/**
647	 * (privée) Génération du pied du tableau
648	 */
649	_addFooter : function() {
650		if (this.rendered) {
651			return;
652		}
653		var parent = this;
654		
655		var content = "";
656		content += '<div id="'+ this.tableId + '_loading" class="aktable-loading-area"></div>';
657		
658		if (!this.hasFooter) {
659			this.$el.after(content);
660			return;
661		}
662		
663		content += '<div id="'+ this.tableId + '_footer" style="border-top:1px solid #DDDDDD;padding:5px;background-color: #E5E5E5;height:45px">';
664		content += '	<table width="100%" border="0" cellspacing="0" cellpadding="0"><tr>';
665		content += '		<td style="width:130px"><div class="btn-group">';
666		if (window.AKTableCanDisplayExport && window.AKTableCanDisplayExport === true) {
667			content += '			<button class="btn btn-static" id="' + this.tableId + '_footer_excel_all" title="'+ keywords['aktable.export.all.lines']+'"><i class="aktable-excel"></i></button>';
668		}
669		content += '			<button class="btn btn-static" id="' + this.tableId + '_footer_config" title="'+ keywords['aktable.column.management']+'"><i class="icon-list-alt"></i></button>';
670		content += '			<button class="btn btn-static" id="' + this.tableId + '_footer_reload" title="'+ keywords['aktable.reload']+'"><i class="icon-refresh"></i></button>';
671		content += '		</div></td>';
672		content += '		<td align="center"><div class="btn-group">';
673		content += '			<button class="btn btn-static" id="' + this.tableId + '_footer_first" title="'+ keywords['aktable.first.page']+'"><i class="aktable-first"></i></button>';
674		content += '			<button class="btn btn-static" id="' + this.tableId + '_footer_previous" title="'+ keywords['aktable.previous.page']+'"><i class="aktable-previous"></i></button>';
675		content += '			<button class="btn btn-static" ><span id="' + this.tableId + '_footer_page"></span></button>';
676		content += '			<button class="btn btn-static" id="' + this.tableId + '_footer_next" title="'+ keywords['aktable.next.page']+'"><i class="aktable-next"></i></button>';
677		content += '			<button class="btn btn-static" id="' + this.tableId + '_footer_last" title="'+ keywords['aktable.last.page']+'"><i class="aktable-last"></i></button>';
678		content += '			<div class="btn btn-static" ><select id="' + this.tableId + '_footer_combo" style="width:50px;height:17px;line-height:18px;margin:0;padding:0;font-size:9.5px">';
679		
680		$.each([-1,10,25,50,100,250,500],function(key,val) {
681			var selected = '';
682			if (val == parent.rownum) {
683				selected = ' selected="selected"';
684			}
685			
686			var displayedValue = val==-1?"Auto":val;
687			content += '<option value="'+val+'"'+ selected +'>'+displayedValue+'</option>';			
688		});
689		
690		content += '			</select><span style="font-size:11px" id="' + this.tableId + '_footer_lblPerPage"></span>';
691		content += '			</div>';
692		content += '		</div></td>';
693		content += '		<td align="right" id="' + this.tableId + '_total"></td>';
694		content += '	</tr></table>';
695		content += '</div>';
696		this.$el.after(content);
697
698		// Boutons export excel
699		$('#' + this.tableId + '_footer_excel_all').click(function() {
700			parent._exportExcel(true);
701		});
702
703		// Bouton Config & Reload
704		$('#' + this.tableId + '_footer_config').click(function() {
705			var leftList = [], rightList = [];
706			for (var colIndex = 0; colIndex < parent.columns.length; colIndex++) {
707				var currentCol = parent.columns[colIndex];
708				rightList.push({key:currentCol.property, value:currentCol.title});
709			}
710			
711			for (var originalColIndex = 0; originalColIndex < parent.originalColumns.length; originalColIndex++) {
712				var currentOriginalCol = parent.originalColumns[originalColIndex];
713				if (currentOriginalCol.visible === false) {
714					leftList.push({key:currentOriginalCol.property, value:currentOriginalCol.title});
715				}
716			}
717
718			parent.selector.open(leftList,rightList); 
719		});		
720		$('#' + this.tableId + '_footer_reload').click(function() {
721			parent.loadData();
722		});
723
724		// Boutons first,previous,next,last
725		$('#' + this.tableId + '_footer_first').click(function() {
726			parent.goToPage(1);
727		});
728		$('#' + this.tableId + '_footer_previous').click(function() {
729			parent.goToPage(parent.page - 1);
730		});
731		$('#' + this.tableId + '_footer_next').click(function() {
732			parent.goToPage(parent.page + 1);
733		});
734		$('#' + this.tableId + '_footer_last').click(function() {
735			parent.goToPage(parent.numberOfPages);
736		});
737
738		// Combo
739		$('#' + this.tableId + '_footer_combo').change(function() {
740			parent.rownum = $('#' + parent.tableId+ '_footer_combo option:selected').val();
741			if (parent.rownum==-1) {
742				$(window).scrollTop(0);
743				parent.autoRownum=-1;
744			}
745			parent._storeInCookies();
746			parent.page = 1;
747			parent.loadData();
748		});
749
750	},
751
752	/**
753	 * (privée) rafraîchit le pied du tableau
754	 */
755	_refreshFooter : function() {
756		if (!this.hasFooter) {
757			return;
758		}
759		
760		if (this.numberOfRecords == 0) {
761			$('#' + this.tableId + '_footer_excel_all').attr("disabled","disabled");
762			$('#' + this.tableId + '_footer_first').attr("disabled","disabled");
763			$('#' + this.tableId + '_footer_previous').attr("disabled","disabled");
764			$('#' + this.tableId + '_footer_next').attr("disabled","disabled");
765			$('#' + this.tableId + '_footer_last').attr("disabled","disabled");
766		
767			$('#' + this.tableId + '_footer_page').html("&nbsp;");
768			$('#' + this.tableId + '_total').text(keywords['aktable.nodata']);
769			return;
770		}
771		
772		if (this.rownum == -1) {
773			// Recalcul du nombre de pages
774			if (this.autoRownum > 0) {
775				this.numberOfPages = Math.ceil(this.numberOfRecords / this.autoRownum); 		
776			} 
777		}
778		
779		$('#' + this.tableId + '_footer_excel_all').removeAttr("disabled");
780		
781		// Zone de navigation
782		$('#' + this.tableId + '_footer_page').text(this.page + " / " + this.numberOfPages);
783
784		if (this.page > 1) {
785			$('#' + this.tableId + '_footer_previous,#' + this.tableId + '_footer_first').removeAttr("disabled");
786		} else {
787			$('#' + this.tableId + '_footer_previous,#' + this.tableId + '_footer_first').attr("disabled","disabled");
788		}
789
790		if (this.page != this.numberOfPages) {
791			$('#' + this.tableId + '_footer_next,#' + this.tableId + '_footer_last').removeAttr("disabled");
792		} else {
793			$('#' + this.tableId + '_footer_next,#' + this.tableId + '_footer_last').attr("disabled","disabled");
794		}
795
796		// Libellé XXXX/page
797		$('#' + this.tableId + '_footer_lblPerPage').text(keywords[this.groupByProperties ? 'aktable.categories.per.page' : 'aktable.lines.per.page']);
798		
799		// Section "1-10 sur XX"
800		var start = (this.page - 1) * (this.rownum==-1?this.autoRownum:this.rownum) + 1;
801		var end = Math.min((this.page) * (this.rownum==-1?this.autoRownum:this.rownum), this.numberOfRecords);
802		var total = start + " - " + end + " "+ keywords['aktable.on'] +" " + this.numberOfRecords;
803		$('#' + this.tableId + '_total').text(total);
804	},
805
806	/**
807	 * (privée) Export Excel
808	 * @param exportAll si vrai, exporte toutes les données sinon exporte seulement la page en cours
809	 */
810	_exportExcel : function(exportAll) {
811		var exportedColumns = [];
812
813		for ( var i = 0; i < this.columns.length; i++) {
814			var currentCol = this.columns[i];
815			if (currentCol.exportable) {
816				exportedColumns.push({
817					property : currentCol.property,
818					visible : currentCol.visible,
819					position : currentCol.position,
820					title : currentCol.title
821				});
822			}
823		}
824
825		if (exportAll) {
826			page = -1;
827			rowsPerPage = -1;
828			sort = '';
829		} else {
830			page = this.page;
831			rowsPerPage = (this.rownum==-1?this.autoRownum:this.rownum);
832			sort = this.currentSort;
833		}
834
835		var jsonColumns = JSON.stringify(exportedColumns).replace(/'/g,"\\u0027"); // Remplace les quotes par le caractère unicode
836		var calledUrl = this.url + '?' ;
837		if (this.params != null && this.params.length != 0) {
838			calledUrl = calledUrl + $.param(this.params) + '&';
839		}
840		calledUrl = calledUrl + 'exportExcel=true&ts='+ new Date().getTime(); // Pour empêcher les problèmes sous IE
841		
842		// On créé un faux formulaire que l'on va soumettre
843		var content = '<input type="text" name="page" value="' + page + '" />';
844		content += '<input type="text" name="rowsPerPage" value="' + rowsPerPage + '" />';
845		content += '<input type="text" name="sort" value="' + sort + '" />';
846		content += '<input type="text" name="jsonColumns" value=\'' + jsonColumns + '\'/>';
847
848		var token = $("meta[name='_csrf']").attr("content");
849		content += '<input type="text" name="_csrf" value="' + token+ '" />';
850
851		
852		$('<form>', {
853			"id" : "exportExcelTempForm",
854			"method" : "post",
855			"html" : content,
856			"action" : calledUrl
857		}).appendTo(document.body).submit();
858
859		setTimeout(function() {
860			$('#exportExcelTempForm').remove();
861		}, 100);
862	},
863	
864	
865	/**
866	 * (privée) Fonction appelée après réorganisation des colonnes
867	 * @param parent objet aktable associé à la table
868	 */
869	_changeOrder:function(parent) {
870		parent._reorganize(parent, parent.selector.getSelection(),true);
871	},
872
873	/**
874	 * (privée) Fonction appelée après avoir cliqué sur "Valeurs par défaut" réorganisation des colonnes
875	 * @param parent objet aktable associé à la table
876	 */
877	_resetOrder:function(parent) {
878		var selection = [];
879		$.each(parent.originalColumns,function(key,column) {
880			selection.push(column.property);
881		});
882		parent._reorganize(parent, selection,true);
883	},	
884	
885	/**
886	 * (privée) 
887	 * @param _self
888	 * @param selection
889	 */
890	_reorganize:function(_self, selection, goToPage) {
891		var content = _self.originalRowTemplate;
892		
893		var beginIndex = content.indexOf('<tr');
894		var endIndex = content.indexOf('<td');
895		
896		var trDescription = content.substring(beginIndex,endIndex);
897		var templateWithoutTR = content.substring(endIndex).replace(/<\/tr>/g,'');
898		
899		var template = $('<table><tr>
899' + templateWithoutTR + '</tr></table>');
900		
901		// On bascule toutes les colonnes en invisible
902		$.each(_self.originalColumns,function(key,column) {
903			column.visible = false;
904		});
905		
906		// Remplit une map de données
907		var map = {};
908		for (var i=0;i<_self.originalColumns.length;i++) {
909			var currentCol = _self.originalColumns[i];
910			var th = $('<table>' + _self.originalHeader + '</table>').find('tr:first th')[i];
911			currentCol.htmlContent = $(th)[0].outerHTML;
912			
913			var thTemplate = template.find('td')[i];
914			currentCol.templateHtmlContent = thTemplate?$(thTemplate)[0].outerHTML:'';			
915			
916			map[currentCol.property] = currentCol; 
917		}
918
919		// Efface toutes les colonnes des en-têtes et du template
920		_self.$el.find('th').remove();
921		template.find('td').remove();
922		
923		// Reconstruit le tout :)
924		var newColumns = [];
925		
926		for (var i=0;i<selection.length;i++) {
927			var key = selection[i];
928			var column = map[key];
929			if (!column) {
930				continue;
931			}
932			column.visible = true;
933			newColumns.push(column);
934
935			// La colonne originale est marquée comme visible
936			_self._getOriginalColumn(column.property).visible = true;
937			
938			// Contruite l'entête
939			_self.$el.find('tr:first').append(column.htmlContent);
940			// Et le template
941			template.find('tr:first').append(column.templateHtmlContent);
942		}
943		
944		// Ok on remet en état
945		_self.columns = newColumns;
946		var htmlContent = $(template)[0].outerHTML;
947		var cleanHtmlContent = htmlContent.replace(/<table>|<\/table>/gi,'').replace(/<tbody>|<\/tbody>/gi,'').replace(/&lt;/gi,'<').replace(/&gt;/gi,'>').replace(/<tr>|<\/tr>/gi,'');
948		_self.rowTemplate = trDescription + cleanHtmlContent + '</tr>';
949		_self.rendered = false;
950		_self._decorateHeaders();
951		_self.rendered = true;
952		
953		_self._recreateSliders();
954		
955		if (goToPage) {
956			_self.goToPage(1);
957		}
958		_self._storeInCookies();
959		_self.onRenderComplete();
960	},
961	
962	/**
963	 * (privée) Renvoie une propriété en fonction de la colonne
964	 * @param property
965	 * @returns
966	 */
967	_getOriginalColumn : function(property) {
968		var length = this.originalColumns.length;
969		for ( var i = 0; i < length; i++) {
970			var cd = this.originalColumns[i];
971			if (cd.property == property) {
972				return cd;
973			}
974		}
975		return null;
976	},
977	
978	_storeInCookies:function() {
979		var cols = [];
980		var width = [];
981		$.each(this.columns, function (index,column) {
982			cols.push(column.property);
983			if (column.size>-1) {
984				width.push({key:column.property,value:column.size});
985			}
986		});
987		
988		var toStore = {order:cols,width:width, rownum:this.rownum};
989		$.cookie(this._buildCookieId(), JSON.stringify(toStore), {expires:365});
990	},
991	
992	_retrieveFromCookies:function (goToPage) {
993		var cookie = $.cookie(this._buildCookieId());
994		if (cookie == null || cookie == '') {
995			return;
996		}
997		
998		var data = JSON.parse(cookie); 
999
1000		if (data.order==null || data.order.length == 0) {
1001			return;
1002		}
1003		
1004		
1005		// Réorganisation des colonnes
1006		this._reorganize(this, data.order,goToPage);
1007		
1008		// Et changement des tailles
1009		if (data.width.length>0) {
1010			var parent = this;
1011			$.each(data.width, function(widthPairIndex,widthPair) {
1012				parent.$el.find("tr:first th").each(function(columnIndex) {
1013					var property = $(this).attr('data-property');
1014					if (property == widthPair.key) {
1015						var newWidth = widthPair.value + 'px';
1016						$('#' + parent.tableId + ' tr').each(function() {
1017							$(this).find('td').eq(columnIndex).css('width', newWidth);
1018							$(this).find('th').eq(columnIndex).css('width', newWidth);
1019						});
1020
1021					}
1022				});
1023			});
1024		}
1025
1026		if (typeof data.rownum !== 'undefined') {
1027			this.rownum = data.rownum;
1028			this.autoRownum=-1;
1029			if (this.hasFooter) {
1030				$('#' + this.tableId + '_footer_combo').val(data.rownum);
1031			}
1032		}
1033		this._recreateSliders();
1034		
1035	},
1036
1037	_buildCookieId:function() {
1038		var url = window.location.pathname;
1039		var filename = url.substring(url.lastIndexOf('/')+1);
1040		return filename + "_" + this.tableId;
1041	},
1042	
1043	/********* Section publique *********/  
1044	/**
1045	 * Dessin de la table
1046	 */
1047	render : function(_goToPage) {
1048		var goToPage = typeof _goToPage !== 'undefined'? _goToPage: true ;
1049		this._decorateHeaders();
1050		this._generateContents();
1051		this._addFooter();
1052		this._recreateSliders();
1053		this.rendered = true;
1054		this._retrieveFromCookies(goToPage);
1055		// Réorganisation des colonnes
1056		var selection = [];
1057		$.each(this.columns,function(key,column) {
1058			if (column.visible) {
1059				selection.push(column.property);
1060			}
1061		});
1062		this._reorganize(this, selection,goToPage);
1063		this.onRenderComplete();
1064		return this ;
1065	},
1066	
1067
1068	/**
1069	 * Code appelé une fois le rendu terminé
1070	 */
1071	onRenderComplete:function() {
1072		
1073	},		
1074	
1075	/**
1076	 * Chargement des données
1077	 */
1078	loadData : function() {
1079		var parent = this;
1080		
1081		var rowsPerPage = this.rownum;
1082		if (this.rownum == -1) {
1083			if (this.autoRownum == -1) {
1084				rowsPerPage = 50;
1085			} else {
1086				rowsPerPage = this.autoRownum;
1087			}
1088		}
1089		
1090		var settings = {
1091			page:this.page,
1092			rowsPerPage:rowsPerPage,
1093			sort:this.currentSort,
1094			categoriesExpanded: JSON.stringify( this.categoriesExpanded).replace(/\"|\[|\]/g,""), // Supprime les doubles quotes et les crochets
1095			ts:new Date().getTime() // Pour empêcher les problèmes sous IE
1096		};
1097		
1098		if (this.params != null) {
1099			$.extend(settings,this.params);
1100		}
1101		
1102		var loadingId = this.$el.attr('id') + '_loading';
1103		
1104		var loadingArea = $('#' +loadingId);
1105		var table = $(this.$el);
1106		loadingArea.css("top", table.position().top);
1107		loadingArea.css("left",table.position().left);
1108		loadingArea.css("width",table.outerWidth());
1109		loadingArea.css("height",table.outerHeight());
1110		
1111		$.ajax({
1112			type : 'GET',
1113			url : this.url + '?' + $.param(settings),
1114			contentType : 'application/json;charset=UTF-8',
1115			dataType : 'json',
1116			cache : false,
1117			beforeSend : function () {
1118				loadingArea.show(); 
1119			},
1120			success : function(_data) {
1121				loadingArea.hide(); 
1122				parent.setData(_data);
1123			},
1124			error : function(jqXHR, textStatus, errorThrown) {
1125				loadingArea.hide();
1126				alert(keywords['aktable.error.message']);
1127			}
1128		});
1129		return this ;
1130	},
1131
1132	/**
1133	 * Initialise les données du tableau
1134	 * @param _data données du tableau
1135	 */
1136	setData:function(_data) {
1137		
1138		// Initialisation des paramètres de l'objet
1139		this.numberOfPages = _data.numberOfPages;
1140		this.page = _data.currentPage;
1141		this.numberOfRecords = _data.numberOfRecords;
1142		this.data = _data.rows;
1143		this.groupByProperties = _data.groupByProperties;
1144
1145		// Génération du tableau, rafraichissement de l'entête
1146		// et des sliders pour le redimensionnement
1147		this._generateContents();
1148		this._refreshFooter();
1149		this.onLoadComplete();
1150		this._recreateSliders();
1151	},
1152	
1153	/**
1154	 * Code appelé une fois le chargement de données terminé
1155	 */
1156	onLoadComplete:function() {
1157		
1158	},	
1159	
1160	/**
1161	 * Permet de se rendre à une page donnée
1162	 * 
1163	 * @param newPage
1164	 */
1165	goToPage : function(newPage) {
1166		this.page = newPage;
1167		this.loadData();
1168	},
1169	
1170	/**
1171	 * Permet de montrer le tableau dans la page s'il a été caché
1172	 */
1173	show : function(){
1174		this.$el.show() ;
1175		if (this.hasFooter) {
1176			$('#' + this.tableId + '_footer').show() ;
1177		}
1178		$('.aktable-resize-drag-handle').show() ;
1179		this._recreateSliders();
1180		return this ;
1181	},
1182	
1183	/**
1184	 * Permet de cacher le tableau dans la page
1185	 */
1186	hide : function(){
1187		this.$el.hide() ;
1188		if (this.hasFooter) {
1189			$('#' + this.tableId + '_footer').hide() ;
1190		}
1191		$('.aktable-resize-drag-handle').hide() ;
1192		return this ;
1193	},
1194
1195	/**
1196	 * Permet de cacher une colonne
1197	 * @param propertyName
1198	 */
1199	hideColumn : function(propertyName) {
1200		for ( var colIndex = 0; colIndex < this.columns.length; colIndex++) {
1201			var column = this.columns[colIndex];
1202			if (column.property == propertyName) {
1203				column.visible = false;
1204				this._updateAfterVisibilityChange(true);
1205				return;
1206			}
1207		}
1208	},
1209	
1210	_updateAfterVisibilityChange:function(goToPage) {
1211		var selection = [];
1212		$.each(this.originalColumns,function(key,column) {
1213			if (column.visible) {
1214				selection.push(column.property);
1215			}
1216		});
1217		this._reorganize(this, selection,goToPage);
1218	},	
1219	
1220	/**
1221	 * Permet de cacher une colonne
1222	 * @param propertyName
1223	 */
1224	showColumn : function(propertyName) {
1225		for ( var colIndex = 0; colIndex < this.columns.length; colIndex++) {
1226			var column = this.columns[colIndex];
1227			if (column.property == propertyName) {
1228				column.visible = true;
1229				this._updateAfterVisibilityChange(true);
1230				return;
1231			}
1232		}		
1233	}
1234	
1235
1236});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.