/******************************************************************************
 *
 *                   INDIGEN SOLUTIONS CODE PROPERTY
 *       The present javascript code is property of Indigen Solutions. This 
 *     code can only be used inside Internet/Intranet web sites located on 
 *  *web servers*, as the outcome of a licensed Indigen Solutions application 
 *  only. Any unauthorized use, reverse-engineering, alteration, transmission, 
 * transformation, facsimile, or copying of any means (electronic or not) is 
 *     strictly prohibited and will be prosecuted. Removal of the present 
 *              copyright notice is strictly prohibited
 *         Copyright (c) 2004 Indigen Solutions. All Rights Reserved.
 *
 * RCS Id                       $Id: istruct.js,v 1.13 2006/07/24 09:21:26 indigen Exp $
 * 
 ******************************************************************************/

/**
* @fileoverview This file contains the implementation of the IStruct class
* @author mig
* @version 1.0
*/

/**
* Construct a default IType instance
* @param {object} formal the description of how actual data can be edited. The
* definition and use of formal is left to classes extending <code>IType</code>
* @class The <code>IType</code> defines a data type and related methods for
* get, setting, editing and validating the data
*/
function IType(formal) {
	this.formal = formal;
	if(formal) {
		if(formal.defaultValue)
			this.defaultData = formal.defaultValue;
	}
	this.setData = IType.prototype.setData;
	this.setDataField = IType.prototype.setDataField;
	this.edit = IType.prototype.edit;
	this.updateData = IType.prototype.updateData;
	this.canDirectData = IType.prototype.canDirectData;
	this.validate = IType.prototype.validate;
	this.getDefaultData = IType.prototype.getDefaultData;
	this.draw = IType.prototype.draw;
	this.displayLabel = IType.prototype.displayLabel;
	this.start = IType.prototype.start;
	this.startBlinking = IType.prototype.startBlinking;
	this.endBlinking = IType.prototype.endBlinking;
	this.getParentNode = IType.prototype.getParentNode;
	this.notify = IType.prototype.notify;
	this.setClass = IType.prototype.setClass;
}

/**
* Set the data to be managed directly by the object
* @param {any} data any data representing the actual to be managed by the IType object
*/
IType.prototype.setData = function(data) {
		this.directData = data;
		this.isDirectData = true;
}

/**
* Set the data to be managed indirectly by the object.
* @param {Object_or_Array} base the data containing the actual data to be managed by the object
* @param {String_or_int} field the name of the object member or index in the array to access the actual data
*/
IType.prototype.setDataField = function(base, field) {
		this.baseData = base;
		this.dataField = field;
		this.isDirectData = false;
}

/**
* Modify the DOM to generate an editor for the data. This method is to be overwritten in sub-classes.
* @param {Node} parentNode the HTML element where to attach the editor
*/
IType.prototype.edit = function(parentNode) {
	alert("Method IType.edit must be overwritten");
}

/**
* Update actual data with the edited values. This method is to be overwritten in sub-classes.
*/
IType.prototype.updateData = function() {
		alert("Method IType.updateData must be overwritten");
}

IType.prototype.updateStaticData = function() {
	if(this.formal && this.formal.staticData && this.formal.staticData[0]) {
		for(var i=0;i<this.formal.staticData.length;i++) {
			var data = this.formal.staticData[i];
			this.directData[data.field] = data.value;
		}
	}
}

/**
* Determine whether the managed data can be modified directly by the <code>IType</code> instance or
* if the actual data is accessed from its container.
* @return boolean <code>true</code> if actual data is modified directly, <code>false</code> otherwise
*/
IType.prototype.canDirectData = function() {
		return true;
}

/**
* Check whether the current edited data is valid, displaying warnings if necessary
* @return {boolean} <code>true</code> if data is valid, <code>false</code> otherwise
*/
IType.prototype.validate = function() {
		return true;
}

/**
* Return the default data for this object
* @return {any} the default empty data for the object
*/
IType.prototype.getDefaultData = function() {
		var value = null;
		if ( this.defaultData )
		  eval("value = "+this.defaultData);
		return value;
}

/**
* Returns the IType parent node
* @return {Node} the object parent node or null if not found
*/
IType.prototype.getParentNode = function() {
	return this.doc.getElementById(this.parentNodeId);
}

/**
* Highlight the object and display the given message in an alert dialog.
* Once user click the dialog button, the initial state is restored
* @param {string} message the message to be displayed in an alert dialog
*/
IType.prototype.notify = function(message) {
	var pn = this.getParentNode();
	var bc = pn.style.backgroundColor;
	pn.style.backgroundColor = "Red";
	alert(message);
	pn.style.backgroundColor = bc;
}

/**
* Utility to set the class attribute of a DOM node.
* The method takes a variable number of arguments, additional arguments
* go by pair
* @param {Node} the node to set the class attribute
* @param {string} formalField the name of the class fied in the formal
* @param {string} defValue the class value if the formal field is not set
*/
IType.prototype.setClass = function(node) {
	var args = this.setClass.arguments;
	var clazz=""
	for(var i=1;i<args.length;i+=2) {
		if(i>1)
			clazz+=" ";
		if(this.formal && this.formal[args[i]]) 
			clazz+=this.formal[args[i]];
		else
			clazz+=args[i+1];
	}
	node.className = clazz;
}

/**
* Stores parent node and document and call {@link #edit} method
* @param {Node} parentNode the DOM object to attach the IType object to.
*/
IType.prototype.draw = function(parentNode) {
	this.doc = parentNode.ownerDocument;
	this.parentNode = parentNode;
	this.parentNodeId = parentNode.getAttribute("id");
	if(this.parentNodeId==null || this.parentNodeId=="") {
		this.parentNodeId = "itype-"+IStruct.getId();
		parentNode.setAttribute("id",this.parentNodeId);
	}
	this.edit(parentNode);
}

IType.prototype.displayLabel = function() {
  return true;
};

/**
* Call {@link #draw} method and refresh the display. This is the method to be 
* called by applications to display the editor.
* @param {Node} parentNode the DOM object to attach the IType object to.
*/
IType.prototype.start = function(parentNode) {
	this.draw(parentNode);
}

/**
* Global to remember which object is blinking
*/
IType.blinkingIType = null;

/**
* Request the object to startblinking
*/
IType.prototype.startBlinking = function() {
	this.originalBkgColor = this.parentNode.style.backgroundColor;
	this.originalPadding = this.parentNode.style.padding;
	this.originalBorder = this.parentNode.style.border;
	IType.blinkingIType = this;
	IType.blink(0);
}

/**
* This method is called when blinking is over. If the <code>message</code>
* object member is set, it is displayed in an alert dialog.
*/
IType.prototype.endBlinking = function() {
	this.parentNode.style.backgroundColor = this.originalBkgColor;
	this.parentNode.style.padding = this.originalPadding;
	this.parentNode.style.border = this.originalBorder;
	if(this.message!=null)
		alert(this.message);
}

/**
* Performs the actual blinking
* @param {int} step blink count to avoid blinking for ever
*/
IType.blink = function(step) {
	step = parseInt(step);
	if(step>7) {
		IType.blinkingIType.endBlinking();
		return;
	}
	if((step%2)==0) {
		IType.blinkingIType.parentNode.style.backgroundColor = "Red"; 
		IType.blinkingIType.parentNode.style.padding = "5px";
	} else {
		IType.blinkingIType.parentNode.style.backgroundColor = "White"; 
		IType.blinkingIType.parentNode.style.border = "5px solid White"; 
	}
	setTimeout("IType.blink("+(step+1)+")",250);
}

/**
* Construct a new IStruct instance
* @param {Object} formals the formal definition of the data. <code>formal</code> is an object with the following 
* members:<ul>
* <li><code>fields</code> an array of object with the following members:<ul>
* <li><code>field</code> the name of the member in the actual</li>
* <li><code>label</code> the text to be displayed as title for this member</li>
* <li><code>description</code> help text to explain this member</li>
* <li><code>type</code> the name of the <code>IType</code> sub-class to manage the member actual data</li>
* <li><code>formal</code> optionally, the formal of the <code>IType</code> sub-class to manage the member actual data</li>
* </ul></li>
* <li><code>staticData</code> an array of members and associated values that are automatically added
* to the actual data<ul>
* <li><code>field</code> the name of the member</li>
* <li><code>value</code> the value for the member</li>
* </ul></li>
* <li><code>tableClass</code> the CSS class for IStruct container, default <code>istruct-table</code></li>
* <li><code>labelClass</code> the CSS class for IStruct label container, default <code>ifield-td-label</code></li>
* <li><code>valueClass</code> the CSS class for IStruct value container, default <code>ifield-td-value</code></li>
* </ul>
* @class The <code>IStruct</code> object provides facilities for
* editing data of <code>Object</code> type using given constraints
*/
function IStruct(formals) {
	this.defaultData = "{}";
	this.base = IType;
	this.base(formals);
	this.edit = IStruct.prototype.edit;
	this.updateData = IStruct.prototype.updateData;
	this.validate = IStruct.prototype.validate;
}
	
IStruct.prototype = new IType;

/**
* Update the actual data with content of the editor. This is performed by calling 
* the <code>updateData</code> method of each member.
*/
IStruct.prototype.updateData = function() {
	for(var i=0;i<this.entries.length;i++) {
		var entry = this.entries[i];
		entry.itype.updateData();
	}
	this.updateStaticData();
}

/**
* Display the form for editing the data
* @param {Node} parentNode the DOM node the form is attached to
*/
IStruct.prototype.edit = function(parentNode) {

	this.doc = parentNode.ownerDocument;
	var doc = this.doc;
	
	var table = doc.createElement("table");
	parentNode.appendChild(table);
	table.setAttribute("cellSpacing","0");
	table.setAttribute("cellPadding","0");
	this.setClass(table,"tableClass","istruct-table");

	var tbody = doc.createElement("tbody");
	table.appendChild(tbody);
	
	this.entries = [];

	var globalTr = null;
	if ( this.formal.byCols ) {
	  globalTr = doc.createElement("tr");
	  tbody.appendChild(globalTr);
	}
	
	for(var i=0;i<this.formal.fields.length;i++) {
	        var entry = {};
		this.entries.push(entry);
		if(this.formal==null) {
			alert("Missing formal");
			break;			
		}
		if(this.formal.fields==null) {
			alert("Missing formal fields");
			break;
		}
		var formal = this.formal.fields[i];
		if(formal.field==null) {
			alert("Missing formal field");
			break;
		}
		if(formal.label==null) {
			alert("Missing formal label");
			break;
		}
		if(formal.type==null) {
			alert("Missing formal type");
			break;
		}
		var fieldName = formal.field;

		var tdLabel = doc.createElement("td");
		this.setClass(tdLabel,"labelClass","istruct-td-label");
		var tdValue = doc.createElement("td");
		if ( globalTr )
		  this.setClass(tdValue,"valueClass","istruct-td-globaltr-value");
		else
		  this.setClass(tdValue,"valueClass","istruct-td-value");
		if ( formal.label_html )
		  tdLabel.innerHTML = formal.label;
		else
		  tdLabel.appendChild(doc.createTextNode(formal.label));
		var itype;
		try {
			itype = eval("new "+formal.type+"(formal.formal)");
		} catch(e) {
			alert("! Error instantiating "+formal.type+": "+e);
			break;
		}
		if(itype.canDirectData()) {
			if(this.directData[fieldName] == null) {
				this.directData[fieldName] = itype.getDefaultData();
			}
			itype.setData(this.directData[fieldName]);
		} else {
			itype.setDataField(this.directData, fieldName);
		}
		var tr = null;
		if ( globalTr )
		  tr = globalTr;
		else {
		  tr = doc.createElement("tr");
		  tbody.appendChild(tr);
		}
		if(formal.description != null)
		  tr.setAttribute("title",formal.description);
		if ( this.formal.noLabel || !itype.displayLabel() ) {
		  tr.appendChild(tdValue);
		} else {
		  tr.appendChild(tdLabel);
		  tr.appendChild(tdValue);		
		}
		itype.draw(tdValue);
		entry.itype = itype;
		if ( formal.readonly )
		  mapDOMElementsRecursively(tdValue, function(elem) {elem.disabled = true;});
	}
	if ( globalTr ) {
	  var tdWidth = doc.createElement("td");
	  tdWidth.style.width = "100%";
	  tr.appendChild(tdWidth);
	}
}

/**
* Check the availability of the edited data by checking each of its fields
*/
IStruct.prototype.validate = function() {
	for(var i=0;i<this.entries.length;i++) {
		var entry = this.entries[i];
		var v = entry.itype.validate();
		if(v == false)
			return false;
	}
	return true;
}

/**
* Construct a new editor to manage data as a single line string
* @param {object} formals an object describing the behaviour of the editor,
* or <code>null</code> for defaults. If non-null, the formal may the following
* optional members<ul>
* <li><code>size</code> the <code>size</code> attribute of the input</li>
* <li><code>maxlength</code> the <code>maxlength</code> attribute of the input</li>
* <li><code>password</code> if set to <code>true</code> password input is used</li>
* <li><code>nullIfEmpty</code> if set to <code>true</code> the actual field is set to null instead of empty string</li>
* <li><code>removeIfEmpty</code> if set to <code>true</code> the actual field is deleted instead of set to an empty string</li>
* <li><code>reject</code> a list of regular expressions to be rejected,
* with the corresponding error message:<ul>
* <li><code>expr</code> the regular expression to be matched</li>
* <li><code>message</code> the message to be displayed if expression matches</li>
* </ul></li>
* <li><code>expr</code> the regular expression to be matched</li>
* <li><code>message</code> the message to be displayed if value does not match</li>
* <li><code>inputClass</code> the CSS class for the input element, default <code>itextfield-input</code></li>
* </ul>
* @class The <code>ITextField</code> class manages data as a single line string
*/
function ITextField(formals) {
	this.defaultData = "''";
	this.base = IType;
	this.base(formals);
	this.edit = ITextField.prototype.edit;
	this.updateData = ITextField.prototype.updateData;
	this.validate = ITextField.prototype.validate;
	this.canDirectData = ITextField.prototype.canDirectData;
}

ITextField.prototype = new IType;

/**
* Returns <code>false</code> to indicate that this object does not handle
* direct data
*/
ITextField.prototype.canDirectData = function() {
		return false;
}

/**
* Display the string editor
* @param {node} parentNode the HTML node to attach the editor to
*/
ITextField.prototype.edit = function(parentNode) {
	
	this.doc = parentNode.ownerDocument; 
	var doc = this.doc;
	var input = doc.createElement("input");
	if(this.formal && this.formal.password==true)
		input.setAttribute("type","password");
	else
		input.setAttribute("type","text");
	this.setClass(input,"inputClass","itextfield-input");
	if(this.formal) {
		if(this.formal.maxlength)
			input.setAttribute("maxlength",this.formal.maxlength);
		if(this.formal.size)
			input.setAttribute("size",this.formal.size);
	}
	this.inputId = "itextfield-"+IStruct.getId();
	input.setAttribute("id",this.inputId);
	parentNode.appendChild(input);
	if(this.baseData[this.dataField]!=null)
		input.setAttribute("value",this.baseData[this.dataField]);
	else
		input.setAttribute("value",this.getDefaultData());
}

/**
* Update the actual data with the content of the editor
*/
ITextField.prototype.updateData = function() {
	var input = document.getElementById(this.inputId);
	var value = input.value;
	if(value=="") {
		if(this.formal) {
			if(this.formal.nullIfEmpty==true)
				value=null;
			if(this.formal.removeIfEmpty==true) {
				try {
					delete this.baseData[this.dataField];
					return;
				} catch(e) {
				}
			}
		}
	}
	this.baseData[this.dataField] = value; 
}

/**
* Check the validity of the data
*/
ITextField.prototype.validate = function() {
	if(this.formal) {
		var message = "Bad format";
		var input = document.getElementById(this.inputId);	
		var value = input.value;
		if(this.formal.reject) {
			for(var i=0;i<this.formal.reject.length;i++) {
					var rej = this.formal.reject[i];
					if(rej.expr.test(value)) {
						if(rej.message)
							message = rej.message;
						this.notify(message);
						this.doc.getElementById(this.inputId).focus();
						return false;
					}
			}
		}
		if(this.formal.expr) {
			if(this.formal.message)
				message = this.formal.message;

			if(this.formal.expr!=null && this.formal.expr.test(value)==false) {
				this.notify(message);
				this.doc.getElementById(this.inputId).focus();
				return false;
			}
		}
	}
	return true;
}

/**
* Construct a new editor to manage data as a boolean value
* @param {object} formals an object describing the behaviour of the editor,
* or <code>null</code> for defaults. If non-null, the formal may the following
* optional members<ul>
* <li><code>inputClass</code> the CSS class for the input element, default <code>icheckfield-input</code></li>
* </ul>
* @class The <code>ICheckField</code> class manages data as a boolean value
*/
function ICheckField(formals) {
	this.defaultData = "false";
	this.base = IType;
	this.base(formals);
	this.edit = ICheckField.prototype.edit;
	this.updateData = ICheckField.prototype.updateData;
	this.canDirectData = ICheckField.prototype.canDirectData;
}

ICheckField.prototype = new IType;

/**
* Returns <code>false</code> to indicate that this object does not handle
* direct data
*/
ICheckField.prototype.canDirectData = function() {
		return false;
}

/**
* Display the boolean editor
* @param {node} parentNode the HTML node to attach the editor to
*/
ICheckField.prototype.edit = function(parentNode) {
	this.doc = parentNode.ownerDocument;
	var doc = this.doc;
	var input = doc.createElement("input");
	input.setAttribute("type","checkbox");
	this.setClass(input,"inputClass","icheckfield-input");
	this.inputId = "icheckfield-"+IStruct.getId();
	input.setAttribute("id",this.inputId);
	parentNode.appendChild(input);
	var checked = this.getDefaultData() == true;
	if(this.baseData[this.dataField]!=null)
		checked = this.baseData[this.dataField] == true;
	if(checked)
		input.setAttribute("checked","on");
	else
		input.removeAttribute("checked");
}

/**
* Update the actual data with the content of the editor
*/
ICheckField.prototype.updateData = function() {
	var input = document.getElementById(this.inputId);
	this.baseData[this.dataField] = input.checked == true;
}

/**
* Construct a new editor to manage data as a multiline string
* @param {object} formals an object describing the behaviour of the editor,
* or <code>null</code> for defaults. If non-null, in addition to {@link #ITextField}
* formal parameters, the formal may have the following optional members<ul>
* <li><code>rows</code> the <code>rows</code> attribute of the textarea</li>
* <li><code>cols</code> the <code>cols</code> attribute of the textarea</li>
* </ul>
* Note that the following {@link ITextField} formals are invalid for an ITextArea<ul>
* <li><code>size</code></li>
* <li><code>maxlength</code></li>
* <li><code>password</code></li>
* <li><code>inputClass</code> the CSS class for the textarea element, default <code>itextarea-textarea</code></li>
* </ul>
* @class The <code>ITextArea</code> class manages data as a multiline string
*/
function ITextArea(formals) {
	this.defaultData = "''";
	this.base = ITextField;
	this.base(formals);
	this.edit = ITextArea.prototype.edit;
}

ITextArea.prototype = new ITextField;

/**
* Display the multiline string editor
* @param {node} parentNode the HTML node to attach the editor to
*/
ITextArea.prototype.edit = function(parentNode) {
	this.doc = parentNode.ownerDocument; 
	var doc = this.doc;
	var textarea = doc.createElement("textarea");
	this.setClass(textarea,"inputClass","itextarea-textarea");
	if(this.formal!=null) {
		if(this.formal.rows!=null) {
			textarea.setAttribute("rows",this.formal.rows);
		}
		if(this.formal.cols!=null) {
			textarea.setAttribute("cols",this.formal.cols);
		}
	}
	this.inputId = "itextarea-"+IStruct.getId();
	textarea.setAttribute("id",this.inputId);
	parentNode.appendChild(textarea);
	var value="";
	if(this.baseData[this.dataField]!=null)
		value = this.baseData[this.dataField];
	else
		value = this.getDefaultData();
	textarea.value = value;
}

/**
* Construct a new editor to manage data as finite set choice
* @param {object} formals an object describing the behaviour of the editor.
* The formal must have the following members<ul>
* <li><code>entries</code> an array of objects, describing the choices:<ul>
* <li><code>label</code> the text to be displayed for this choice
* <li><code>value</code> the value to assign the data with</li>
* </ul>
* </li>
* <li><code>inputClass</code> the CSS class for the select element, default <code>iselectfield-select</code></li>
* </ul>
* @class The <code>ISelectField</code> class manages data as a choice in a list
*/
function ISelectField(formals) {
	this.defaultData = "'none'";
	this.base = IType;
	this.base(formals);
	this.edit = ISelectField.prototype.edit;
	this.updateData = ISelectField.prototype.updateData;
	this.canDirectData = ISelectField.prototype.canDirectData;
}

ISelectField.prototype = new IType;

/**
* Returns <code>false</code> to indicate that this object does not handle
* direct data
*/
ISelectField.prototype.canDirectData = function() {
		return false;
}

/**
* Display the choice editor
* @param {node} parentNode the HTML node to attach the editor to
*/
ISelectField.prototype.edit = function(parentNode) {
	this.doc = parentNode.ownerDocument;
	var doc = this.doc;
	var select = doc.createElement("select");
	this.setClass(select,"inputClass","iselectfield-select");
	this.selectId = "iselectfield-"+IStruct.getId();
	select.setAttribute("id",this.selectId);
	parentNode.appendChild(select);
	if(this.formal==null) {
		alert("ISelectField missing formal");
		return;
	}
	if(this.formal.entries==null) {
		alert("ISelectField missing formal entries");
		return;
	}
	var value = this.getDefaultData();
	if(this.baseData[this.dataField]!=null)
		value = this.baseData[this.dataField];
	for(var i=0;i<this.formal.entries.length;i++) {
		var option = this.formal.entries[i];
		if(option.value==null) {
			alert("ISelectField entry missing value");
			return;
		}
		if(option.label==null) {
			alert("ISelectField entry missing label");
			return;
		}
		var opt = doc.createElement("option");
		opt.setAttribute("value",option.value);
		if(option.value == value) {
			opt.setAttribute("selected","selected");
		}
		select.appendChild(opt);
		opt.appendChild(doc.createTextNode(option.label));
	}
}

/**
* Update the actual data with the content of the editor
*/
ISelectField.prototype.updateData = function() {
	var select = document.getElementById(this.selectId);
	this.baseData[this.dataField] = select.value;
}

/**
* Construct a new editor to manage data as an array of data
* @param {object} formals an object describing the behaviour of the editor.
* The formal must have the following members<ul>
* <li><code>entries</code> an array of objects, describing the possible array item types:<ul>
* <li><code>label</code> an optional label to be displayed in the add
* type selector. If not set, <code>type</code> member is used</li>
* <li><code>type</code> the class name for editing this type of item</li>
* <li><code>typeTest</code> a piece of javascript code to be evaluated as 
* a boolean to check if a particular <code>data</code> is to be handled
* by this editor</li>
* <li><code>formal</code> an optional formal object to instantiate the corresponding editor</li>
* <li><code>tableClass</code> the CSS class for the array table, default <code>iarray-table</code></li>
* <li><code>valueClass</code> the CSS class for the value table cell, default <code>iarray-td-value</code></li>
* <li><code>addCellClass</code> the CSS class for the table cell containing the add selector, default <code>iarray-td-sel</code></li>
* <li><code>addSelClass</code> the CSS class for the add selector, default <code>iarray-sel</code></li>
* <li><code>nocmdClass</code> the CSS class for the table cell representing a disabled command, default <code>iarray-nocmd</code></li>
* <li><code>cmdClass</code> the CSS class for the table cell representing an enabled command, default <code>iarray-cmd</code></li>
* <li><code>upClass</code> additional CSS class for the table cell representing an UP command, default <code>iarray-up</code></li>
* <li><code>downClass</code> additional CSS class for the table cell representing a DOWN command, default <code>iarray-down</code></li>
* <li><code>removeClass</code> additional CSS class for the table cell representing a REMOVE command, default <code>iarray-remove</code></li>
* <li><code>addClass</code> additional CSS class for the table cell representing an ADD command, default <code>iarray-add</code></li>
* <li><code>buttonClass</code> the CSS class for the button containers, default <code>iarray-div-cmd</code></li>
* </ul>
* </li>
* </ul>
* @class The <code>IArray</code> class manages data as an array
*/
function IArray(formals) {
	this.defaultData = "[]";
	this.base = IType;
	this.base(formals);
	this.edit = IArray.prototype.edit;
	this.updateData = IArray.prototype.updateData;
	this.upItem = IArray.prototype.upItem; 
	this.downItem = IArray.prototype.downItem;
	this.removeItem = IArray.prototype.removeItem;
	this.appendItem = IArray.prototype.appendItem;
	this.addLine = IArray.prototype.addLine;
	this.buildCommands = IArray.prototype.buildCommands;
	this.validate = IArray.prototype.validate;
}

IArray.prototype = new IType;

/**
* Handle a UP command on an array item
* @param {int} index the item index to be moved upward
*/
IArray.prototype.upItem = function(index) {
	try {
		var entry = this.entries[index];
		if(entry.itype.dataField==index) {
			entry.itype.dataField=index-1;
		}
		if(this.entries[index-1].itype.dataField==index-1) {
			this.entries[index-1].itype.dataField=index;
		}
		this.entries.splice(index,1);
		this.entries.splice(index-1,0,entry);
		var data = this.directData[index];
		this.directData.splice(index,1);
		this.directData.splice(index-1,0,data);
		var tr = document.getElementById(entry.trId);
		var trPrev = tr.previousSibling;
		tr.parentNode.removeChild(tr);
		trPrev.parentNode.insertBefore(tr,trPrev);
		this.buildCommands();
	} catch(e) {
		alert("Error removing entry: "+e);
	}
}

/**
* Handle a DOWN command on an array item
* @param {int} index the item index to be moved downward
*/
IArray.prototype.downItem = function(index) {
		try {
			var entry = this.entries[index];
			if(entry.itype.dataField==index) {
				entry.itype.dataField=index+1;
			}
			if(this.entries[index+1].itype.dataField=index+1) {
				this.entries[index+1].itype.dataField=index;
			}
			this.entries.splice(index,1);
			this.entries.splice(index+1,0,entry);
			var data = this.directData[index];
			this.directData.splice(index,1);
			this.directData.splice(index+1,0,data);
			var tr = document.getElementById(entry.trId);
			var trNext = tr.nextSibling.nextSibling;
			var parent = tr.parentNode;
			parent.removeChild(tr);
			parent.insertBefore(tr,trNext);
			this.buildCommands();
		} catch(e) {
			alert("Error removing entry: "+e);
		}
}

/**
* Handle a REMOVE command on an array item
* @param {int} index the item index to be removed
*/
IArray.prototype.removeItem = function(index) {
	try {
		var entry = this.entries[index];
		this.entries.splice(index,1);
		var tr = document.getElementById(entry.trId);
		this.directData.splice(index,1);
		for(var i=index;i<this.entries.length;i++) {
			var itype = this.entries[i].itype;
			if(itype.dataField == i+1)
				itype.dataField = i;
		}
		tr.parentNode.removeChild(tr);
		this.buildCommands();
	} catch(e) {
		alert("Error removing entry: "+e);
	}
}

/**
* Add a new item at the end of the array
*/
IArray.prototype.appendItem = function() {
	var typeIndex = 0;
	var typeSelector = this.doc.getElementById(this.typeSelectId);
	if(typeSelector!=null) {
		typeIndex = typeSelector.value;
	}
	this.addLine(this.directData.length,typeIndex);
	this.buildCommands();
}

/**
* Add a new editor line. This method is called for each item when creating the 
* initial editor, and then once every created item.
* @param {int} i the index of the line
* @param {int} typeIndex the index of the line editor type, or if <code>null</code>
* the editor type is guessed from the actual data
*/
IArray.prototype.addLine = function(i, typeIndex) {
	var doc = this.doc;
	var ent0 = {};
	this.entries.push(ent0);
	var data = this.directData[i];
	var tr = doc.createElement("tr");
	var selector = doc.getElementById(this.selectorTrId);
	var tbody = selector.parentNode;
	tbody.insertBefore(tr,tbody.lastChild);
	ent0.trId = "iarray-"+IStruct.getId();
	tr.setAttribute("id",ent0.trId);
	var td1 = doc.createElement("td");
	tr.appendChild(td1);
	ent0.tdUpId = "iarray-"+IStruct.getId();
	td1.setAttribute("id",ent0.tdUpId);
	var div1 = doc.createElement("div");
	div1.innerHTML = "&nbsp;";
	this.setClass(div1, "buttonClass", "iarray-div-cmd");
	td1.appendChild(div1);
	var td2 = doc.createElement("td");
	tr.appendChild(td2);
	ent0.tdDownId = "iarray-"+IStruct.getId();
	td2.setAttribute("id",ent0.tdDownId);
	var div2 = doc.createElement("div");
	div2.innerHTML = "&nbsp;";
	this.setClass(div2, "buttonClass", "iarray-div-cmd");
	td2.appendChild(div2);
	var td3 = doc.createElement("td");
	ent0.tdRemoveId = "iarray-"+IStruct.getId();
	td3.setAttribute("id",ent0.tdRemoveId);
	tr.appendChild(td3);
	var div3 = doc.createElement("div");
	div3.innerHTML = "&nbsp;";
	this.setClass(div3, "buttonClass", "iarray-div-cmd");
	td3.appendChild(div3);
	var tdValue = doc.createElement("td");
	this.setClass(tdValue,"valueClass","iarray-td-value");
	tr.appendChild(tdValue);
	if(this.formal==null) {
		alert("IArray missing formal");
		return;
	}
	if(this.formal.entries==null) {
		alert("IArray missing formal entries");
		return;
	}
	if(!this.formal.entries instanceof Array) {
		alert("IArray formal entries is not a array");
		return;
	}
	var entry=null;
	if(typeIndex!=null) {
		entry = this.formal.entries[typeIndex];
	} else {
		for(var j=0;j<this.formal.entries.length;j++) {
			var ent = this.formal.entries[j];
			if(ent.type==null) {
				alert("IArray formal entry has no type");
				return;
			}				
			if(ent.typeTest==null) {
				entry=ent;
				break;
			}
			var check = eval(ent.typeTest);
			if(check==true) {
				entry = ent;
				break;
			}
		}
	}
	if(entry==null) {
			alert("Could not map IArray data to formal entry");
			return;
	}
	var itype;
	try {
	  // FIXME : deep copy for formal (to correct bug to many hselect field in an array).
	  var tmp = util_serialize(entry.formal);
	  itype = eval("new "+entry.type+"(" + tmp + ")");
	} catch(e) {
		alert("!! Error instantiating "+entry.type+": "+e);
		alert(tmp);
		return;
	}
	if(i==this.directData.length)
		this.directData.push(itype.getDefaultData());
	if(itype.canDirectData()) {
		itype.setData(this.directData[i]);
	} else {
		itype.setDataField(this.directData, i);
	}

	itype.draw(tdValue);
	ent0.itype = itype;
}

/**
* Update the comand cells depending of the item index. For instance, the first
* item must not have a triggerable UP command
*/
IArray.prototype.buildCommands = function() {
	for(var i=0;i<this.entries.length;i++) {
		var ent = this.entries[i];
		var td1 = document.getElementById(ent.tdUpId);
		var td2 = document.getElementById(ent.tdDownId);
		var td3 = document.getElementById(ent.tdRemoveId);
		if(i>0) {
			td1.onclick = new Function("IStruct.getRegisteredElement('"+this.id+"').upItem("+i+");");
			this.setClass(td1,"cmdClass","iarray-cmd","upClass","iarray-up");
		}	else {
			td1.onclick = new Function("");
			this.setClass(td1,"nocmdClass","iarray-nocmd");
		}
		if(i<this.entries.length-1) {
			td2.onclick = new Function("IStruct.getRegisteredElement('"+this.id+"').downItem("+i+");");
			this.setClass(td2,"cmdClass","iarray-cmd","downClass","iarray-down");
		} else {
			td2.onclick = new Function("");
			this.setClass(td2,"nocmdClass","iarray-nocmd");
		}
		this.setClass(td3,"cmdClass","iarray-cmd","removeClass","iarray-remove");
		td3.onclick = new Function("IStruct.getRegisteredElement('"+this.id+"').removeItem("+i+");");
	}
}

/**
* Display the array editor
* @param {node} parentNode the HTML node to attach the editor to
*/
IArray.prototype.edit = function(parentNode) {
	this.doc = parentNode.ownerDocument;
	var doc = this.doc;
	if(!this.directData instanceof Array) {
			alert("IArray data is not an array");
			return;
	}
	table = doc.createElement("table");
	table.setAttribute("cellSpacing","0");
	table.setAttribute("cellPadding","0");
	parentNode.appendChild(table);
	this.setClass(table,"tableClass","iarray-table");
	this.entries = [];
	this.id=IStruct.registerElement(this);
	var tr = doc.createElement("tr");
	var tbody = doc.createElement("tbody");
	table.appendChild(tbody);
	tbody.appendChild(tr);
	this.selectorTrId = "iarray-"+IStruct.getId();
	tr.setAttribute("id",this.selectorTrId);
	var td = doc.createElement("td");
	tr.appendChild(td);
	this.setClass(td,"cmdClass","iarray-cmd","addClass","iarray-add");
	td.onclick = new Function("IStruct.getRegisteredElement('"+this.id+"').appendItem();");
	var div = doc.createElement("div");
	this.setClass(div, "buttonClass", "iarray-div-cmd");
	td.appendChild(div);
	td = doc.createElement("td");
	tr.appendChild(doc.createElement("td"));	
	tr.appendChild(doc.createElement("td"));
	tr.appendChild(td);
	this.setClass(td,"addCellClass","iarray-td-sel");
	if(this.formal.entries.length>1) {
		var select = document.createElement("select");
		this.setClass(select,"addSelClass","iarray-sel");
		this.typeSelectId = "iarray-"+IStruct.getId();
		select.setAttribute("id",this.typeSelectId);
		td.appendChild(select);
		for(var i=0;i<this.formal.entries.length;i++) {
			var entry = this.formal.entries[i];
			var option = doc.createElement("option");
			select.appendChild(option);
			option.setAttribute("value",i);
			var label=entry.label;
			if(label==null)
				label = entry.type;
			option.appendChild(doc.createTextNode(label));
		}
	} else {
		td.innerHTML = "&nbsp;";
	}
	for(var i=0;i<this.directData.length;i++) {
		this.addLine(i,null);
	}
	
	this.buildCommands();
}

/**
* Update the actual data with the content of the editor. This is performed
* by calling the sub-editors <code>updateData</code> method for each item
*/
IArray.prototype.updateData = function() {
	for(var i=0;i<this.entries.length;i++) {
		var entry = this.entries[i];
		entry.itype.updateData();
	}
}

/**
* Check the availability of the edited data by checking each of its fields
*/
IArray.prototype.validate = function() {
	for(var i=0;i<this.entries.length;i++) {
		var entry = this.entries[i];
		var v = entry.itype.validate();
		if(v == false)
			return false;
	}
	return true;
}

/**
* Construct a new editor to manage data as set of strings within a list of
* possible values
* @param {object} formals an object describing the behaviour of the editor.
* The formal have the following members<ul>
* <li><code>entries</code> an array of entries representing the choices:<ul>
* <li><code>value</code> the value to be stored</li>
* <li><code>label</code> the text to be displayed for this value</li>
* </ul></li>
* <li><code>minItems</code> the minimum number of selected entries (optional)</li>
* <li><code>minItemsMsg</code> the message to be displayed in case there are too few entries (optional)</li>
* <li><code>maxItems</code> the maximum number of selected entries (optional) </li>
* <li><code>maxItemsMsg</code> the message to be displayed in case there are too many entries (optional)</li>
* <li><code>size</code> the number of visible items (optional)</li>
* <li><code>inputClass</code> the CSS class for the select element, default <code>iselectfield-select</code></li>
* </ul>
* @class The <code>IMultiSelect</code> class manages data as a choice of multiple items in a list
*/
function IMultiSelect(formals) {
	this.defaultData = "[]";
	this.base = IType;
	this.base(formals);
	this.edit = IMultiSelect.prototype.edit;
	this.updateData = IMultiSelect.prototype.updateData;
	this.canDirectData = IMultiSelect.prototype.canDirectData;
	this.validate = IMultiSelect.prototype.validate;
}

IMultiSelect.prototype = new IType;

/**
* Returns <code>false</code> to indicate that this object does not handle
* direct data
*/
IMultiSelect.prototype.canDirectData = function() {
		return false;
}

/**
* Display the choice editor
* @param {node} parentNode the HTML node to attach the editor to
*/
IMultiSelect.prototype.edit = function(parentNode) {
	this.doc = parentNode.ownerDocument;
	var doc = this.doc;
	var select = doc.createElement("select");
	this.setClass(select,"inputClass","iselectfield-select");
	this.selectId = "iselectfield-"+IStruct.getId();
	select.setAttribute("id",this.selectId);
	select.setAttribute("multiple","multiple");
	if(this.formal && this.formal.size)
		select.setAttribute("size",this.formal.size);
		
	parentNode.appendChild(select);
	if(this.formal==null) {
		alert("IMultiSelect missing formal");
		return;
	}
	if(this.formal.entries==null || this.formal.entries[0]==null) {
		alert("IMultiSelect missing or bad formal entries");
		return;
	}
	var value = this.getDefaultData();
	if(this.baseData[this.dataField]!=null)
		value = this.baseData[this.dataField];
	for(var i=0;i<this.formal.entries.length;i++) {
		var option = this.formal.entries[i];
		var opt = doc.createElement("option");
		opt.setAttribute("value",option.value);
		for(var j=0;j<value.length;j++) {
			if(option.value==value[j]) {
				opt.selected = true;
				break;
			}
		}
		select.appendChild(opt);
		opt.appendChild(doc.createTextNode(option.label));
	}
}

/**
* Update the actual data with the content of the editor
*/
IMultiSelect.prototype.updateData = function() {
	var select = document.getElementById(this.selectId);
	var value=[];
	var index=0;
	for(var opt=select.firstChild;opt;opt=opt.nextSibling) {
		if(opt.selected)
			value.push(this.formal.entries[index].value);
		index++;
	}
	this.baseData[this.dataField] = value;
}

/**
* Check validity of the current choice and eventually display warning
*/
IMultiSelect.prototype.validate = function() {
	var select = this.doc.getElementById(this.selectId);
	var count=0;
	for(var opt=select.firstChild;opt;opt=opt.nextSibling) {
		if(opt.selected)
			count++;
	}
	var ok=true;
	var message=null;
	if(this.formal) {
		if(this.formal.minItems) {
			if(count<this.formal.minItems) {
				message = "Minimum "+this.formal.minItems+" selection(s)";
				ok = false;
			}
			if(count>this.formal.maxItems) {
				message = "Maximum "+this.formal.maxItems+" selection(s)";
				ok = false;
			}
		}
	}
	if(ok==false) {
		this.notify(message);
		select.focus();
	}	
	return ok;
}

/**
* Construct a new I18N instance for managing internationalization
* @param {Object} formals the formal definition of the data. <code>formal</code> is an object with the following 
* members:<ul>
* <li><code>i18nSelId</code> the id of a select element describing languages</li>
* <li><code>i18nType</code> the <code>IType</code> sub-class to manage language dependent data</li>
* <li><code>...</code> any formal attributes for instantiating the <code>i18nType</code> object</li>
* <li><code>i18nStaticData</code> an array of members and associated values that are automatically added
* to the actual data<ul>
* <li><code>field</code> the name of the member</li>
* <li><code>value</code> the value for the member</li>
* </ul></li>
* <li><code>i18nTableClass</code> the CSS class for the container table, default <code>i18n-table</code></li>
* <li><code>i18nLabelClass</code> the CSS class for the cell containing the language label, default <code>i18n-td-label</code></li>
* <li><code>i18nValueClass</code> the CSS class for the cell containing value, default <code>i18n-td-value</code></li>
* </ul>
* @class The <code>I18N</code> object provides facilities for editing language dependent data
*/
function I18N(formals) {
	this.defaultData = "{}";
	this.base = IType;
	this.base(formals);
	this.displayLabel = I18N.prototype.displayLabel;
	this.edit = I18N.prototype.edit;
	this.updateData = I18N.prototype.updateData;
	this.validate = I18N.prototype.validate;
	this.selectChanged = I18N.prototype.selectChanged;
	this.selectLang = I18N.prototype.selectLang;
	this.canDirectData = I18N.prototype.canDirectData;
	if(formals==null) {
		alert("I18N missing formal");
		return;
	}
	if(formals.i18nSelId==null) {
		alert("I18N missing i18nSelId formal");
		return;
	}
	if(formals.i18nType==null) {
		alert("I18N missing i18nType formal");
		return;
	}
	this.i18nSelId = formals.i18nSelId;
}

I18N.prototype = new IType;

I18N.prototype.displayLabel = function() {
  try {
    var itype = eval("new "+this.formal.i18nType+"(this.formal)");
    return itype.displayLabel();
  } catch ( e ) { }
};

/**
* Display the object editor
* @param {node} parentNode the HTML node to attach the editor to
*/
I18N.prototype.edit = function(parentNode) {
	if(this.baseData[this.dataField]==null)
		this.baseData[this.dataField] = this.getDefaultData();
	var select = this.doc.getElementById(this.i18nSelId);
	if(select==null) {
		alert("I18N did not find a select element with id "+this.i18nSelId);
		return;
	}
	// build map of available languages and their name
	this.langMap = {};
	var node=select.firstChild;
	while(node) {
		if(node.nodeName.toLowerCase()=="option") {
			var value = node.getAttribute("value");
			if(value && value!="" && node.firstChild) {
				var label = /[ \n\r]*(.*)[ \n\r]*/.exec(node.firstChild.nodeValue)[1];
				if(label && label!="") {
					this.langMap[value] = label;
				}
			}
				
		}
		node = node.nextSibling;
	}
	this.id = IStruct.registerElement(this);
	var fnt = new Function("IStruct.getRegisteredElement(\""+this.id+"\").selectChanged()");
  if(document.addEventListener)
    select.addEventListener("change", fnt, false);
  else
    if(document.attachEvent)
      select.attachEvent("onchange", fnt);
	this.entries = {};
	var table = this.doc.createElement("table");
	parentNode.appendChild(table);
	this.setClass(table,"i18nTableClass","i18n-table");
	table.setAttribute("cellpadding","0");
	table.setAttribute("cellspacing","0");
	var tbody = this.doc.createElement("tbody");
	table.appendChild(tbody);
	for(var l in this.langMap) {
		var entry = { lang: l };
		this.entries[l] = entry;
		var tr = this.doc.createElement("tr");
		tbody.appendChild(tr);
		entry.defDisplay = tr.style.display;
		tr.style.display="none";
		entry.trId = "i18n-"+IStruct.getId();
		tr.setAttribute("id",entry.trId);
		var itype;
		try {
			itype = eval("new "+this.formal.i18nType+"(this.formal)");
		} catch(e) {
			alert("I18N error instantiating "+formal.type+": "+e);
			break;
		}
		if(itype.canDirectData()) {
			if(this.baseData[this.dataField][entry.lang] == null) {
				this.baseData[this.dataField][entry.lang] = itype.getDefaultData();
			}
			itype.setData(this.baseData[this.dataField][entry.lang]);
		} else {
			itype.setDataField(this.baseData[this.dataField], entry.lang);
		}
		entry.itype = itype; 
		if ( itype.displayLabel() ) {
		  var td = this.doc.createElement("td");
		  tr.appendChild(td);
		  this.setClass(td,"i18nLabelClass","i18n-td-label");
		  td.appendChild(this.doc.createTextNode(this.langMap[l]));
		}
		var tdValue = this.doc.createElement("td");
		entry.tdValueId = "i18n-"+IStruct.getId();
		tdValue.setAttribute("id",entry.tdValueId);
		tr.appendChild(tdValue);
		this.setClass(tdValue,"i18nValueClass","i18n-td-value");
		itype.draw(tdValue);
	}
	this.selectChanged();
}

/**
* Indicate that the language selection has changed. The method
* access the language selector and update current language accordingly
*/
I18N.prototype.selectChanged = function() {
	var select = this.doc.getElementById(this.i18nSelId);
	var lang = select.value;
	this.selectLang(lang);
}

/**
* Set the current language and display editor accordingly
* @param {string} lang the new current language
*/
I18N.prototype.selectLang = function(lang) {
	for(var l in this.entries) {
		var entry = this.entries[l];
		var tr = this.doc.getElementById(entry.trId);
		if(l==lang) {
			tr.style.backgroundColor = "White";
			tr.style.display = entry.defDisplay;
		} else {
			tr.style.backgroundColor = "Yellow";
			tr.style.display = "none";
		}
	}
	this.selectedLang = lang;
}

/**
* Update the actual data with the content of the editor
*/
I18N.prototype.updateData = function() {
	for(var l in this.entries) {
		var entry = this.entries[l];
		entry.itype.updateData();
	}
	if(this.formal && this.formal.i18nStaticData && this.formal.i18nStaticData[0]) {
		for(var i=0;i<this.formal.i18nStaticData.length;i++) {
			var data = this.formal.i18nStaticData[i];
			this.baseData[this.dataField][data.field] = data.value;
		}
	}
}

/**
* Check validity of the current choice and eventually display warning
*/
I18N.prototype.validate = function() {
	var selected = this.selectedLang;
	for(var l in this.entries) {
		var entry = this.entries[l];
		this.selectLang(entry.lang);
		if(entry.itype.validate()==false) {
			this.selectLang(selected);
			return false;
		}
	}
	this.selectLang(selected);
	return true;
}

/**
* Indicate that the object cannot directly manage the data but instead
* access the data from its container and a field name 
*/
I18N.prototype.canDirectData = function() {
	return false;
}

/**
* Construct a new editor to manage data as hierarchical select values. The actual data uses a dotted notation.
* @param {object} formals an object describing the behaviour of the editor, with the following fields:<ul>
* <li><code>entries</code> an array of objects with the fields:<ul>
* <li><code>value</code> the value for this node/leaf</li>
* <li><code>label</code> the label that is displayed for this option</li>
* <li><code>entries</code> an optional array with the same syntax describing the sub-choices</li>
* <li><code>nodata</code> optional, if set to true, the entry is not reflected in the actual</li>
* </ul></li>
* <li><code>tableClass</code> the class for the table containing the whole field, default is <code>ihselectfield-table</code></li>
* <li><code>inputClass</code> the CSS primary class for the select element, default <code>ihselectfield-select</code>.
* In addition to the primary class, a secondary class is used for each select which is the base class prefixed by the
* level (0 based). For instance, the select HTML element at the second position is written this way:
* <code>&lt;select class="ihselectfield-select ihselectfield-select1"&gt;</li>
* <li><code>tdClass</code> the class for the each column in the table containing the whole field, default is <code>ihselectfield-td</code>.
* The same secondary class mechanism as <code>inputClass</code> is used</li>
* </ul>
* @class The <code>IHSelectField</code> class manages data as a hierarchical tree of choices
*/
function IHSelectField(formals) {
	this.defaultData = null;
	this.base = IType;
	this.base(formals);
	this.edit = IHSelectField.prototype.edit;
	this.updateData = IHSelectField.prototype.updateData;
	this.canDirectData = IHSelectField.prototype.canDirectData;
	this.buildSelect = IHSelectField.prototype.buildSelect;
	this.displayValue = IHSelectField.prototype.displayValue;
	this.newValue = IHSelectField.prototype.newValue;
}

IHSelectField.prototype = new IType;
                              
/**
* Display the hierarchical select editor
* @param {node} parentNode the HTML node to attach the editor to
*/
IHSelectField.prototype.edit = function(parentNode) {
	var table = this.doc.createElement("table");
	table.setAttribute("cellspacing","0");
	table.setAttribute("cellpadding","0");
	this.setClass(table,"tableClass","ihselectfield-table");
	parentNode.appendChild(table);
	var tbody = this.doc.createElement("tbody");
	table.appendChild(tbody);
	var tr = this.doc.createElement("tr");
	tbody.appendChild(tr);
	this.column = [];
	if(this.formal == null) {
		alert("IHSelectField: missing formal");
		return;
	}
	if(this.formal.entries[0] == null) {
		alert("IHSelectField: missing formal entries");
		return;
	}
	this.id = IStruct.registerElement(this);
	this.buildSelect(tr,this.formal,0,"");
	var data = this.baseData[this.dataField]; 
	if(data!=null)
		this.displayValue(data,this.formal,0);
	else
		this.displayValue(this.formal.entries[0].value,this.formal,0);
}

/**
* Update the state of the select elements to reflect the value
* @param {string} value the dotted data string, or null if selects must be hidden
* @param {object} formal the part of the formal handling the data
*/
IHSelectField.prototype.displayValue = function(value,formal) {
        var separator = ( ( this.formal.separator ) ? this.formal.separator : "." );
	var select = this.doc.getElementById(formal.selectId);
	var found = false;
	if(value==null) {
		for(var i=0;i<formal.entries.length;i++) {
			var entry = formal.entries[i];
			if(entry.entries!=null)
				this.displayValue(null,entry);
			select.style.display = "none";
		}
	} else {
		var parts = new RegExp("^\\" + separator + "?(.*?)(\\" + separator + ".*)?$").exec(value);
		for(var i=0;i<formal.entries.length;i++) {
			var entry = formal.entries[i];
			if(parts[1]==entry.value) {
				select.value = parts[1];
				found = true;
				if(entry.entries!=null) {
					if(parts[2]!=null && parts[2]!="") {
						this.displayValue(parts[2],entry);
					} else {
						this.displayValue(entry.entries[0].value,entry);
					}
				}
			} else if(entry.entries!=null) {
				this.displayValue(null,entry);
			}
		}
		if(found) {
			select.style.display = "inline";
			// force redraw of select option as a workaround for ie
			var parent = select.parentNode;
			parent.removeChild(select);
			parent.appendChild(select);
		} else {
			select.style.display = "none";
			alert("IHSelectField: not found formal for '"+parts[0]+"' in value '"+this.baseData[this.dataField]+"'");
		}
	}
}

/**
* Called when a selection has changed in a select element
* @param {string} selectId the id attribute of the select element responsible for the event
* @param {string} the begining of the data leading to this node
*/
IHSelectField.prototype.newValue = function(selectId, baseValue) {
        var separator = ( ( this.formal.separator ) ? this.formal.separator : "." );
	var select = this.doc.getElementById(selectId);
	var value = select.value;
	if(baseValue!="")
		value = baseValue + separator + value;
	this.displayValue(value,this.formal,0);
}

/**
* Construct the select elements recursively
* @param {Node} tr the table row element to attach columns to
* @param {object} formal the formal information for this part of the tree structure
* @param {level} level the 0-based depth of the node
* @param {string} fValue the dotted value leading to this node 
*/
IHSelectField.prototype.buildSelect = function(tr,formal,level,fValue) {
        var separator = ( ( this.formal.separator ) ? this.formal.separator : "." );
	var td=null;
	if(this.column[level]==null) {
		this.column[level] = { selectIds: [] };
		this.column[level].tdId = "ihselectfield-td-"+IStruct.getId();
		td = this.doc.createElement("td");
		td.setAttribute("id",this.column[level].tdId);
		this.setClass(td,"tdClass","ihselectfield-td",
			"tdClass"+level,"ihselectfield-td"+level);
		tr.appendChild(td);
	} else {
		td = this.doc.getElementById(this.column[level].tdId);
	}
	var select = this.doc.createElement("select");
	selId = "ihselectfield-sel-"+IStruct.getId();
	select.setAttribute("id",selId);
	select.onchange = new Function("IStruct.getRegisteredElement('"+this.id+"').newValue('"+selId+"','"+fValue+"')");
	this.setClass(select,"inputClass","ihselectfield-select",
			"inputClass"+level,"ihselectfield-select"+level);
	select.style.display = "none";
	td.appendChild(select);
	this.column[level].selectIds.push(selId);
	formal.selectId = selId;
	for(var i=0;i<formal.entries.length;i++) {
		var entry = formal.entries[i];
		entry.index = i;
		if(entry.value == null) {
			alert("IHSelectField: missing value");
			return;
		}
		if(entry.label == null) {
			alert("IHSelectField: missing label");
			return;
		}
		var option = this.doc.createElement("option");
		option.setAttribute("value",entry.value);
		select.appendChild(option);
		option.appendChild(this.doc.createTextNode(entry.label));
		if(entry.entries!=null) {
			var fValue0 = null;
			if(fValue=="")
				fValue0 = entry.value;
			else
				fValue0 = fValue + separator + entry.value;
			this.buildSelect(tr,entry,level+1,fValue0);
		}
	}
}

/**
* Update the actual data with the content of the editor
*/
IHSelectField.prototype.updateData = function() {
        var separator = ( ( this.formal.separator ) ? this.formal.separator : "." );
	var value="";
	var formal = this.formal;
	var loop = true;
	while(loop) {
		if(formal.entries==null)
			break;
		var select = this.doc.getElementById(formal.selectId);
		var found = false;
		for(var i=0;i<formal.entries.length;i++) {
			var entry = formal.entries[i];
			if(entry.value == select.value) {
				formal = entry;
				found = true;
				if(entry.nodata == true)
					loop = false;
				break;
			}
		}
		if(found == false) {
			alert("IHSelectField: updateData not found '"+select.value+"', stay at value '"+value+"'");
			break;
		}
		if(loop==true) {
			if(value=="")
				value = select.value;
			else
				value += separator + select.value;
		}
	}
	this.baseData[this.dataField] = value;
}

/**
* Returns <code>false</code> to indicate that this object does not handle
* direct data
*/
IHSelectField.prototype.canDirectData = function() {
	return false;
}

/**
* Construct a new editor to manage data as hierarchical select values. The actual data uses a dotted notation.
* @param {object} formals an object describing the behaviour of the editor. All attributes are identical to <code>IHSelectField</code>,
* except for <code>entries</code> which is now an of dotted strings representing the possible values of the whole tree. An additional 
* attribute, <code>stopperLabel</code> can be added to the formal to indicate the option to be displayed in the next select when
* a non-leaf node can be accessible, default is <code>--</code>
* @class The <code>ISimpleHSelectField</code> class manages data as a hierarchical tree of choices, using simpler
* formal entries definition
*/
function ISimpleHSelectField(formals) {
	this.base = IHSelectField;
	this.base(formals);
	this.buildFormals = ISimpleHSelectField.prototype.buildFormals;
	this.addFormal = ISimpleHSelectField.prototype.addFormal;
	this.fixFormal = ISimpleHSelectField.prototype.fixFormal;
	this.formal = {};
	for(var k in formals) {
		if(k != "entries") {
			this.formal[k] = formals[k];
		}
	}
	this.buildFormals(formals.entries,this.formal);
	this.fixFormal(this.formal);
}

ISimpleHSelectField.prototype = new IHSelectField;

/**
* Create a regular formal structure based on a simplified one
* @param {array} vFormals the simplified formal as an array of dotted string values
* @param {object} oFormal the object to be filled with new formal structure
*/
ISimpleHSelectField.prototype.buildFormals = function(vFormals,oFormal) {
	for(var i=0;i<vFormals.length;i++) {
		var vFormal = vFormals[i];
		this.addFormal(vFormal,oFormal);
	}
}

/**
* Modify the formal structure to accept a new option
* @param {string} vFormal the dotted string representing the new option
* @param {object} oFormal the object to be filled with new formal structure
*/
ISimpleHSelectField.prototype.addFormal = function(vFormal,oFormal) {
	var parts = /^\.?(.*?)(\..*)?$/.exec(vFormal);
	if(oFormal.entries==null) {
		oFormal.entries = [];
	}
	var entry = null;
	for(var i=0;i<oFormal.entries.length;i++) {
		if(oFormal.entries[i].value == parts[1]) {
			entry = oFormal.entries[i];
			break;
		}
	}
	if(entry==null) {
		entry = {
			value: parts[1],
			label: parts[1]
		};
		oFormal.entries.push(entry);
	}
	if(parts[2]!=null && parts[2]!="") {
		this.addFormal(parts[2],entry);
	} else {
		entry.accessible = true;
	}
}

/**
* Make sure accessible non-leaf nodes can be selected by adding a special option
* if the next select
* @param {object} oFormal the object formal
*/
ISimpleHSelectField.prototype.fixFormal = function(oFormal) {
	if(oFormal.accessible == true) {
		if(oFormal.entries!=null &&	oFormal.entries[0].nodata!=true) {
			var entry = {
				value: "none",
				label: "--",
				nodata: true
			};
			if(this.formal.stopperLabel!=null)
				entry.label = this.formal.stopperLabel;
			oFormal.entries.splice(0,0,entry);
		}
	}
	if(oFormal.entries!=null) {
		for(var i=0;i<oFormal.entries.length;i++) {
			this.fixFormal(oFormal.entries[i]);
		}
	}
}

/**
* Counter to ensure unicity of IDs
*/
IStruct.lastId = 0;

/**
* A map of registered objects
*/
IStruct.registeredElements = {}

/**
* Returns a new unique id
*/
IStruct.getId = function() {
	var id = ""+IStruct.lastId++;
	return id;
}

/**
* Store an object globally and returns the key to retrieve it
* @param {any} obj the object to be stored
* @return {string} the key to retrieve the object
*/
IStruct.registerElement = function(obj) {
	var id = "istruct-obj-" + IStruct.getId();
	IStruct.registeredElements[id]=obj;	
	return id;
}

/**
* Returns a previously registered object, given its key
* @param {string} id the object key
* @return {any} the object or null if not found
*/
IStruct.getRegisteredElement = function(id) {
	return IStruct.registeredElements[id];
}

/**
* Add missing push method in IE
*/
if(Array!=null) {
	if(Array.push == null) {
	/**
	* Implement method push  of Array as it is missing on IE
	*/
	Array.prototype.push = function (element) {
	    this[this.length] = element;
    	return this.length;
		};
	}
}

function IFixed(formals) {
	this.defaultData = "{}";
	this.base = IType;
	this.base(formals);
	this.edit = IFixed.prototype.edit;
	this.updateData = IFixed.prototype.updateData;
}
	
IFixed.prototype = new IType;

IFixed.prototype.edit = function(parentNode) {
	this.doc = parentNode.ownerDocument;
	var doc = this.doc;
	
	var text = doc.createTextNode(this.formal.fixedText);
	var span = doc.createElement("span");
	span.appendChild(text);
	this.setClass(span,"textClass","ifixed-text");
	parentNode.appendChild(span);
}

/**
* Update actual data with the edited values. This method is to be overwritten in sub-classes.
*/
IFixed.prototype.updateData = function() {
	this.updateStaticData();
}

