/* $Id: raffinement.js,v 1.22 2008-10-23 20:11:40 christian Exp $
Prototype for refining/sorting table rows using a reference JS object
built server-side and reflecting the rows data to sort/refine
Required: the 6 columnIds strings in that order => ['id', 'nom', 'prenom', 'firme', 'expertise', 'disciplinaires']
'expertise','disciplinaires' => empty values must be defined and set to null */
ECI_raffinement.prototype.appendElementNode = function(parent, el, attr, style, text)
{ // (DOM) return text appended to el node appended to parent; creates el if it's a string; attr/style are: [name,value,name,value,...];
	if (typeof el == 'string'){el = document.createElement(el)}
	for (var i = 0; i < attr.length; i+=2){el.setAttribute(attr[i],attr[i+1])}
	for (var i = 0; i < style.length; i+=2){eval("el.style."+style[i]+"='"+style[i+1]+"'")}
	if (typeof text == 'string'){el.appendChild(document.createTextNode(text))}
	return parent.appendChild(el);
}
ECI_raffinement.prototype.appendSortArrow = function(container, asc)
{ // (DOM) append arrow 'asc' (boolean) to container
	this.removeArrows();
	var arrow = document.getElementById(container.id + this.arrowIdSuffix);
	arrow = this.appendElementNode(
		container,
		'span',
		['id', container.id + this.arrowIdSuffix],
		['color','white','cursor','pointer'],
		(asc === true ? this.sortStateStrs[0] : this.sortStateStrs[1])
	);
	return arrow;
}
ECI_raffinement.prototype.buildMenu = function(menu, obj)
{ // (DOM/JS) append the menus to the document
	var p = document.createElement('p');
	var strong = document.createElement('strong');
	var br = document.createElement('br');
	if (menu == this.expertiseCol)
	{
		var label = document.createTextNode(this.expertiseLabel);
		strong.appendChild(label);
		var defaultOption = this.buildOption(this.defaultExpDisLabel);
		var select = this.buildOptgroupSelect(obj, menu, defaultOption);
		select.onchange = this.filterExpertise;
	}
	else if (menu == this.disciplinairesCol)
	{
		var label = document.createTextNode(this.disciplinairesLabel);
		strong.appendChild(label);
		var defaultOption = this.buildOption(this.defaultExpDisLabel);
		var select = this.buildOptionSelect(obj, menu, defaultOption);
		select.onchange = this.filterDisciplinaires;
	}
	select.selectedIndex = 0;
	select.blur();
	p.appendChild(strong);
	p.appendChild(br);
	p.appendChild(select);
	document.getElementById(this.refineMenuParentId).appendChild(p);
}
ECI_raffinement.prototype.buildMenus = function(obj, expertise, disciplinaires)
{ // (DOM/JS) empty menu container, check if a menu will have options, and if so create it
	// first let's empty the menu container
	this.extractElementsByTagName(document.getElementById(this.refineMenuParentId), false);
	// build menu ?
	if (this.crumbs == 2 || this.currentObj.length < 2)
	{
		return;
	}
	if (expertise === true)
	{
		expertise = this.hasMenu(obj, this.expertiseCol);
	}
	if (disciplinaires === true)
	{
		disciplinaires = this.hasMenu(obj, this.disciplinairesCol);
	}
	// build
	if (expertise === true)
	{
		this.buildMenu(this.expertiseCol, obj);
	}
	if (disciplinaires === true)
	{
		this.buildMenu(this.disciplinairesCol, obj);
	}
}
ECI_raffinement.prototype.buildOptgroupSelect = function(obj, menu, defaultOption)
{ // (DOM/JS) for the specified menu (with optgroup(s)), create and return a select element
	// create a straight array of the optgroups
	var tree = this.getOptgroups(obj, 'optgroups', menu);
	//console.log(tree);
	// then transform array into a suited object for building the menu
	for (var i = 0; i < tree.length; i++)
	{
		tree[i] = {
			"OPTGROUP" : tree[i],
			"OPTIONS" : []
		};
	}
	// get options in an optgroup
	for (var i = 0; i < tree.length; i++)
	{
		for (var row = 0; row < obj.length; row++)
		{
			// parse "data" object to find duplication(s)
			// and populate tree[i].OPTIONS
			// with second level options
			if (obj[row][menu] == null) { // no menu at all like the noExpertise case
				continue;
			}
			if (typeof obj[row][menu][tree[i].OPTGROUP] == 'object' && obj[row][menu][tree[i].OPTGROUP] !== null)
			{
				// we have options in an optgroup
				for (var option = 0; option < obj[row][menu][tree[i].OPTGROUP].length; option++)
				{
					if (!this.isInArray(tree[i].OPTIONS, obj[row][menu][tree[i].OPTGROUP][option]))
					{
						tree[i].OPTIONS[tree[i].OPTIONS.length] = obj[row][menu][tree[i].OPTGROUP][option];
					}
				}
				tree[i].OPTIONS.sort();
			}
		}
	}
	// now get the options that are not in an optgroup
	var firstLevelOptions = this.getOptgroups(obj, 'firstLevelOptions', menu);
	for (var i = 0; i < firstLevelOptions.length; i++)
	{
		tree[tree.length] = {
			"OPTION" : firstLevelOptions[i]
		}
	}
	// add a special option if noExpertise
	var noExpertiseOption = this.getOptgroups(obj, 'noExpertiseOption', menu);
	//console.log(noExpertiseOption);
	if (noExpertiseOption.length > 0) {
		tree[tree.length] = {"OPTION" : this.noExpertise}
	}

	var select = document.createElement('select');
	if (defaultOption)
	{
		select.appendChild(defaultOption);
	}
	// let's put the first level options at beginning of menu
	for (var i = 0; i < tree.length; i++)
	{
		if (String(tree[i].OPTION) != "undefined")
		{
			var value = tree[i].OPTION;
			var text =  this.previewResults ? 
				tree[i].OPTION + ' (' + this.searchOptgroupsMenu([tree[i].OPTION], obj, menu).length + ')' : 
				value;
			//console.log(text);
			var option = this.buildOption(value, text);
			select.appendChild(option);
		}
	}
	// and the optgroups after
	for (var i = 0; i < tree.length; i++)
	{
		if (String(tree[i].OPTGROUP) != "undefined")
		{
			var optgroup = document.createElement('optgroup');
			optgroup.label = tree[i].OPTGROUP;
			for (var j = 0; j < tree[i].OPTIONS.length; j++)
			{
				var value = tree[i].OPTGROUP + this.concatenateStr + tree[i].OPTIONS[j];
				var text = this.previewResults ? tree[i].OPTIONS[j] + ' (' + this.searchOptgroupsMenu(
						[tree[i].OPTGROUP, tree[i].OPTIONS[j]], obj, menu
				).length + ')' : tree[i].OPTIONS[j];
				var option = this.buildOption(value, text);
				optgroup.appendChild(option);
			}
			select.appendChild(optgroup);
		}
	}
	return select;
}
ECI_raffinement.prototype.buildOption = function(value, text)
{ // (DOM) create and return an option element
	if (String(text)=="undefined") { text = value; }
	var option = document.createElement('option');
	var txt = document.createTextNode(text);
	option.value = value;
	option.appendChild(txt);
	return option;
}
ECI_raffinement.prototype.buildOptionSelect = function(obj, menu, defaultOption)
{ // (DOM/JS) for the specified menu (without optgroup), create and return a select element
	var select = document.createElement('select');
	if (defaultOption)
	{
		select.appendChild(defaultOption);
	}
	var tree = this.getOptions(obj, menu);
	for (var i = 0; i < tree.length; i++)
	{
		var value = tree[i];
		var text =  this.previewResults ? 
			tree[i] + ' (' + this.searchOptionsMenu(tree[i] , obj, menu).length + ')' :
			value;
		var text = 
		select.appendChild(this.buildOption(value, text));
	}
	return select;
}
/*
ECI_raffinement.prototype.buildTable = function(container, obj)
{ // (DOM) return a JS array of TR nodes and use obj as the reference for TDs content
	var rows = [];
	for (var i = 0; i < obj.length; i++)
	{
		rows[i] = document.createElement('tr');
		rows[i].id = obj[i].id;
		var tds = [];
		while (tds.length < 3)
		{
			with (tds[tds.length] = document.createElement('td'))
			{
				appendChild(this.getCellContent(obj[i], tds.length));
				rows[i].appendChild(tds[tds.length-1]);
			}
		}		
		// pour la loupe (temporaire)
		with (tds[tds.length] = document.createElement('td'))
		{
			innerHTML = '<a href="/publicsspec/references/firmestravspec/fiche.php"><img src="http://maddesign.dev.espacecourbe.com/images/publicsspec/outils/voir_on.gif" border="0" alt="Voir" /></a>';
			rows[i].appendChild(tds[tds.length-1]);
		}
	}
	return rows;
}
*/
ECI_raffinement.prototype.compareValues = function(v1, v2)
{ // from: http://brainjar.com/dhtml/tablesort/
	// If the values are numeric, convert them to floats.
	var f1 = parseFloat(v1);
	var f2 = parseFloat(v2);
	if (!isNaN(f1) && !isNaN(f2))
	{
		v1 = f1;
		v2 = f2;
	}
	// Compare the two values.
	if (v1 == v2) return 0;
	if (v1 > v2) return 1
	return -1;
}
ECI_raffinement.prototype.displayObject = function(obj)
{ // (JS) utility method to display JS object in a human readable form

	var message = "";
	for (var a in obj)
	{
		message += (a + " => " + obj[a] + "\n");
		if (typeof obj[a] != 'object') continue;
		for (var c in obj[a])
		{
			message += ("\t" + c + " => " + obj[a][c] + "\n");
			if (typeof obj[a][c] != 'object') continue;
			for (var d in obj[a][c])
			{
				message += ("\t\t" + d + " => " + obj[a][c][d] + "\n");
				if (typeof obj[a][c][d] != 'object') continue;
				for (var e in obj[a][c][d])
				{
					message += ("\t\t\t" + e + " => " + obj[a][c][d][e] + "\n");
				}
			}
		}
	}
	var b = window.open();
	b.document.open();
	b.document.write('<textarea wrap="off" style="width:100%;height:600px">'+message+'</textarea>');
	b.document.close();
}
ECI_raffinement.prototype.displaySearch = function(container, ids, depth, optionValue, menu)
{ // (DOM) called from onchange event handler and onclick crumb1 event handler
	var expertise = (menu == this.expertiseCol ? false : true);
	var disciplinaires = (menu == this.disciplinairesCol ? false : true);
	this.extractElementsByTagName(container, 'tr');
	this.putRows(container, ids, this.initialRows);
	this.buildMenus(this.currentObj, expertise, disciplinaires);
	this.updateBreadCrumbs(optionValue, depth, menu);
	this.sortTable(
		this.rowsParentId,
		this.lastColSorted,
		false, // start reverse order?
		true // resume
	);
}
ECI_raffinement.prototype.extractElementsByTagName = function(parent, el)
{ // (DOM) extractElementsByTagName : empty parent node and return array of "el" children elements
	if (parent.normalize) parent.normalize();
	var els = [];
	var i = 0;
	while ((el !== false) && parent.getElementsByTagName(el)[i])
	{
		if (parent.getElementsByTagName(el)[i].parentNode === parent)
		{ // we have a first level child 'el'
			els[els.length] = parent.removeChild(parent.getElementsByTagName(el)[i]);
		}
		else
		{ // not a first level child, check next 'el' in 'parent'
			i++;
		}
	}
	// delete all eventuel other child nodes left by "normalize" and the previous loop
	while (parent.hasChildNodes())
	{
		parent.removeChild(parent.firstChild);
	}
	return els;
}
ECI_raffinement.prototype.filterDisciplinaires = function(event, depth)
{ // (DOM/JS) "onchange" event handler for "disciplinaires" menu
	var instance = eval(window.ECI_raffinement_instanceName);
	var container = document.getElementById(instance.rowsParentId);
	if (typeof depth == "undefined")
	{
		depth = ++instance.crumbs;
	}
	//if (this.value == instance.defaultExpDisLabel){instance.init(false);return;}
	var ids = instance.searchOptionsMenu(
		this.value,
		instance.currentObj,
		instance.disciplinairesCol
	);
	instance.displaySearch(container, ids, depth, this.value, instance.disciplinairesCol);
}
ECI_raffinement.prototype.filterExpertise = function(event, depth)
{ // (DOM/JS) "onchange" event handler for "expertise" menu
	var instance = eval(window.ECI_raffinement_instanceName);
	var container = document.getElementById(instance.rowsParentId);
	if (typeof depth == "undefined")
	{
		depth = ++instance.crumbs;
	}
	instance.extractElementsByTagName(container, 'tr');
	//if (this.value == instance.defaultExpDisLabel){instance.init(false);return;}
	var ids = instance.searchOptgroupsMenu(
		this.value.split(instance.concatenateStr),
		instance.currentObj,
		instance.expertiseCol
	);
	instance.displaySearch(container, ids, depth, this.value, instance.expertiseCol);
}
/*
ECI_raffinement.prototype.getCellContent = function(row, col)
{ // (JS/DOM) return a textNode
	var out;
	switch (col)
	{
		// Nom col
		case 1 : out = function(t){return row[t.nomCol]?row[t.nomCol]:' '}; break;
		// Prénom col
		case 2 : out = function(t){return row[t.prenomCol]?row[t.prenomCol]:' '}; break;
		// Firme col
		case 3 : out = function(t){return row[t.firmeCol]?row[t.firmeCol]:' '}; break;
		// Expertise col
		case 4 : { out = function(t) { return (
			t.getOptgroups([row],'firstLevelOptions',t.expertiseCol).concat(
				t.getOptgroups([row],'secondLevelOptions',t.expertiseCol),
				t.getOptions([row],t.disciplinairesCol)
			)
		)}}
	}
	return document.createTextNode(String(out(this)));
}
*/
ECI_raffinement.prototype.getIds = function(obj)
{ // (DOM/JS) return [ids] of rows in obj
	var ids = [];
	for (var i = 0; i < obj.length; i++)
	{
		ids[ids.length] = obj[i].id;
	}
	return ids;
}
ECI_raffinement.prototype.getOptgroups = function(obj, what, menu)
{ // (JS) return [options] from a menu "menu" that have optgroup(s)
	var optgroups = [];
	var firstLevelOptions = [];
	var secondLevelOptions = [];
	var noExpertiseOption = [];
	var out;
	for (var row = 0; row < obj.length; row++)
	{
		if (obj[row][menu] === null && noExpertiseOption.length == 0) {
			//console.log(this.noExpertise);
			noExpertiseOption[0] = this.noExpertise;
		}
		for (var opt in obj[row][menu])
		{
			var option = obj[row][menu][opt];
			if (option === null)
			{ // this is an option not in an optgroup
				if (!this.isInArray(firstLevelOptions, opt))
				{
					firstLevelOptions[firstLevelOptions.length] = opt;
				}
			}
			else if (!this.isInArray(optgroups, opt))
			{
				optgroups[optgroups.length] = opt;
				secondLevelOptions[secondLevelOptions.length] = option;
			}
		}
	}
	switch (what)
	{
		case 'optgroups' : out = optgroups; break;
		case 'firstLevelOptions' : out = firstLevelOptions; break;
		case 'secondLevelOptions' : out = secondLevelOptions; break;
		case 'noExpertiseOption' : out = noExpertiseOption; break;
	}
	return out.sort();
}
ECI_raffinement.prototype.getOptions = function(obj, menu)
{ // (JS) return [options] from a menu that does not have optgroup
	var $options = []; // "options" is a DOM reserved keyword, so dollar prefix used
	for (var row = 0; row < obj.length; row++)
	{
		if (obj[row][menu] === null) { continue; }
		for (var option = 0; option < obj[row][menu].length; option++)
		{
			if (!this.isInArray($options, obj[row][menu][option]))
			{
				$options[$options.length] = obj[row][menu][option];
			}
		}
	}
	return $options.sort();
}
ECI_raffinement.prototype.getRowsById = function(container, ids)
{ // (DOM/JS) return objects with [ids] in container (getElementById can't be used)
	var rows = [];
	for (var i = 0; i < ids.length; i++)
	{
		for (var row = 0; row < container.length; row++)
		{
			if (ids[i] == container[row].id)
			{
				rows[rows.length] = container[row];
			}
		}
	}
	return rows;
}
ECI_raffinement.prototype.getTextValue = function(el)
{ // from: http://brainjar.com/dhtml/tablesort/
	// Find and concatenate the values of all text nodes contained within the element.
	var s = "";
	// This code is necessary for browsers that don't reflect the DOM constants (like IE)
	if (document.ELEMENT_NODE == null)
	{
		document.ELEMENT_NODE = 1;
		document.TEXT_NODE = 3;
	}
	for (var i = 0; i < el.childNodes.length; i++)
	{
		if (el.childNodes[i].nodeType == document.TEXT_NODE)
		{
			s += el.childNodes[i].nodeValue;
		}
		else if (
			el.childNodes[i].nodeType == document.ELEMENT_NODE && 
			el.childNodes[i].tagName.toUpperCase() == "BR"
		)
		{
			s += " ";
		}
		else
		{
			// Use recursion to get text within sub-elements.
			s += this.getTextValue(el.childNodes[i]);
		}
	}
	s = this.removeAccents(s);
	return this.normalizeString(s);
}
ECI_raffinement.prototype.hasMenu = function(obj, menu)
{ // (JS) return true if (menu!==null)
	for (var row = 0; row < obj.length; row++)
	{
		if (obj[row][menu] !== null)
		{
			return true;
		}
	}
	return false;
}
ECI_raffinement.prototype.init = function(buildTable)
{ // to start all over again (doesn't keep sort state)
	// save currentObj
	this.currentObj = this.initialObj;
	// save all ids
	this.initialIds = this.getIds(this.initialObj);
	document.getElementById(this.totalId).innerHTML = this.initialIds.length;
	// save the original TBODY content
	if (buildTable && typeof this.initialRows == 'undefined')
	{
		this.initialRows = this.buildTable(document.getElementById(this.rowsParentId), this.initialObj);
	}
	else if (typeof this.initialRows == 'undefined')
	{
		this.initialRows = this.extractElementsByTagName(document.getElementById(this.rowsParentId), 'tr');
	}
	// fill TBODY with all rows
	// note: putRows updates this.currentObj
	this.putRows(document.getElementById(this.rowsParentId), this.initialIds, this.initialRows);
	// reset breadcrumbs
	this.crumbs = 0;
	this.updateBreadCrumbs(null, 0, null);
	// build menus from initial Obj
	this.buildMenus(this.initialObj, true, true);
	if (typeof this.lastColSorted != 'undefined')
	{
		this.sortTable(
			this.rowsParentId,
			this.lastColSorted,
			false, // start reverse order?
			true // force resume (don't reverse sort direction)
		);		
	}
}
ECI_raffinement.prototype.initSort = function()
{ // (DOM) attach click event handler to sortable THs and sort first TH asc
	for (var i = 0; i < this.sortableCols.length; i++)
	{
		var th = document.getElementById(this.sortableCols[i]);
		th.style.cursor = 'pointer';
		th.onclick = function()
		{
			var instance = eval(window.ECI_raffinement_instanceName);
			switch (this.id)
			{ // find col index to sort
				case instance.sortableCols[0] : col = 0; break;
				case instance.sortableCols[1] : col = 1; break;
				case instance.sortableCols[2] : col = 2; break;
			}
			instance.sortTable(
				instance.rowsParentId,
				col,
				false, // start reverse order?
				false // force resume (don't reverse sort direction)
			);
			instance.appendSortArrow(
				this,
				!instance.reverseSort[col]
			);
			try{document.body.focus()}catch(e){}
		}
		th.onmouseover = function()
		{
			this.style.backgroundColor = '#CC6600';
		}
		th.onmouseout = function()
		{
			this.style.backgroundColor = '#666600';
		}
	}
	// initially, sort first col ascending
	this.lastColSorted = 0;
	this.sortTable(
		this.rowsParentId,
		this.lastColSorted,
		false, // start reverse order?
		true // force resume (don't reverse sort direction)
	);
}
ECI_raffinement.prototype.isInArray = function(arr, val)
{ // (JS/DOM) return true if "val" is already in [arr]
	for (var i = 0; i < arr.length; i++)
	{
		if (arr[i] == val) return true;
	}
	return false;
}
ECI_raffinement.prototype.normalizeString = function(s)
{ // from: http://brainjar.com/dhtml/tablesort/
	// Regular expressions for normalizing white space.
	var whtSpEnds = new RegExp("^\\s*|\\s*$", "g");
	var whtSpMult = new RegExp("\\s\\s+", "g");
	s = s.replace(whtSpMult, " ");  // Collapse any multiple whites space.
	s = s.replace(whtSpEnds, "");   // Remove leading or trailing white space.
	return s;
}
ECI_raffinement.prototype.putRows = function(container, ids, rows)
{ // (DOM/JS) append [rows] with [ids] to container; [rows] is a JS array of nodes elements
	this.currentObj = this.getRowsById(this.initialObj, ids);
	for (var i = 0; i < ids.length; i++)
	{
		container.appendChild(
			this.getRowsById(
				rows,
				[this.currentObj[i].id]
			)[0]
		);
	}
}
ECI_raffinement.prototype.removeAccents = function(msg)
{// from: http://www.google.com/codesearch?hl=en&q=show:zJnSRgiuax0:AaxG-t1rqyQ:cULI5NyXMkg&sa=N&ct=rd&cs_p=https://desafionetbeans.dev.java.net/plug-ins/prototyper.zip&cs_f=Prototyper/src/resources/layout/to_copy/js/generic.js	
	msg = msg.replace(/[áàâãä]/g, "a");
	msg = msg.replace(/[éèêë]/g, "e");
	msg = msg.replace(/[íìîï]/g, "i");
	msg = msg.replace(/[óòôõö]/g, "o");
	msg = msg.replace(/[úùûü]/g, "u");
	msg = msg.replace(/[ýÿ]/g, "y");
	msg = msg.replace(/[ç]/g, "c");
	msg = msg.replace(/[ñ]/g, "n");

	msg = msg.replace(/[ÁÀÂÃÄ]/g, "A");
	msg = msg.replace(/[ÉÈÊË]/g, "E");
	msg = msg.replace(/[ÍÌÎÏ]/g, "I");
	msg = msg.replace(/[ÓÒÔÕÖ]/g, "O");
	msg = msg.replace(/[ÚÙÛÜ]/g, "U");
	msg = msg.replace(/[Ý]/g, "Y");
	msg = msg.replace(/[Ç]/g, "C");
	msg = msg.replace(/[Ñ]/g, "N");
	return msg;
}
ECI_raffinement.prototype.removeArrows = function()
{ // (DOM) remove arrows from all THs
	for (var i = 0; i < this.sortableCols.length; i++)
	{
		var th = document.getElementById(this.sortableCols[i]);
		var arrow = document.getElementById(this.sortableCols[i] + this.arrowIdSuffix);
		if (!th || !arrow) continue;
		while (arrow.parentNode && arrow.parentNode == th)
		{
			th.removeChild(arrow);
		}
	}
}
ECI_raffinement.prototype.searchOptgroupsMenu = function(find, obj, menu)
{ // (JS) return [arr] of rows' id with [find] or [optgroup,find] in menu
	var ids = [];
	for (var row = 0; row < obj.length; row++)
	{
		// if the menu is set to null and if it is the expertise menu, we got a noExpertiseOption
		if (typeof obj[row][menu] == 'object' && obj[row][menu] === null && find[0] == this.noExpertise) {
			//console.log(find);
			ids[ids.length] = obj[row].id;
			continue;
		}
		// if the menu is set to null, continue to next row
		if (typeof obj[row][menu] == 'object' && obj[row][menu] === null) continue;
		// if it's a first level option, it is set to null, and find has a length of 1
		if (
			find.length == 1 &&
			(typeof obj[row][menu][find[0]] == 'object' && obj[row][menu][find[0]] === null) &&
			!this.isInArray(ids, obj[row].id)
		)
		{
			ids[ids.length] = obj[row].id;
			// we got what we want from this row, continue to next one
			continue;
		}
		for (var optgroup in obj[row][menu])
		{
			var $options = obj[row][menu][optgroup];
			// if it's a first level option, it is set to null, continue to next row
			if ($options === null) { continue; }
			for (var j = 0; (j<$options.length && find.length==2); j++)
			{
				if (
					optgroup == find[0] &&
					$options[j] == find[1] &&
					!this.isInArray(ids, obj[row].id)

				)
				{
					ids[ids.length] = obj[row].id;
				}
			}
		}
	}
	return ids;
}
ECI_raffinement.prototype.searchOptionsMenu = function(find, obj, menu)
{ // (JS) return [arr] of rows' id with "find" in menu
	var ids = [];
	for (var row = 0; row < obj.length; row++)
	{
		// if the menu is set to null, continue to next row
		if (typeof obj[row][menu] == 'object' && obj[row][menu] === null) continue;
		for (var option = 0; option < obj[row][menu].length; option++)
		{
			if (
				typeof obj[row][menu][option] == 'string' &&
				obj[row][menu][option] == find &&
				!this.isInArray(ids, obj[row].id)
			)
			{
				ids[ids.length] = obj[row].id;
			}
		}
	}
	return ids;
}
ECI_raffinement.prototype.sortTable = function(id, col, rev, resume)
{ // from: http://brainjar.com/dhtml/tablesort/

	//  id  - ID of the TBODY element to be sorted.
	//  col - Index of the column to sort, 0 = first column, 1 = second column,
	//  rev - If true, the column is sorted in reverse (descending) order initially.
	//  resume - restore previous sort

	var tbody = document.getElementById(id);
	// The first time this function is called for a given tbody, set up an
	// array of reverse sort flags.
	if (typeof this.reverseSort == 'undefined') { this.reverseSort = []; }
	// If this column has not been sorted before, set the initial sort direction.
	if (typeof this.reverseSort[col] == 'undefined') { this.reverseSort[col] = rev; }
	// If this column was the last one sorted, reverse its sort direction.
	if (col == this.lastColSorted && !resume) { this.reverseSort[col] = !this.reverseSort[col]; }
	// Remember this column as the last one sorted.
	this.lastColSorted = col;
	// Set the tbody display style to "none" - necessary for Netscape 6 
	var oldDsply = tbody.style.display;
	tbody.style.display = "none";
	// Sort the rows based on the content of the specified column using a selection sort algorithm
	for (var i = 0; i < tbody.rows.length - 1; i++)
	{
		// Assume the current row has the minimum value.
		var minIdx = i;
		var minVal = this.getTextValue(tbody.rows[i].cells[col]);
		// Search the rows that follow the current one for a smaller value.
		for (var j = i + 1; j < tbody.rows.length; j++)
		{
			var testVal = this.getTextValue(tbody.rows[j].cells[col]);
			var cmp = this.compareValues(minVal, testVal);
			// Negate the comparison result if the reverse sort flag is set.
			if (this.reverseSort[col]) { cmp = -cmp; }
			// Sort by the second column (team name) if those values are equal.
			if (cmp == 0 && col != 1)
			{
				cmp = this.compareValues(
					this.getTextValue(tbody.rows[minIdx].cells[1]),
					this.getTextValue(tbody.rows[j].cells[1])
				);
			}
			// If this row has a smaller value than the current minimum, remember its
			// position and update the current minimum value.
			if (cmp > 0)
			{
				minIdx = j;
				minVal = testVal;
			}
		}
		// By now, we have the row with the smallest value. Remove it from the
		// tbody and insert it before the current row.
		if (minIdx > i)
		{
			var tmpEl = tbody.removeChild(tbody.rows[minIdx]);
			tbody.insertBefore(tmpEl, tbody.rows[i]);
		}
	}
	// Restore the table's display style.
	tbody.style.display = oldDsply;
	this.appendSortArrow(
		document.getElementById(this.sortableCols[col]),
		!this.reverseSort[col]
	);
}
ECI_raffinement.prototype.updateBreadCrumbs = function(optionValue, depth, menu)
{ // (DOM) change breadcrumbs as user filters results
	var crumb = (optionValue !== null && (
		optionValue.split(this.concatenateStr).length == 2 ?
		optionValue.split(this.concatenateStr)[1] :
		optionValue
	));
	var rootCrumb = document.getElementById(this.rootCrumbId);
	var crumb1 = document.getElementById(this.crumb1Id);
	var crumb2 = document.getElementById(this.crumb2Id);
	this.updateCounter();
	if (depth == 0)
	{
		rootCrumb.removeAttribute('href');
		rootCrumb.style.color = '';
		this.extractElementsByTagName(crumb1, false);
		this.extractElementsByTagName(crumb2, false);
	}
	else if (depth == 1)
	{
		rootCrumb.setAttribute('href','#');
		rootCrumb.style.color = 'blue';
		this.extractElementsByTagName(crumb1, false);
		this.extractElementsByTagName(crumb2, false);
		this.appendElementNode(crumb1, 'span', [], [], ' > ');
		var crumbElement = this.appendElementNode(crumb1, 'a', [], ['fontWeight','bold'], crumb);
		//crumbElement.crumbObj = this.currentObj;
		crumbElement.menu = menu;
		crumbElement.optionValue = optionValue;
		crumbElement.onclick = function()
		{
			var instance = eval(window.ECI_raffinement_instanceName);
			var container = document.getElementById(instance.rowsParentId);
			instance.crumbs = 1;
			instance.currentObj = instance.initialObj;
			if (this.menu == instance.expertiseCol)
			{
				var ids = instance.searchOptgroupsMenu(
					this.optionValue.split(instance.concatenateStr),
					instance.currentObj,
					instance.expertiseCol
				);
			}
			else if (this.menu == instance.disciplinairesCol)
			{
				var ids = instance.searchOptionsMenu(
					this.optionValue,
					instance.currentObj,
					instance.disciplinairesCol
				);
			}
			instance.displaySearch(container, ids, instance.crumbs, this.optionValue, this.menu);
			return false;
		}
	}
	else if (depth == 2)
	{
		rootCrumb.setAttribute('href','#');
		rootCrumb.style.color = 'blue';
		crumb1.getElementsByTagName('a')[0].setAttribute('href','#');
		crumb1.getElementsByTagName('a')[0].style.color = 'blue';
		this.extractElementsByTagName(crumb2, false);
		this.appendElementNode(crumb2, 'span', [], [], ' > ');
		this.appendElementNode(crumb2, 'a', [], ['fontWeight','bold'], crumb);
	}
}
ECI_raffinement.prototype.updateCounter = function()
{ // (DOM) set the displayed number to currentObj.length
	var rowsNumber = this.currentObj.length;
	var rowsNumberContainer = document.getElementById(this.counterId);
	this.extractElementsByTagName(rowsNumberContainer, false);
	rowsNumberContainer.appendChild(document.createTextNode(rowsNumber));
}
function ECI_raffinement(
	instanceName,
	initialObj,

	rowsParentId,
	refineMenuParentId,

	columnIds,

	expertiseLabel,
	disciplinairesLabel,
	defaultExpDisLabel,

	concatenateStr,
	counterId,
	totalId,
	rootCrumbId,
	crumb1Id,
	crumb2Id,
	sortStateStrs,
	sortableCols,

	previewResults,
	noExpertise
)
{
	// For accessing ECI_raffinement methods from event handler.
	// This is handy but allows for only one instance of ECI_raffinement.
	// For many instances, use YUI to register event handler.
	// http://developer.yahoo.com/yui/event/
	window.ECI_raffinement_instanceName = instanceName;

	// reference JS object
	this.initialObj = initialObj;

	// id of the THEAD, TFOOT or TBODY in which are the TR to refine/sort
	this.rowsParentId = rowsParentId;
	// id of the container to append the refine menu
	this.refineMenuParentId = refineMenuParentId;

	this.idCol = columnIds[0];
	this.nomCol = columnIds[1];
	this.prenomCol = columnIds[2];
	this.firmeCol =  columnIds[3];
	this.expertiseCol = columnIds[4];
	this.disciplinairesCol = columnIds[5];

	this.expertiseLabel = expertiseLabel;
	this.disciplinairesLabel = disciplinairesLabel;
	this.defaultExpDisLabel = defaultExpDisLabel;

	this.concatenateStr = concatenateStr;
	this.counterId = counterId;
	this.totalId = totalId;
	this.rootCrumbId = rootCrumbId;
	this.crumb1Id = crumb1Id;
	this.crumb2Id = crumb2Id;
	this.sortStateStrs = sortStateStrs;
	this.sortableCols = sortableCols;

	this.previewResults = previewResults || false;
	this.noExpertise = noExpertise;

	this.arrowIdSuffix = '-sort-arrow';

	this.init(false);
	this.initSort();
}

