/* * DEK JavaScript Library * Copyright(c) 2008-2010, DEK International * * Version 1.0.12 */ /* ############################## XML utils ############################## *//** * XML utils contains useful methods for navigating XML. * @class Core.XML * @singleton */Core.XML = {	/**	 * This will find the first child of the specified parent.	 * @method findFirstChild	 * @static	 * @param DOMNode parent The node containing the children you wish to look for.	 * @param {String} nodeName (Optional) When specified this will look for the first child with that node name, if not specified the first child of any node name will be returned	 * @return DOMNode The first child, if there are no children or no children of the specified type, this will return null.	 */	findFirstChild: function(parent, nodeName) {		return Core.XML.findChild(parent.firstChild, nodeName);	},	/**	 * This will find the next sibling of the child specified.	 * @method findNextChild	 * @static	 * @param DOMNode child The node whos siblings your are looking for.	 * @param {String} nodeName (Optional) When specified this will look for the first sibling with that node name, if not specified the first sibling of any node name will be returned	 * @return DOMNode The first sibling, if there are no more siblings or no more siblings of the specified type, this will return null.	 */	findNextChild: function(child, nodeName) {		return Core.XML.findChild(child.nextSibling, nodeName);	},	/* private */	findChild: function(child, nodeName) {		while(child && child.nodeType) {			if(child.nodeType==1) {				if(!nodeName || child.nodeName.toUpperCase()==nodeName.toUpperCase()) {					return child;				}			}			child = child.nextSibling;		}		return null;	},	/**	 * This will attempt to read an attribute from a node, if the attribute does not exsisit the <code>defaultValue</code> will be returned.	 * @method getAttribute	 * @static	 * @param DOMNode node The node you wish to read the attribute from.	 * @param {String} attributeName The name of the attribute.	 * @param {String} defaultValue The default value to return if the attribute is missing.	 * @return {String} The value of the attribute or default value when it is missing.	 */	getAttribute: function(node, attributeName, defaultValue) {		if(attributeName!="") {			if(node.getAttribute(attributeName))	{				return node.getAttribute(attributeName);			}		}		return defaultValue;	}}/* ############################## JSON utils ############################## *//** * A JSON parser and serialiser. * @class Core.JSON * @singleton */Core.JSON = {	unicode: /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,	escapable: /[\x00-\x1f\x22\x5c\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,	serilaiseEscapes: {		'\b': '\\b',		'\t': '\\t',		'\n': '\\n',		'\f': '\\f',		'\r': '\\r',		'"' : '\\"',		'\\': '\\\\'	},	/**	 * <p>This method produces JSON text from a JavaScript value.</p>	 * <p> When an object value is found, if the object contains a toJSON	 * method, its toJSON method will be called and the result will be	 * serialised. A toJSON method does not serialise, it returns the	 * value represented by the name/value pair that should be serialised,	 * or undefined if nothing should be serialised. The toJSON method	 * will be passed the key associated with the value, and this will be	 * bound to the value</p>	 * <p>For example, this would serialise Dates as ISO strings.</p><pre><code>Date.prototype.toJSON = function (key) {	function f(n) {		// Format integers to have at least two digits.		return n < 10 ? '0' + n : n;	}	return this.getUTCFullYear()   + '-' +		f(this.getUTCMonth() + 1) + '-' +		f(this.getUTCDate())      + 'T' +		f(this.getUTCHours())     + ':' +		f(this.getUTCMinutes())   + ':' +		f(this.getUTCSeconds())   + 'Z';};</code></pre>	 * <p>Values that do not have JSON representations, such as undefined or	 * functions, will not be serialised. Such values in objects will be	 * dropped; in arrays they will be replaced with null.</p>	 * @method serialise	 * @static	 * @param {Object} value Any JavaScript value, usually an object or array.	 * @param {String} indent (Optional) String to use as an indent when creating the serialisation.	 */	serialise : function(value, indent) {		var newLine = "\n";		if(typeof(indent)!="string") {			indent = "";			newLine = "";		}		return this.serialiseObject("", {"": value}, indent, "", newLine);	},	/*	 * American spelling of serialise.	 * @method serialize	 * @static	 * @param {Object} value Any JavaScript value, usually an object or array.	 * @param {String} indent (Optional) String to use as an indent when creating the serialisation.	 */          serialize : function(value, indent) {		// serialize		return this.serialise(value, indent);	},		/* private	 * converts an object in to a JSON string	 */	serialiseObject: function(key, holder, indent, offset, newLine) {		var index, k, v, partial, value = holder[key];		// If the value has a toJSON method, call it to obtain a replacement value.		if (value && typeof(value)=== "object" && typeof(value.toJSON)==="function") {			value = value.toJSON(key);		}				// What happens next depends on the value's type.		switch (typeof(value)) {		case 'string':			return Core.JSON.serialiseString(value);					case 'number':			return isFinite(value) ? String(value) : "null";		case 'boolean':		case 'null':			return String(value);		case 'object': // objects include object, arrays and nulls			if (!value) {				return "null";			}			// Make an array to hold the partial results of stringifying this object value.			var partial = [];			// Is the value an array?			if(typeof(value.length)=="number") {				for (index=0; index<value.length; index++) {					v = this.serialiseObject(index, value, indent, offset+indent, newLine);					if (v) {						partial[index] = offset + indent + v;					} else {						partial[index] = offset + indent + "null";					}				}								if(partial.length>0) {					return "[" + newLine + partial.join(","+newLine) + newLine + offset + "]";				} else {					return "[]";				}			}			// iterate through all of the keys in the object.			for (k in value) {				if (Object.hasOwnProperty.call(value, k)) {					v = this.serialiseObject(k, value, indent, offset+indent, newLine);					if (v) {						partial.push(offset + indent + Core.JSON.serialiseString(k) + ":" + v);					}				}			}						if(partial.length>0) {				return "{" + newLine + partial.join(","+newLine) + newLine + offset + "}";			} else {				return "{}";			}		}	},		/* private      * If the string contains no control characters, no quote characters, and no	 * backslash characters, then we can safely slap some quotes around it.	 * Otherwise we must also replace the offending characters with safe escape	 * sequences.	 */	serialiseString: function(string) {		Core.JSON.escapable.lastIndex = 0;		if(Core.JSON.escapable.test(string)) {			return '"' + string.replace(Core.JSON.escapable, function (a) {				var c = Core.JSON.serilaiseEscapes[a];				return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);			}) + '"';		} else {			return '"' + string + '"';		}	},		/* private: regex patterns for JSON */	jsonToken: new RegExp(		'(?:false|true|null|[\\{\\}\\[\\]]' +		'|' + '(?:-?\\b(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\\b)' + // number		'|' + '(?:\"' + '(?:[^\\0-\\x08\\x0a-\\x1f\"\\\\]' + '|\\\\(?:[\"/\\\\bfnrt]|u[0-9A-Fa-f]{4}))' + '*\")' + ')' // string	, "g"),	escaped: new RegExp('\\\\(?:([^u])|u(.{4}))', 'g'),	parseEscapes: {		'"': '"',		'/': '/',		'\\': '\\',		'b': '\b',		'f': '\f',		'n': '\n',		'r': '\r',		't': '\t'	},	unescapeChar: function(_, ch, hex) {    		return ch ? Core.JSON.parseEscapes[ch] : String.fromCharCode(parseInt(hex, 16));  	},		/**	 * <p>This method parses a JSON text to produce an object or array.</p>	 * @method parse	 * @static	 * @param {String} text The text string with JSON to parse	 * @return {Object} An Object or array, resulting from the JSON string	 */	parse: function (text) {		// split in to tokens		var tokens = text.match(Core.JSON.jsonToken);				// prepare return object		var rv;		var token = tokens[0];		if (token==="{") {			rv = {};		} else if (token==="[") {			rv = [];		} else {      		throw new SyntaxError("JSON parse error: Unsupported top level object");		}				var key;		var stack = [rv];				// parse tokens		for (var index=1; index<tokens.length; index++) {			token = tokens[index];						switch(token.charAt(0)) {				case '"': // string					var text = token.substr(1, token.length-2).replace(Core.JSON.escaped, Core.JSON.unescapeChar);					var stackTop = stack[0];					if(stackTop instanceof Array) {						stackTop[stackTop.length]=text;					} else {						if(key) {							stackTop[key]=text;							key = null;						} else {							key = text;						}					}					break;									case '{': // open opbject					var newObject = {};										var stackTop = stack[0];					stackTop[key || stackTop.length]=newObject;					key = null;										stack.unshift(newObject);										break;								case '[': // open array					var newArray = [];										var stackTop = stack[0];					stackTop[key || stackTop.length]=newArray;					key = null;										stack.unshift(newArray);									break;									case '}': // close object				case ']': // close array					stack.shift();					break;									case 't': // true					var stackTop = stack[0];					stackTop[key || stackTop.length]=true;					key = null;					break;									case 'f': //false					var stackTop = stack[0];					stackTop[key || stackTop.length]=false;					key = null;					break;									case 'n': // null					var stackTop = stack[0];					stackTop[key || stackTop.length]=null;					key = null;					break;									default: // numeric					var stackTop = stack[0];					stackTop[key || stackTop.length]=+(token);					key = null;					break;			}		}				// check to see the stack is empty		if(stack.length>0) {			throw new SyntaxError("JSON parse error: Unbalanced");		}				return rv;	}} /* ############################## Data ############################## */  /** * <p>The data class is intended to store data from various sources in a standadised way so * objects such as Core.Ui.Table and Core.Ui.Tree only have to work with one data format.  The * data class is also designed to deal with data sources that grow incrementally.</p> * <p>Data is organised into rows and each row can have several cells. Each row can also have  * any number of child rows, and they can have children of there own, forming a tree of data. * Each row has a position number, similar to an index in an array, and represents it's possition  * amonst it siblings.  In some methods such as <code>addRow</code> and <code>getRow * </code> a multi level posistion can be used which includes the rows position at each level * seperated with a "." for example "4.15.8".</p> * <p>The data object acts as a single access point for adding and retriving data.  It also provides  * two events to inform listeners when data is added, one for when the entrie object has been * reset, and another for when additional data has been added.</p> * <p>The data class provides an abstract <code>load</code> method which is responsible  * for adding data to the object.  It is intended that the data class should be extended for each * data source and the load method overridden with the appropriate code to add the data to the * object.</p> * @class Core.Data * @see Core.Data.Row */ Core.Data = Core.create({ 	/** 	 * Craeate a new empty data object 	 * @method init 	 * @constructor 	 */	init: function() {		this.resetData();		this.positionSeperator = ".";				// events				/**		 * Fires when all old data is removed and new data is added.		 * @event reloadedEvent		 * @param {Core.Data} data A reference to this data object.		 * @param {Array} newRows An array of {Core.Data.Row} with all the new rows added.  In the case of a realoadedEvent it will be all the rows.		 */		this.reloadedEvent = new Core.Event("Core.Data:reloaded");				/**		 * Fires when new data is added.		 * @event additionalRowsEvent		 * @param {Core.Data} data A reference to this data object.		 * @param {Array} newRows An array of {Core.Data.Row} with all the new rows added.		 */		this.additionalRowsEvent = new Core.Event("Core.Data:additionalRows");	},	/** 	 * @method getTable 	 * @return {Core.Data} A reference to this data object. 	 */	getTable: function() {		return this;	},	/** 	 * @method getPositionSeperator 	 * @return {String} The current position seperater. 	 */	getPositionSeperator: function() {		return this.positionSeperator;	},	/**	 * This is called from the <code>addRow</code> method of the {Core.Data.Row}. It is used to	 * create the new row object.  In cases where a Data object has been extended to a custom	 * data type that uses a custom row class, this method should be overridden to create instances 	 * of that class. 	 * @method createRow 	 * @param {Core.Data.Row} parent The parent element for the new row. 	 * @param {String} position The position of this new row. 	 */	createRow: function(parent, position) {		return new Core.Data.Row(this, parent, position); 	},	/**	 * Adds a new blank row as a top level row.  If the position specified is a string, it	 * is broken in to multiple parts using the position seperator and each part added 	 * as a sub row to the previous part.  	 * @method addRow	 * @param {Number}/{String} position The position the new row should be added to.	 */	addRow: function(position) {		return this.root.addRow(position);	},	/**	 * Retrives a row.  If the position specified is a string, it is broken in to multiple parts 	 * using the position seperator and each part is searched for as a sub row of the previous.	 * @method getRow	 * @param {Number}/{String} position The position to retrive.	 */	getRow: function(position) {		return this.root.getRow(position);	},	/**	 * This creates an array containing all the top level rows for this data object.	 * @method getRows	 * @return {Array} The array of rows.	 */	getRows: function() {		return this.root.getChildRows();	},	/**	 * This creates an array containing all the new rows that have been added to the data 	 * object.  In cases where a new row also has children, only the parent row is added 	 * to the array.	 * @method getNewRows	 * @return {Array} The array of rows.	 */	getNewRows: function() {		var newRows = new Array();		this.root.findNewRows(newRows);		return newRows;	},	/**	 * This returns the last row that was added to the data object.	 * @method getLastRow	 * @return {Core.Data.Row} The last row added.	 */	getLastRow: function() {		return this.lastRow;	},	/**	 * The records the last row added to the data object.	 * @method setLastRow	 * @param {Core.Data.Row} row The last row added.	 */	setLastRow: function(row) {		this.lastRow = row;	},	/**	 * This deletes all the data in object and reset the data object.	 * @method resetData	 */	resetData: function() {		this.root = this.createRow(null, 0);		this.root.isNew = false;		this.lastRow = null;		this.useReloadedEvent = true;			},	/**	 * An abstract method responsible for adding data to the object.  By default this load 	 * method does nothing, It is intended that the data class should be extended for 	 * each data source and the load method overridden with the appropriate code to 	 * add the data to the object.	 * @method load	 */	load: function() {		// abstract - do nothing	},	/**	 * This is a convenience method to add listeners to both the data objects events.	 * If the reloadedEvent has already fired when this is called, it is fired again for 	 * the new listener only.	 * @method addListeners	 * @param {Function} reloadListener The listener for the reloadedEvent	 * @param {Function} addListener The listener for the additionalRowsEvent	 */	addListeners: function(reloadListener, addListener) {		this.reloadedEvent.addListener(reloadListener);		this.additionalRowsEvent.addListener(addListener);		if(!this.useReloadedEvent) {			reloadListener({data: this});		}	},	/**	 * This method works out which event to fire the reloadedEvent or the 	 * additionalRowsEvent.  It then fires the correct event.	 * @method fireUpdateEvent	 */	fireUpdateEvent: function() {		var newRows = this.getNewRows();		if(this.useReloadedEvent) {			this.useReloadedEvent = false;			this.reloadedEvent.fire({data: this, newRows: newRows});		} else {			this.additionalRowsEvent.fire({data: this, newRows: newRows});		}	}, 	dump: function(element) {		element = Core.get(element);		element.setHtml(this.toString());	},	toString: function() {		return this.root.toString();	}}); Core.copy(Core.Data, {	/**	 * @property STRING	 * @static Data Types	 */	STRING: 0,		/**	 * @property NUMBER	 * @static Data Types	 */	NUMBER: 1,		/**	 * UTF-8 encoded string.	 * @property UTF8	 * @static Data Types	 */	UTF8: 2,		/**	 * @property DATE	 * @static Data Types	 */	DATE: 3,	/*	 * @property IMAGE	 * @static Data Types	 */	IMAGE: 4,		/**	 * @property ICON	 * @static Data Types	 */	ICON: 5,	/**	 * @property PROGRESS	 * @static Data Types	 */	PROGRESS: 100,		/**	 * @property CUSTOM	 * @static Data Types	 */	CUSTOM: -1}); /** * <p>Represents a row in a data tree.</p> * @class Core.Data.Row * @see Core.Data */Core.Data.Row = Core.create({	/**	 * This consructor creates a new empty row.  Rows are not normally created directly but via 	 * <code>addRow</code> methods of {Core.Data} and {Core.Data.Row}	 * @method init	 * @constructor	 * @param {Core.Data} table The data object for this row.	 * @param {Core.Row} parent This rows parent row.	 * @param {String} position The position of this row in the current level.	 */	init: function(table, parent, position) {		this.table = table;		this.parent = parent;		this.position = position;		this.siblings = 1;		this.children = 0;		this.rows = new Array();		this.hash = new Array();		this.table.setLastRow(this);		this.cells = {};			this.isNew=true;		},	/**	 * Provides accees to the this rows data object. 	 * @method getTable 	 * @return {Core.Data} A reference to the data object. 	 */	getTable: function() {		return this.table;	},	/** 	 * @method getPositionSeperator 	 * @return {String} The current position seperater. 	 */	getPositionSeperator: function() {		return this.getTable().getPositionSeperator();	},	/**	 * For top level rows this will return the hidden root row.  For others it will return the parent row for this row.	 * @method getParent 	 * @return {Core.Data.Row} The parent row. 	 */	getParent: function() {		return this.parent;	},	/**	 * This will return the position of this row in it's current level.  For the full position use the <code>getFullPosition</code>.	 * @method getPosition 	 * @return {String} The rows position. 	 */	getPosition: function() {		//return parseInt(this.position);		return this.position;	},	/**	 * This will return an array of positions.  The position at element 0 will be the rows position at the top level, element 1 	 * will be the position at the next level and so on. For the position in the current level use the <code>getPosition</code>	 * method.	 * @method getFullPosition 	 * @return {Array} The rows position as an array of numbers 	 */	getFullPosition: function(arr) {		if(this.parent==null) {			return new Array();		} else {			var arr = this.parent.getFullPosition();			arr.push(this.getPosition());			return arr;		}	},	/**	 * This will return the number of siblings this row has.  The result will always be at least 1 as it counts it's self.	 * @method getSiblings 	 * @return {Number} The number of siblings this row has. 	 */	getSiblings: function() {		return this.siblings;	},	/**	 * Sets the number of siblings this row has, the value should always be at least 1 as it should count it's self.	 * @method setSiblings 	 * @param {Number} siblings The number of siblings this row has. 	 */	setSiblings: function(siblings) {		this.siblings = siblings;	},	/**	 * This will return the number of children this row has.	 * @method getChildren 	 * @return {Number} The number of children this row has. 	 */	getChildren: function() {		return this.children;	},	/**	 * This will set the number of children this row has.	 * @method setChildren 	 * @param {Number} children The number of children this row has. 	 */	setChildren: function(children) {		this.children = children	},	/**	 * Checks to see if this row has children.	 * @method hasChildren 	 * @return Boolean True if this row has children. 	 */	hasChildren: function() {		return this.children>0 || this.rows.length>0;	},	/* private?	 * Creates a sub row for this row.	 * @method createSubRow	 * @param {String} position The position of the row	 */	createSubRow: function(position) {		return this.table.createRow(this, position); 	},	/**	 * Adds a new blank sub row to the current row.  If the position specified is a string, it	 * is broken in to multiple parts using the position seperator and each part added 	 * as a sub row to the previous part.  	 * @method addRow	 * @param {Number}/{String} position The position the new row should be added to.	 */	addRow: function(position) {		var index;		var subPosition=null;		var firstPositionPart=null;						// parse position		if(typeof(position)=="number") {			firstPositionPart = ""+position;		}		if(typeof(position)=="string") {			var positionParts = position.split(this.getPositionSeperator());			var firstPositionPart = ""+positionParts.shift();			if(positionParts.length>0) {				subPosition = positionParts.join(this.getPositionSeperator());			}		}		if(firstPositionPart!=null) {			if(typeof(this.hash[firstPositionPart])!="undefined") {				index = this.hash[firstPositionPart];			} else {				index = this.rows.length;				this.hash[firstPositionPart] = index;			}		} else {			index=this.rows.length;			firstPositionPart = ""+index;		}				// add row		if(!this.rows[index]) {			this.rows[index] = this.createSubRow(firstPositionPart);		}				if(subPosition!=null) {						return this.rows[index].addRow(subPosition);		} else {			return this.rows[index];		}	},	/**	 * Retrives a sub row from this row.  If the position specified is a string, it is broken in	 * to multiple parts using the position seperator and each part is searched for as a sub 	 * row of the previous.	 * @method getRow	 * @param {Number}/{String} position The position to retrive.	 */	getRow: function(position) {		var index;		var subPosition=null;						// parse position		if(typeof(position)=="number") {			index=Math.floor(position);		}		if(typeof(position)=="string") {			var positionParts = position.split(this.getPositionSeperator());			var firstPositionPart = positionParts.shift();			if(isNaN(firstPositionPart)) {				if(typeof(this.hash[firstPositionPart])!="undefined") {					index = this.hash[firstPositionPart];				}			} else {				index=parseInt(firstPositionPart);			}						if(positionParts.length>0) {				subPosition = positionParts.join(this.getPositionSeperator());			}		}				// return row		if(!isNaN(index)) {			if(this.rows[index]) {				if(subPosition!=null) {								return this.rows[index].getRow(subPosition);				} else {					return this.rows[index];				}			}		}		return null;			},	/**	 * This creates an array containing all child rows for this row.	 * @method getChildRows	 * @return {Array} The array of rows.	 */	getChildRows: function() {		var rv = new Array();		for(var index=0; index<this.rows.length; index++) {			if(this.rows[index]) {				rv.push(this.rows[index]);			}		}		return rv;	},	/**	 * 	 * @method loadChildRows	 */	loadChildRows: function() {		// abstract - do nothing	},	/*	 * This is used by getNewRows in Core.Data to build the new row list.	 * @method findNewRows	 * @param {Array} The array of new rows.	 */	findNewRows: function(newRows) {		if(this.isNew) {			newRows.push(this);			this.clearNewRows();		} else {			for(var index=0; index<this.rows.length; index++) {				if(this.rows[index]) {					this.rows[index].findNewRows(newRows);				}			}		}	},	/*	 * Clears new rows, used by findNewRows	 * @method clearNewRows	 */	clearNewRows: function() {		this.isNew = false;		for(var index=0; index<this.rows.length; index++) {			if(this.rows[index]) {				this.rows[index].clearNewRows();			}		}	},	/**	 * Adds a cell to this row.	 * @method addCell	 * @pararm {Number} type The datatype for this cell.  See {Core.Data} for a list of types.	 * @param {String} name The name for this cell.	 * @param Mixed value For custom data types this should be a cell object, for all others it should be the cell value.	 */	addCell: function(type, name, value) {		if(type==Core.Data.CUSTOM) {			this.cells[name] = value;		} else {			this.cells[name] = new Core.Data.Cell(type, name, value);		}		return this.cells[name];	},	/**	 * Check to see if a cell of the specify name exsitis in this row.	 * @method hasCell	 * @param {String} name The name of the cell to check.	 * @return Boolean True is the cell exsitis.	 */	hasCell: function(name) {		return typeof(this.cells[name])!="undefined";	},	/**	 * Returns a requested cell	 * @method hasCell	 * @param {String} name The name of the cell to retrive.	 * @return {Core.Data.Cell} The request cell	 */	getCell: function(name) {		return typeof(this.cells[name])!="undefined" ? this.cells[name] : null;	},	/**	 * Check to see if a cell of the specify name exsitis in this row and has a value.	 * @method hasValue	 * @param {String} name The name of the cell to check.	 * @return Boolean True is the cell has a value.	 */	hasValue: function(name) {		return typeof(this.cells[name])!="undefined";	},	/**	 * Returns the value of a specified cell.	 * @method getValue	 * @param {String} name The name of the cell to retrive.	 * @return Mixed The request cell value.	 */	getValue: function(name) {		return typeof(this.cells[name])!="undefined" ? this.cells[name].getValue() : "";	},	/**	 * Creates an array of cell names for this row.	 * @method getCellNames	 * @return {Array} The array of cell names	 */	getCellNames: function() {		var cellsArray = new Array();		for(var name in this.cells) {			cellsArray.push(name);		}		return cellsArray;	},	toString: function() {		var childRows = this.getChildRows();		var cellsArray = new Array();		for(var name in this.cells) {			cellsArray.push(this.cells[name].toString());		}				if(childRows.length>0) {			if(cellsArray.length>0) {				return "["+cellsArray.join(", ")+" ["+childRows.join(", ")+"]]";			} else {				return "["+childRows.join(", ")+"]";			}					} else {			return "["+cellsArray.join(", ")+"]";		}			}});/** * Represents a cell in a data tree. * @class Core.Data.Cell * @see Core.Data */Core.Data.Cell = Core.create({	/**	 * Creates a new cell.	 * @method init	 * @constructor	 * @pararm {Number} type The datatype for this cell.  See {Core.Data} for a list of types.	 * @param {String} name The name for this cell.	 * @param Mixed value The cell value.	 */	init: function(type, name, value) {		this.type = type;		this.name = name;		this.value = value;		if(this.type == Core.Data.UTF8) {			this.value = Core.decode(this.value);		}				this.dynamic = false;		this.indent = 0;	},	/**	 * @method getType	 * @return {Number} The type of this cell.	 */	getType: function() {		return this.type;	},	/**	 * @method getValue	 * @return Mixed The value of this cell.	 */	getValue: function() {		return this.value;	},	/**	 * Set the value of the cell, if this cell has been specified as dynamic this will fire an updateEvent.	 * @method setValue	 * @param Mixed value The value this cell should be set to	 */	setValue: function(value) {		this.value = value;		if(this.isDynamic()) {			this.updateEvent.fire({cell: this, value: value});		}	},	/**	 * @method isDynamic	 * @return Boolean True if this cell has been specifed as dynamic	 */	isDynamic: function() {		return this.dynamic;	},	/**	 * Set the cell to dynamic and creates the updateEvent.	 * @method setDynamic	 * @param Boolean dynamic When true the cell will be set as dynamic.	 */	setDynamic: function(dynamic) {		this.dynamic = dynamic;		if(this.dynamic && !this.updateEvent) {			this.updateEvent = new Core.Event("Core.Data.Cell:update");		}			},	/**	 * Adds the value of this cell to a HTML element.	 * @param {Core.Element} element The element to add the cell value to.	 * @param {String} url (Optional) When specified the value rendered as a link.	 * @method render	 */	render: function(element, url) {		if(this.type==Core.Data.PROGRESS) {			element.setHtml(this.getValue()+"%");		} else if(this.type==Core.Data.DATE) {			element.setHtml(this.getValue().format("dd-mmm-yyyy"));		} else if(this.type==Core.Data.ICON) {			var y = parseInt(this.getValue());			var x = parseInt(y/10);			y -= (x*10);			element.setHtml("<div class=\"inline-block icon\" style=\"background-position:-"+(x*14)+"px -"+(y*12)+"px;\"></div>");		} else if(url && url!="") {			element.setHtml("<a href=\""+url+"\" onclick=\"return false;\">"+this.getValue()+"</a>");		} else {			element.setHtml(this.getValue());		}	},	/**	 * Sets the indent for this cell	 * @param {Number} indent The level of indentation.	 * @method setIndent	 */	setIndent: function(indent) {		this.indent = indent;	},	/**	 * Gets the indent for this cell	 * @return {Number} The level of indentation.	 * @method getIndent	 */	getIndent: function() {		return this.indent;	},	toString: function() {		return this.type+" "+this.name+"=\""+this.value+"\"";	}});/** * <p>A Core.Data object that uses an array as it's data source.  The array should be an  * array of objects, each element in the array is added as a row and each property in the  * object is added as cell.</p><pre><code>var columns = new Core.Data.Array([  {    name: "name",    title: "Name",    width: "300px"  },  {    name: "size",    title: "Size (KB)",    width: "125px"  },  {    name: "modified",    title: "Modified",    width: "125px"  }]);</code></pre> * @class Core.Data.Array * @extends Core.Data */Core.Data.Array = Core.create(Core.Data, {	/**	 * Create a new Array based Core.Data object.	 * @method init	 * @constructor	 * @param {Array} data (Optional) When specified this array will be passed to the <code>load</code> method.	 */	init: function(data) {		Core.Data.prototype.init.call(this);				if(data) {			this.load(data);		}	},		/**	 * Responsible for adding data to the object.	 * @method load	 * @param {Array} data The data to load.	 */	load: function(data) {		this.resetData();				this.traverseArray(this, data);				this.fireUpdateEvent();	},	traverseArray: function (parent, data) {		for(var index=0; index<data.length; index++) {			var row = parent.addRow();			for(var name in data[index]) {				if(data[index][name].constructor.toString().indexOf("Array") != -1) {					this.traverseArray(row, data[index][name]);				} else {					row.addCell(Core.Data.STRING, name, data[index][name]);				}			}		}	}	});/** * <p>A Core.Data object that uses an array as it's data source.  The array should be  * a simple single dimentional array, each element is added as a row in the data object  * with a cell name of "data"</p><pre><code>var columns = new Core.Data.SimpleArray(["Apple", "Orange", "Banana"]);</code></pre> * @class Core.Data.SimpleArray * @extends Core.Data */Core.Data.SimpleArray = Core.create(Core.Data, {	/**	 * Create a new Array based Core.Data object.	 * @method init	 * @constructor	 * @param {Array} data (Optional) When specified this array will be passed to the <code>load</code> method.	 */	init: function(data) {		Core.Data.prototype.init.call(this);				if(data) {			this.load(data);		}	},		/**	 * Responsible for adding data to the object.	 * @method load	 * @param {Array} data The data to load.	 */	load: function(data) {		this.resetData();				this.traverseArray(this, data);				this.fireUpdateEvent();	},	traverseArray: function (parent, data) {		for(var index=0; index<data.length; index++) {			var row = parent.addRow();			row.addCell(Core.Data.STRING, "data", data[index]);		}	}	});/** * <p>A Core.Data object that uses XML as it's data source.  You can confugure how the *  XML is loaded into the data object by passing an options object to the construtor.  The options * object can have the following parameters:</p><pre>Option Name             Description======================  ========================================================url                     The url of the XML document to read.rootNode                The node name of the root element.rowContainer            The node name of the element that contains all the row                         elements, if unspecifed the root node is assumed to be                         the rowContainer.rowNode                 The node name of row nodes.positionAttribute       The row attribute that contains the rows position.positionSeperator       The charater seperating a multi level position.siblingsAttribute       The row attribute that contains the number of siblings                         this row has.childrenAttribute       The row attribute that contains the number of children                         this row has.cellAttributes          An array of attribute names that should extracted from                         the row element and stored as children.cellAttributeTypes      An array of data types defining the types of cells                         craeted with cellAttributes.cellContainer           The node name of the element that contains all the cell                         elements, if unspecifed the row node is assumed to be                         the cellContainer.cellNode                The node name of cell nodes.cellNodeTypeAttribute   The cell attribute that defines the type of the cell.cellNodeNameAttribute   The cell attribute that defines the name of the cell.cellNodeValueAttribute  The cell attribute that defined the value of the cell.cellMap                 An object to map cell names.  The property is the name                         of the cell in the xml, the value of that property is                        the name it should be stored as.subRowContainer         The node name of the element that contains all the sub                         row elements, if unspecifed the row node is assumed to                        be the rowContainer.</pre> * <p>A valid coniguration needs to define at least the <code>url</code>  * <code>rootNode</code>, <code>rowNode</code> and <code>cellAttributes</code> * or <code>cellNode</code>.</p> * <h5>Example</h5><p>This is an example XML file and the code required to load it</p> <pre><code>&lt;?xml version="1.0" encoding="UTF-8"?&gt;&lt;table&gt;  &lt;columns&gt;    &lt;col id="name" title="Name" width="300px"/&gt;    &lt;col id="size" title="Size (KB)" width="125px"/&gt;    &lt;col id="modified" title="Modified" width="125px"/&gt;  &lt;/columns&gt;&lt;/table&gt;</code></pre><pre><code>var columns = new Core.Data.XML({  rootNode: "table";  rowContainer: "columns";  rowNode: "col";  cellAttributes: ["id", "size", "modified"];  cellMap: {id: "name"}});</code></pre> * @class Core.Data.XML * @extends Core.Data */Core.Data.XML = Core.create(Core.Data, {	/**	 * <p>Create a new XML based Core.Data object.	 * @method init	 * @constructor	 * @param Object options The configuration of this XML data source. See comments above.	 */	init: function(options) {		Core.Data.prototype.init.call(this);			Core.copyNew(options, Core.Options);				this.url = options.readOption("url", "");				this.rootNodeName = options.readOption("rootNode", "");		this.rowContainerName = options.readOption("rowContainer", "");		this.rowNodeName = options.readOption("rowNode", "");		this.positionAttributeName = options.readOption("positionAttribute", "");		this.positionSeperator = options.readOption("positionSeperator", ".");		this.siblingsAttributeName = options.readOption("siblingsAttribute", "");		this.childrenAttributeName = options.readOption("childrenAttribute", "");		this.cellAttributeNames = options.readOption("cellAttributes", new Array());		this.cellAttributeTypes = options.readOption("cellAttributeTypes", new Array());		this.cellContainerName = options.readOption("cellContainer", "");		this.cellNodeName = options.readOption("cellNode", "");		this.cellNode_typeAttributeName = options.readOption("cellNodeTypeAttribute", "");		this.cellNode_nameAttributeName = options.readOption("cellNodeNameAttribute", "");		this.cellNode_valueAttributeName = options.readOption("cellNodeValueAttribute", "");		this.cellMap = options.readOption("cellMap", {});		this.subRowContainerName = options.readOption("subRowContainer", "");				this.validStructure = (	this.rootNodeName!="" && 										this.rowNodeName!="" && 										(this.cellAttributeNames.length>0 || this.cellNodeName!=""));	},		/**	 * Responsible for adding data to the object.	 * @method load	 */	load: function() {		if(this.url!="") {			Core.addRequest("GET", this.url, "", this.loadData.bind(this, [true, true]));		}	},	/**	 * This is called by the <code>load</code> method when the HTTP request for the xml file returns.	 * @method loadData	 * @param Object event The event information object, in this case will be a Core.HttpManager response event.	 * @param Boolean reset When true <code>resetData</code>will be called.	 * @param Boolean fire When true <code>fireUpdateEvent</code>will be called.	 */	loadData: function(event, reset, fire) {		var rowsLoaded = 0;				if(reset) {			this.resetData();		}			if(this.validStructure && event && event.request && event.request.responseXML) {			var xml = event.request.responseXML;				var rootNode = Core.XML.findFirstChild(xml, this.rootNodeName);			if(rootNode!=null) {				var rowContainer = this.rowContainerName=="" ? rootNode : Core.XML.findFirstChild(rootNode, this.rowContainerName);				if(rowContainer!=null) {					var rowNode = Core.XML.findFirstChild(rowContainer, this.rowNodeName);					while(rowNode!=null) {						if(this.loadRow(this, rowNode)!=null) {							rowsLoaded++;						}						rowNode = Core.XML.findNextChild(rowNode, this.rowNodeName);					}				}			}		}				if(fire) {			this.fireUpdateEvent();		}				return rowsLoaded;	},	/*	 * Called by loadData to load a row.	 * @method loadRow	 * @param {Core.Data.Row} parent The parent to add this row to.	 * @param DOMElement rowNode The DOM node for this new row.	 * @param {Core.Data.Row} This row just loaded or null if there where any problems or the row was filtered.	 */	loadRow: function(parent, rowNode) {		var position = Core.XML.getAttribute(rowNode, this.positionAttributeName, null);		var row = parent.addRow(position);				if(this.siblingsAttributeName!="") {			row.setSiblings(parseInt(Core.XML.getAttribute(rowNode, this.siblingsAttributeName, 0)));		}		if(this.childrenAttributeName!="") {			row.setChildren(parseInt(Core.XML.getAttribute(rowNode, this.childrenAttributeName, 0)));		}				// load cell attributes				for(var index=0; index<this.cellAttributeNames.length; index++) {			this.loadCellAttribute(row, rowNode, this.cellAttributeNames[index], this.cellAttributeTypes[index]);		}		// load cell children		if(this.cellNodeName!="") {			var cellContainer = this.cellContainerName=="" ? rowNode : Core.XML.findFirstChild(rowNode, this.cellContainerName);			if(cellContainer!=null) {				var cellNode = Core.XML.findFirstChild(cellContainer, this.cellNodeName);				while(cellNode!=null) {					this.loadCell(row, cellNode);					cellNode = Core.XML.findNextChild(cellNode, this.cellNodeName);				}			}		}						// sub rows		var rowContainer = this.subRowContainerName!="" ? Core.XML.findFirstChild(rowNode, this.subRowContainerName) : this.rowContainerName!="" ? Core.XML.findFirstChild(rowNode, this.rowContainerName) : rowNode;		if(rowContainer!=null) {			var subRowNode = Core.XML.findFirstChild(rowContainer, this.rowNodeName);			while(subRowNode!=null) {				this.loadRow(row, subRowNode);				subRowNode = Core.XML.findNextChild(subRowNode, this.rowNodeName);			}		}				return row;	},	/*	 * Called by loadRow to load a cell from attributes	 * @method loadRow	 * @param {Core.Data.Row} row The row to add this cell to	 * @param DOMElement rowNode The DOM node for the row.	 * @param {String} attributeName Attribute name.	 * @param {Number} type Attribute type.	 */	loadCellAttribute: function(row, rowNode, attributeName, type) {		var value = Core.XML.getAttribute(rowNode, attributeName, null);		if(typeof(type)=="undefined") {			type = Core.Data.STRING;		} 		if(value!=null) {			row.addCell(type, this.getCellName(attributeName), value);		}	},	/*	 * Called by loadRow to load a cell form a cell node	 * @method loadRow	 * @param {Core.Data.Row} row The row to add this cell to	 * @param DOMElement cellNode The DOM node for this new cell.	 */	loadCell: function(row, cellNode) {		var type = Core.XML.getAttribute(cellNode, this.cellNode_typeAttributeName, Core.Data.STRING);		var name = Core.XML.getAttribute(cellNode, this.cellNode_nameAttributeName, "");		var value = Core.XML.getAttribute(cellNode, this.cellNode_valueAttributeName, "");				if(typeof(Core.Data[type])!="undefined") {			type = Core.Data[type];		}				row.addCell(type, this.getCellName(name), value);	},	/*	 * Called by loadCell and loadCellAttribute to get the correct name for this cell using the cell map	 * @method getCellName	 * @param {String} name The name of the cell in the XML	 * @return {String} The name this cell should be in the data object.	 */	getCellName: function(name) {		if(this.cellMap[name]) {			return this.cellMap[name];		} else {			return name;		}	}});/** * <p>A Core.Data object that uses anouther Core.Data object and reorganises the data using a  * cell as a new position value.  You can confugure how the data is translated by passing an options  * object to the construtor.  The options object can have the following parameters:</p><pre>Option Name             Description======================  ========================================================data                    The source data object.positionCell            The cells defines the new position.positionSeperator       The charater seperating a multi level position.</pre> * <p>A valid coniguration needs to define at least the <code>data</code> and * <code>positionCell</code></p> * @class Core.Data.Translation * @extends Core.Data */Core.Data.Translation = Core.create(Core.Data, {	init: function(options) {		Core.Data.prototype.init.call(this);			Core.copyNew(options, Core.Options);				data = options.readOption("data", null);		this.positionCellName = options.readOption("positionCell", "");		this.positionSeperator = options.readOption("positionSeperator", ".");		this.newPositionCellName = options.readOption("newPositionCell", this.positionCellName);		this.indent = options.readBooleanOption("indent", false);						if(data!=null && this.positionCellName!="") {			data.addListeners(this.load.bind(this, [true]), this.load.bind(this, [false]));		}		},	/**	 * Responsible for adding data to the object.  This will be called automatily every time the source data is updated.	 * @method load	 * @param Object event The update event data from the source data.	 * @param Boolean reset When true will reset the data source.	 */	load: function(event, reset) {		var data;		if(reset) {			this.resetData();			data = event.data.getRows();		} else {			data = event.newRows;		}				for(var index=0; index<data.length; index++) {			var positions = data[index].getValue(this.positionCellName).split(this.positionSeperator);							// add row					var row = this.addRow(data[index].getValue(this.positionCellName));						var cells = data[index].getCellNames();			for(var cell=0; cell<cells.length; cell++) {				var oldCell = data[index].getCell(cells[cell]);				if(cells[cell] != this.newPositionCellName) {					row.addCell(Core.Data.CUSTOM, cells[cell], oldCell);				}			}			var cell = row.addCell(Core.Data.STRING, this.newPositionCellName, positions[positions.length-1]);			if(this.indent) {				cell.setIndent(positions.length-1);			}									// fill in sub row positions			for(var pos=0; pos<positions.length-1; pos++) {				var row = this.getRow(positions.slice(0, pos+1).join(this.positionSeperator));				if(row!=null) {					if(!row.hasCell(this.newPositionCellName)) {						var cell = row.addCell(Core.Data.STRING, this.newPositionCellName, positions[pos]);						if(this.indent) {							cell.setIndent(pos);						}					}				}			}				}				this.fireUpdateEvent();	}	});
