/** * @class YAHOO.ext.grid.XMLDataModel * This is an implementation of a DataModel used by the Grid. It works * with XML data. *
Example schema from Amazon search: *

 * var schema = {
 *     tagName: 'Item',
 *     id: 'ASIN',
 *     fields: ['Author', 'Title', 'Manufacturer', 'ProductGroup']
 * };
 * 
* @extends YAHOO.ext.grid.LoadableDataModel * @constructor * @param {Object} schema The schema to use * @param {XMLDocument} xml An XML document to load immediately */ YAHOO.ext.grid.XMLDataModel = function(schema, xml){ YAHOO.ext.grid.XMLDataModel.superclass.constructor.call(this, YAHOO.ext.grid.LoadableDataModel.XML); /**@private*/ this.schema = schema; this.xml = xml; if(xml){ this.loadData(xml); } this.idSeed = 0; }; YAHOO.extendX(YAHOO.ext.grid.XMLDataModel, YAHOO.ext.grid.LoadableDataModel, { getDocument: function(){ return this.xml; }, /** * Overrides loadData in LoadableDataModel to process XML * @param {XMLDocument} doc The document to load * @param {Function} callback (optional) callback to call when loading is complete * @param {Boolean} keepExisting (optional) true to keep existing data * @param {Number} insertIndex (optional) if present, loaded data is inserted at the specified index instead of overwriting existing data */ loadData: function(doc, callback, keepExisting, insertIndex){ this.xml = doc; var idField = this.schema.id; var fields = this.schema.fields; if(this.schema.totalTag){ this.totalCount = null; var totalNode = doc.getElementsByTagName(this.schema.totalTag); if(totalNode && totalNode.item(0) && totalNode.item(0).firstChild) { var v = parseInt(totalNode.item(0).firstChild.nodeValue, 10); if(!isNaN(v)){ this.totalCount = v; } } } var rowData = []; var nodes = doc.getElementsByTagName(this.schema.tagName); if(nodes && nodes.length > 0) { for(var i = 0; i < nodes.length; i++) { var node = nodes.item(i); var colData = []; colData.node = node; colData.id = this.getNamedValue(node, idField, String(++this.idSeed)); for(var j = 0; j < fields.length; j++) { var val = this.getNamedValue(node, fields[j], ""); if(this.preprocessors[j]){ val = this.preprocessors[j](val); } colData.push(val); } rowData.push(colData); } } if(keepExisting !== true){ YAHOO.ext.grid.XMLDataModel.superclass.removeAll.call(this); } if(typeof insertIndex != 'number'){ insertIndex = this.getRowCount(); } YAHOO.ext.grid.XMLDataModel.superclass.insertRows.call(this, insertIndex, rowData); if(typeof callback == 'function'){ callback(this, true); } this.fireLoadEvent(); }, /** * Adds a row to this DataModel and syncs the XML document * @param {String} id The id of the row, if null the next row index is used * @param {Array} cellValues The cell values for this row * @return {Number} The index of the new row (if the model is sorted this index may not be accurate) */ addRow: function(id, cellValues){ var node = this.createNode(this.xml, id, cellValues); cellValues.id = id || ++this.idSeed; cellValues.node = node; return YAHOO.ext.grid.XMLDataModel.superclass.addRow.call(this, cellValues); }, /** * Inserts a row into this DataModel and syncs the XML document * @param {Number} index The index to insert the row * @param {String} id The id of the row, if null the next row index is used * @param {Array} cellValues The cell values for this row * @return {Number} The index of the new row (if the model is sorted this index may not be accurate) */ insertRow: function(index, id, cellValues){ var node = this.createNode(this.xml, id, cellValues); cellValues.id = id || ++this.idSeed; cellValues.node = node; return YAHOO.ext.grid.XMLDataModel.superclass.insertRow.call(this, index, cellValues); }, /** * Removes the row from DataModel and syncs the XML document * @param {Number} index The index of the row to remove */ removeRow: function(index){ var node = this.data[index].node; node.parentNode.removeChild(node); YAHOO.ext.grid.XMLDataModel.superclass.removeRow.call(this, index, index); }, getNode: function(rowIndex){ return this.data[rowIndex].node; }, /** * Override this method to define your own node creation routine for when new rows are added. * By default this method clones the first node and sets the column values in the newly cloned node. * In many instances this will not work and you will have to create the node manually. * @param {XMLDocument} xmlDoc The xml document being used by this model * @param {String/Number} id The row id * @param {Array} colData The column data for the new node * @return {XMLNode} The created node */ createNode: function(xmlDoc, id, colData){ var template = this.data[0].node; var newNode = template.cloneNode(true); var fields = this.schema.fields; for(var i = 0, len = fields.length; i < len; i++){ var nodeValue = colData[i]; if(this.postprocessors[i]){ nodeValue = this.postprocessors[i](nodeValue); } this.setNamedValue(newNode, fields[i], nodeValue); } if(id){ this.setNamedValue(newNode, this.schema.idField, id); } template.parentNode.appendChild(newNode); return newNode; }, /** * @private * Convenience function looks for value in attributes, then in children tags - also * normalizes namespace matches (ie matches ns:tag, FireFox matches tag and not ns:tag). */ getNamedValue: function(node, name, defaultValue){ if(!node || !name){ return defaultValue; } var nodeValue = defaultValue; var attrNode = node.attributes.getNamedItem(name); if(attrNode) { nodeValue = attrNode.value; } else { var childNode = node.getElementsByTagName(name); if(childNode && childNode.item(0) && childNode.item(0).firstChild) { nodeValue = childNode.item(0).firstChild.nodeValue; }else{ // try to strip namespace for FireFox var index = name.indexOf(':'); if(index > 0){ return this.getNamedValue(node, name.substr(index+1), defaultValue); } } } return nodeValue; }, /** * @private * Convenience function set a value in the underlying xml node. */ setNamedValue: function(node, name, value){ if(!node || !name){ return; } var attrNode = node.attributes.getNamedItem(name); if(attrNode) { attrNode.value = value; return; } var childNode = node.getElementsByTagName(name); if(childNode && childNode.item(0) && childNode.item(0).firstChild) { childNode.item(0).firstChild.nodeValue = value; }else{ // try to strip namespace for FireFox var index = name.indexOf(':'); if(index > 0){ this.setNamedValue(node, name.substr(index+1), value); } } }, /** * Overrides DefaultDataModel.setValueAt to update the underlying XML Document * @param {Object} value The new value * @param {Number} rowIndex * @param {Number} colIndex */ setValueAt: function(value, rowIndex, colIndex){ var node = this.data[rowIndex].node; if(node){ var nodeValue = value; if(this.postprocessors[colIndex]){ nodeValue = this.postprocessors[colIndex](value); } this.setNamedValue(node, this.schema.fields[colIndex], nodeValue); } YAHOO.ext.grid.XMLDataModel.superclass.setValueAt.call(this, value, rowIndex, colIndex); }, /** * Overrides getRowId in DefaultDataModel to return the ID value of the specified node. * @param {Number} rowIndex * @return {Number} */ getRowId: function(rowIndex){ return this.data[rowIndex].id; }, addRows : function(rowData){ for(var j = 0, len = rowData.length; j < len; j++){ var cellValues = rowData[j]; var id = ++this.idSeed; var node = this.createNode(this.xml, id, cellValues); cellValues.node=node; cellValues.id = cellValues.id || id; YAHOO.ext.grid.XMLDataModel.superclass.addRow.call(this,cellValues); } }, insertRows : function(index, rowData){ // copy original array so it is not reversed rowData = rowData.slice(0).reverse(); for(var j = 0, len = rowData.length; j < len; j++){ var cellValues = rowData[j]; var id = ++this.idSeed; var node = this.createNode(this.xml, id, cellValues); cellValues.id = cellValues.id || id; cellValues.node = node; YAHOO.ext.grid.XMLDataModel.superclass.insertRow.call(this, index, cellValues); } } }); YAHOO.ext.grid.XMLQueryDataModel = function(){ YAHOO.ext.grid.XMLQueryDataModel.superclass.constructor.apply(this, arguments); }; YAHOO.extendX(YAHOO.ext.grid.XMLQueryDataModel, YAHOO.ext.grid.XMLDataModel, { getNamedValue: function(node, name, defaultValue){ if(!node || !name){ return defaultValue; } var nodeValue = defaultValue; var childNode = cssQuery(name, node); if(childNode && childNode[0]) { nodeValue = childNode[0].firstChild.nodeValue; } return nodeValue; } });