summaryrefslogtreecommitdiff
path: root/frontend/beta/js/YUI-extensions/data
Side-by-side diff
Diffstat (limited to 'frontend/beta/js/YUI-extensions/data') (more/less context) (show whitespace changes)
-rw-r--r--frontend/beta/js/YUI-extensions/data/AbstractDataModel.js226
-rw-r--r--frontend/beta/js/YUI-extensions/data/DefaultDataModel.js339
-rw-r--r--frontend/beta/js/YUI-extensions/data/JSONDataModel.js81
-rw-r--r--frontend/beta/js/YUI-extensions/data/LoadableDataModel.js330
-rw-r--r--frontend/beta/js/YUI-extensions/data/Tree.js412
-rw-r--r--frontend/beta/js/YUI-extensions/data/XMLDataModel.js274
6 files changed, 1662 insertions, 0 deletions
diff --git a/frontend/beta/js/YUI-extensions/data/AbstractDataModel.js b/frontend/beta/js/YUI-extensions/data/AbstractDataModel.js
new file mode 100644
index 0000000..092ea75
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/data/AbstractDataModel.js
@@ -0,0 +1,226 @@
+/**
+ * @class YAHOO.ext.grid.AbstractDataModel
+ * @extends YAHOO.ext.util.Observable
+ * This abstract class provides default implementations of the events required by the Grid.
+ It takes care of the creating the CustomEvents and provides some convenient methods for firing the events. <br><br>
+ * @constructor
+*/
+YAHOO.ext.grid.AbstractDataModel = function(){
+ /** Fires when a cell is updated - fireDirect sig: (this, rowIndex, columnIndex)
+ * @private
+ * @type YAHOO.util.CustomEvent
+ * @deprecated Use addListener instead of accessing directly
+ */
+ this.onCellUpdated = new YAHOO.util.CustomEvent('onCellUpdated');
+ /** Fires when all data needs to be revalidated - fireDirect sig: (thisd)
+ * @private
+ * @type YAHOO.util.CustomEvent
+ * @deprecated Use addListener instead of accessing directly
+ */
+ this.onTableDataChanged = new YAHOO.util.CustomEvent('onTableDataChanged');
+ /** Fires when rows are deleted - fireDirect sig: (this, firstRowIndex, lastRowIndex)
+ * @private
+ * @type YAHOO.util.CustomEvent
+ * @deprecated Use addListener instead of accessing directly
+ */
+ this.onRowsDeleted = new YAHOO.util.CustomEvent('onRowsDeleted');
+ /** Fires when a rows are inserted - fireDirect sig: (this, firstRowIndex, lastRowIndex)
+ * @private
+ * @type YAHOO.util.CustomEvent
+ * @deprecated Use addListener instead of accessing directly
+ */
+ this.onRowsInserted = new YAHOO.util.CustomEvent('onRowsInserted');
+ /** Fires when a rows are updated - fireDirect sig: (this, firstRowIndex, lastRowIndex)
+ * @private
+ * @type YAHOO.util.CustomEvent
+ * @deprecated Use addListener instead of accessing directly
+ */
+ this.onRowsUpdated = new YAHOO.util.CustomEvent('onRowsUpdated');
+ /** Fires when a sort has reordered the rows - fireDirect sig: (this, sortColumnIndex,
+ * @private
+ * sortDirection = 'ASC' or 'DESC')
+ * @type YAHOO.util.CustomEvent
+ * @deprecated Use addListener instead of accessing directly
+ */
+ this.onRowsSorted = new YAHOO.util.CustomEvent('onRowsSorted');
+
+ this.events = {
+ /**
+ * @event cellupdated
+ * Fires when a cell is updated
+ * @param {DataModel} this
+ * @param {Number} rowIndex
+ * @param {Number} columnIndex
+ */
+ 'cellupdated' : this.onCellUpdated,
+ /**
+ * @event datachanged
+ * Fires when the entire data structure has changed
+ * @param {DataModel} this
+ */
+ 'datachanged' : this.onTableDataChanged,
+ /**
+ * @event rowsdeleted
+ * Fires when a range of rows have been deleted
+ * @param {DataModel} this
+ * @param {Number} firstRowIndex
+ * @param {Number} lastRowIndex
+ */
+ 'rowsdeleted' : this.onRowsDeleted,
+ /**
+ * @event rowsinserted
+ * Fires when a range of rows have been inserted
+ * @param {DataModel} this
+ * @param {Number} firstRowIndex
+ * @param {Number} lastRowIndex
+ */
+ 'rowsinserted' : this.onRowsInserted,
+ /**
+ * @event rowsupdated
+ * Fires when a range of rows have been updated
+ * @param {DataModel} this
+ * @param {Number} firstRowIndex
+ * @param {Number} lastRowIndex
+ */
+ 'rowsupdated' : this.onRowsUpdated,
+ /**
+ * @event rowssorted
+ * Fires when the data has been sorted
+ * @param {DataModel} this
+ */
+ 'rowssorted' : this.onRowsSorted
+ };
+};
+
+YAHOO.ext.grid.AbstractDataModel.prototype = {
+
+ fireEvent : YAHOO.ext.util.Observable.prototype.fireEvent,
+ on : YAHOO.ext.util.Observable.prototype.on,
+ addListener : YAHOO.ext.util.Observable.prototype.addListener,
+ delayedListener : YAHOO.ext.util.Observable.prototype.delayedListener,
+ removeListener : YAHOO.ext.util.Observable.prototype.removeListener,
+ purgeListeners : YAHOO.ext.util.Observable.prototype.purgeListeners,
+ bufferedListener : YAHOO.ext.util.Observable.prototype.bufferedListener,
+
+ /**
+ * Notifies listeners that the value of the cell at [row, col] has been updated
+ * @deprecated
+ * @private
+ */
+ fireCellUpdated : function(row, col){
+ this.onCellUpdated.fireDirect(this, row, col);
+ },
+
+ /**
+ * Notifies listeners that all data for the grid may have changed - use as a last resort. This
+ * also wipes out all selections a user might have made.
+ * @deprecated
+ * @private
+ */
+ fireTableDataChanged : function(){
+ this.onTableDataChanged.fireDirect(this);
+ },
+
+ /**
+ * Notifies listeners that rows in the range [firstRow, lastRow], inclusive, have been deleted
+ * @deprecated
+ * @private
+ */
+ fireRowsDeleted : function(firstRow, lastRow){
+ this.onRowsDeleted.fireDirect(this, firstRow, lastRow);
+ },
+
+ /**
+ * Notifies listeners that rows in the range [firstRow, lastRow], inclusive, have been inserted
+ * @deprecated
+ * @private
+ */
+ fireRowsInserted : function(firstRow, lastRow){
+ this.onRowsInserted.fireDirect(this, firstRow, lastRow);
+ },
+
+ /**
+ * Notifies listeners that rows in the range [firstRow, lastRow], inclusive, have been updated
+ * @deprecated
+ * @private
+ */
+ fireRowsUpdated : function(firstRow, lastRow){
+ this.onRowsUpdated.fireDirect(this, firstRow, lastRow);
+ },
+
+ /**
+ * Notifies listeners that rows have been sorted and any indexes may be invalid
+ * @deprecated
+ * @private
+ */
+ fireRowsSorted : function(sortColumnIndex, sortDir, noRefresh){
+ this.onRowsSorted.fireDirect(this, sortColumnIndex, sortDir, noRefresh);
+ },
+
+ /**
+ * Empty interface method - Classes which extend AbstractDataModel should implement this method.
+ * See {@link YAHOO.ext.DefaultDataModel#sort} for an example implementation.
+ * @private
+ */
+ sort : function(sortInfo, columnIndex, direction, suppressEvent){
+
+ },
+
+ /**
+ * Interface method to supply info regarding the Grid's current sort state - if overridden,
+ * this should return an object like this {column: this.sortColumn, direction: this.sortDir}.
+ * @return {Object}
+ */
+ getSortState : function(){
+ return {column: this.sortColumn, direction: this.sortDir};
+ },
+
+ /**
+ * Empty interface method - Classes which extend AbstractDataModel should implement this method.
+ * See {@link YAHOO.ext.DefaultDataModel} for an example implementation.
+ * @private
+ */
+ getRowCount : function(){
+
+ },
+
+ /**
+ * Empty interface method - Classes which extend AbstractDataModel should implement this method to support virtual row counts.
+ * @private
+ */
+ getTotalRowCount : function(){
+ return this.getRowCount();
+ },
+
+
+ /**
+ * Empty interface method - Classes which extend AbstractDataModel should implement this method.
+ * See {@link YAHOO.ext.DefaultDataModel} for an example implementation.
+ * @private
+ */
+ getRowId : function(rowIndex){
+
+ },
+
+ /**
+ * Empty interface method - Classes which extend AbstractDataModel should implement this method.
+ * See {@link YAHOO.ext.DefaultDataModel} for an example implementation.
+ * @private
+ */
+ getValueAt : function(rowIndex, colIndex){
+
+ },
+
+ /**
+ * Empty interface method - Classes which extend AbstractDataModel should implement this method.
+ * See {@link YAHOO.ext.DefaultDataModel} for an example implementation.
+ * @private
+ */
+ setValueAt : function(value, rowIndex, colIndex){
+
+ },
+
+ isPaged : function(){
+ return false;
+ }
+};
diff --git a/frontend/beta/js/YUI-extensions/data/DefaultDataModel.js b/frontend/beta/js/YUI-extensions/data/DefaultDataModel.js
new file mode 100644
index 0000000..57a022a
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/data/DefaultDataModel.js
@@ -0,0 +1,339 @@
+
+/**
+ * @class YAHOO.ext.grid.DefaultDataModel
+ * This is the default implementation of a DataModel used by the Grid. It works
+ * with multi-dimensional array based data. Using the event system in the base class
+ * {@link YAHOO.ext.grid.AbstractDataModel}, all updates to this DataModel are automatically
+ * reflected in the user interface.
+ * <br>Usage:<br>
+ * <pre><code>
+ * var myData = [
+ ["MSFT","Microsoft Corporation", "314,571.156", "32,187.000", "55000"],
+ ["ORCL", "Oracle Corporation", "62,615.266", "9,519.000", "40650"]
+ * ];
+ * var dataModel = new YAHOO.ext.grid.DefaultDataModel(myData);
+ * </code></pre>
+ * @extends YAHOO.ext.grid.AbstractDataModel
+ * @constructor
+ * @param {Array} data
+*/
+YAHOO.ext.grid.DefaultDataModel = function(data){
+ YAHOO.ext.grid.DefaultDataModel.superclass.constructor.call(this);
+ /**@private*/
+ this.data = data;
+};
+YAHOO.extendX(YAHOO.ext.grid.DefaultDataModel, YAHOO.ext.grid.AbstractDataModel, {
+ /**
+ * Returns the number of rows in the dataset
+ * @return {Number}
+ */
+ getRowCount : function(){
+ return this.data.length;
+ },
+
+ /**
+ * Returns the ID of the specified row. By default it return the value of the first column.
+ * Override to provide more advanced ID handling.
+ * @return {Number}
+ */
+ getRowId : function(rowIndex){
+ return this.data[rowIndex][0];
+ },
+
+ /**
+ * Returns the column data for the specified row.
+ * @return {Array}
+ */
+ getRow : function(rowIndex){
+ return this.data[rowIndex];
+ },
+
+ /**
+ * Returns the column data for the specified rows as a
+ * multi-dimensional array: rows[3][0] would give you the value of row 4, column 0.
+ * @param {Array} indexes The row indexes to fetch
+ * @return {Array}
+ */
+ getRows : function(indexes){
+ var data = this.data;
+ var r = [];
+ for(var i = 0; i < indexes.length; i++){
+ r.push(data[indexes[i]]);
+ }
+ return r;
+ },
+
+ /**
+ * Returns the value at the specified data position
+ * @param {Number} rowIndex
+ * @param {Number} colIndex
+ * @return {Object}
+ */
+ getValueAt : function(rowIndex, colIndex){
+ return this.data[rowIndex][colIndex];
+ },
+
+ /**
+ * Sets the specified value at the specified data position
+ * @param {Object} value The new value
+ * @param {Number} rowIndex
+ * @param {Number} colIndex
+ */
+ setValueAt: function(value, rowIndex, colIndex){
+ this.data[rowIndex][colIndex] = value;
+ this.fireCellUpdated(rowIndex, colIndex);
+ },
+
+ /**
+ * @private
+ * Removes the specified range of rows.
+ * @param {Number} startIndex
+ * @param {<i>Number</i>} endIndex (optional) Defaults to startIndex
+ */
+ removeRows: function(startIndex, endIndex){
+ endIndex = endIndex || startIndex;
+ this.data.splice(startIndex, endIndex-startIndex+1);
+ this.fireRowsDeleted(startIndex, endIndex);
+ },
+
+ /**
+ * Remove a row.
+ * @param {Number} index
+ */
+ removeRow: function(index){
+ this.data.splice(index, 1);
+ this.fireRowsDeleted(index, index);
+ },
+
+ /**
+ * @private
+ * Removes all rows.
+ */
+ removeAll: function(){
+ var count = this.getRowCount();
+ if(count > 0){
+ this.removeRows(0, count-1);
+ }
+ },
+
+ /**
+ * Query the DataModel rows by the filters defined in spec, for example...
+ * <pre><code>
+ * // column 1 starts with Jack, column 2 filtered by myFcn, column 3 equals 'Fred'
+ * dataModel.filter({1: /^Jack.+/i}, 2: myFcn, 3: 'Fred'});
+ * </code></pre>
+ * @param {Object} spec The spec is generally an object literal consisting of
+ * column index and filter type. The filter type can be a string/number (exact match),
+ * a regular expression to test using String.search() or a function to call. If it's a function,
+ * it will be called with the value for the specified column and an array of the all column
+ * values for that row: yourFcn(value, columnData). If it returns anything other than true,
+ * the row is not a match. If you have modified Object.prototype this method may fail.
+ * @param {Boolean} returnUnmatched True to return rows which <b>don't</b> match the query instead
+ * of rows that do match
+ * @return {Array} An array of row indexes that match
+ */
+ query: function(spec, returnUnmatched){
+ var d = this.data;
+ var r = [];
+ for(var i = 0; i < d.length; i++){
+ var row = d[i];
+ var isMatch = true;
+ for(var col in spec){
+ //if(typeof spec[col] != 'function'){
+ if(!isMatch) continue;
+ var filter = spec[col];
+ switch(typeof filter){
+ case 'string':
+ case 'number':
+ case 'boolean':
+ if(row[col] != filter){
+ isMatch = false;
+ }
+ break;
+ case 'function':
+ if(!filter(row[col], row)){
+ isMatch = false;
+ }
+ break;
+ case 'object':
+ if(filter instanceof RegExp){
+ if(String(row[col]).search(filter) === -1){
+ isMatch = false;
+ }
+ }
+ break;
+ }
+ //}
+ }
+ if(isMatch && !returnUnmatched){
+ r.push(i);
+ }else if(!isMatch && returnUnmatched){
+ r.push(i);
+ }
+ }
+ return r;
+ },
+
+ /**
+ * Filter the DataModel rows by the query defined in spec, see {@link #query} for more details
+ * on the query spec.
+ * @param {Object} query The query spec {@link #query}
+ * @return {Number} The number of rows removed
+ */
+ filter: function(query){
+ var matches = this.query(query, true);
+ var data = this.data;
+ // go thru the data setting matches to deleted
+ // while not disturbing row indexes
+ for(var i = 0; i < matches.length; i++){
+ data[matches[i]]._deleted = true;
+ }
+ for(var i = 0; i < data.length; i++){
+ while(data[i] && data[i]._deleted === true){
+ this.removeRow(i);
+ }
+ }
+ return matches.length;
+ },
+
+ /**
+ * Adds a row to the dataset.
+ * @param {Array} cellValues The array of values for the new row
+ * @return {Number} The index of the added row
+ */
+ addRow: function(cellValues){
+ this.data.push(cellValues);
+ var newIndex = this.data.length-1;
+ this.fireRowsInserted(newIndex, newIndex);
+ this.applySort();
+ return newIndex;
+ },
+
+ /**
+ * @private
+ * Adds a set of rows.
+ * @param {Array} rowData This should be an array of arrays like the constructor takes
+ */
+ addRows: function(rowData){
+ this.data = this.data.concat(rowData);
+ var firstIndex = this.data.length-rowData.length;
+ this.fireRowsInserted(firstIndex, firstIndex+rowData.length-1);
+ this.applySort();
+ },
+
+ /**
+ * Inserts a row a the specified location in the dataset.
+ * @param {Number} index The index where the row should be inserted
+ * @param {Array} cellValues The array of values for the new row
+ * @return {Number} The index the row was inserted in
+ */
+ insertRow: function(index, cellValues){
+ this.data.splice(index, 0, cellValues);
+ this.fireRowsInserted(index, index);
+ this.applySort();
+ return index;
+ },
+
+ /**
+ * @private
+ * Inserts a set of rows.
+ * @param {Number} index The index where the rows should be inserted
+ * @param {Array} rowData This should be an array of arrays like the constructor takes
+ */
+ insertRows: function(index, rowData){
+ /*
+ if(index == this.data.length){ // try these two first since they are faster
+ this.data = this.data.concat(rowData);
+ }else if(index == 0){
+ this.data = rowData.concat(this.data);
+ }else{
+ var newData = this.data.slice(0, index);
+ newData.concat(rowData);
+ newData.concat(this.data.slice(index));
+ this.data = newData;
+ }*/
+ var args = rowData.concat();
+ args.splice(0, 0, index, 0);
+ this.data.splice.apply(this.data, args);
+ this.fireRowsInserted(index, index+rowData.length-1);
+ this.applySort();
+ },
+
+ /**
+ * Applies the last used sort to the current data.
+ */
+ applySort: function(suppressEvent){
+ if(typeof this.sortColumn != 'undefined'){
+ this.sort(this.sortInfo, this.sortColumn, this.sortDir, suppressEvent);
+ }
+ },
+
+ /**
+ * Sets the default sort info. Note: this function does not actually apply the sort.
+ * @param {Function/Object} sortInfo A sort comparison function or null to use the default or A object that has a method getSortType(index) that returns a function like
+ * a grid column model.
+ * @param {Number} columnIndex The column index to sort by
+ * @param {String} direction The direction of the sort ('DESC' or 'ASC')
+ */
+ setDefaultSort: function(sortInfo, columnIndex, direction){
+ this.sortInfo = sortInfo;
+ this.sortColumn = columnIndex;
+ this.sortDir = direction;
+ },
+ /**
+ * Sorts the data by the specified column - Uses the sortType specified for the column in the passed columnModel.
+ * @param {Function/Object} sortInfo A sort comparison function or null to use the default or A object that has a method getSortType(index) that returns a function like
+ * a grid column model.
+ * @param {Number} columnIndex The column index to sort by
+ * @param {String} direction The direction of the sort ('DESC' or 'ASC')
+ */
+ sort: function(sortInfo, columnIndex, direction, suppressEvent){
+ // store these so we can maintain sorting when we load new data
+ this.sortInfo = sortInfo;
+ this.sortColumn = columnIndex;
+ this.sortDir = direction;
+
+ var dsc = (direction && direction.toUpperCase() == 'DESC');
+ var sortType = null;
+ if(sortInfo != null){
+ if(typeof sortInfo == 'function'){
+ sortType = sortInfo;
+ }else if(typeof sortInfo == 'object'){
+ sortType = sortInfo.getSortType(columnIndex);;
+ }
+ }
+ var fn = function(cells, cells2){
+ var v1 = sortType ? sortType(cells[columnIndex], cells) : cells[columnIndex];
+ var v2 = sortType ? sortType(cells2[columnIndex], cells2) : cells2[columnIndex];
+ if(v1 < v2)
+ return dsc ? +1 : -1;
+ if(v1 > v2)
+ return dsc ? -1 : +1;
+ return 0;
+ };
+ this.data.sort(fn);
+ if(!suppressEvent){
+ this.fireRowsSorted(columnIndex, direction);
+ }
+ },
+
+ /**
+ * Calls passed function with each rows data - if the function returns false it stops.
+ * @param {Function} fn
+ * @param {Object} scope (optional)
+ */
+ each: function(fn, scope){
+ var d = this.data;
+ for(var i = 0, len = d.length; i < len; i++){
+ if(fn.call(scope || window, d[i], i) === false) break;
+ }
+ }
+});
+
+/**
+ * Alias to YAHOO.ext.grid.DefaultColumnModel.sortTypes
+ * @static
+ */
+if(YAHOO.ext.grid.DefaultColumnModel){
+ YAHOO.ext.grid.DefaultDataModel.sortTypes = YAHOO.ext.grid.DefaultColumnModel.sortTypes;
+}
diff --git a/frontend/beta/js/YUI-extensions/data/JSONDataModel.js b/frontend/beta/js/YUI-extensions/data/JSONDataModel.js
new file mode 100644
index 0000000..ca48dce
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/data/JSONDataModel.js
@@ -0,0 +1,81 @@
+
+/**
+ * @class YAHOO.ext.grid.JSONDataModel
+ * This is an implementation of a DataModel used by the Grid. It works
+ * with JSON data.
+ * <br>Example schema:
+ * <pre><code>
+ * var schema = {
+ * root: 'Results.Result',
+ * id: 'ASIN',
+ * fields: ['Author', 'Title', 'Manufacturer', 'ProductGroup']
+ * };
+ * </code></pre>
+ * @extends YAHOO.ext.grid.LoadableDataModel
+ * @constructor
+*/
+YAHOO.ext.grid.JSONDataModel = function(schema){
+ YAHOO.ext.grid.JSONDataModel.superclass.constructor.call(this, YAHOO.ext.grid.LoadableDataModel.JSON);
+ /**@private*/
+ this.schema = schema;
+};
+YAHOO.extendX(YAHOO.ext.grid.JSONDataModel, YAHOO.ext.grid.LoadableDataModel, {
+ /**
+ * Overrides loadData in LoadableDataModel to process JSON data
+ * @param {Object} data The JSON object to load
+ * @param {Function} callback
+ */
+ loadData : function(data, callback, keepExisting){
+ var idField = this.schema.id;
+ var fields = this.schema.fields;
+ try{
+ if(this.schema.totalProperty){
+ var v = parseInt(eval('data.' + this.schema.totalProperty), 10);
+ if(!isNaN(v)){
+ this.totalCount = v;
+ }
+ }
+ var rowData = [];
+ var root = eval('data.' + this.schema.root);
+ for(var i = 0; i < root.length; i++){
+ var node = root[i];
+ var colData = [];
+ colData.node = node;
+ colData.id = (typeof node[idField] != 'undefined' && node[idField] !== '' ? node[idField] : String(i));
+ for(var j = 0; j < fields.length; j++) {
+ var val = node[fields[j]];
+ if(typeof val == 'undefined'){
+ val = '';
+ }
+ if(this.preprocessors[j]){
+ val = this.preprocessors[j](val);
+ }
+ colData.push(val);
+ }
+ rowData.push(colData);
+ }
+ if(keepExisting !== true){
+ this.removeAll();
+ }
+ this.addRows(rowData);
+ if(typeof callback == 'function'){
+ callback(this, true);
+ }
+ this.fireLoadEvent();
+ }catch(e){
+ this.fireLoadException(e, null);
+ if(typeof callback == 'function'){
+ callback(this, false);
+ }
+ }
+ },
+
+ /**
+ * 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;
+ }
+});
diff --git a/frontend/beta/js/YUI-extensions/data/LoadableDataModel.js b/frontend/beta/js/YUI-extensions/data/LoadableDataModel.js
new file mode 100644
index 0000000..07def44
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/data/LoadableDataModel.js
@@ -0,0 +1,330 @@
+/**
+ * @class YAHOO.ext.grid.LoadableDataModel
+ * This class extends DefaultDataModel and adds the core functionality to load data remotely. Generally you will want to use one of it's subclasses.<br><br>
+ * @extends YAHOO.ext.grid.DefaultDataModel
+ * @constructor
+ * @param {String} dataType YAHOO.ext.grid.LoadableDataModel.XML, YAHOO.ext.grid.LoadableDataModel.TEXT or YAHOO.ext.grid.JSON
+*/
+YAHOO.ext.grid.LoadableDataModel = function(dataType){
+ YAHOO.ext.grid.LoadableDataModel.superclass.constructor.call(this, []);
+
+ /** Fires when a successful load is completed - fireDirect sig: (this)
+ * @type YAHOO.util.CustomEvent
+ * @deprecated Use addListener instead of accessing directly
+ * @private
+ */
+ this.onLoad = new YAHOO.util.CustomEvent('load');
+ /** Fires when a load fails - fireDirect sig: (this, errorMsg, responseObj)
+ * @type YAHOO.util.CustomEvent
+ * @deprecated Use addListener instead of accessing directly
+ * @private
+ */
+ this.onLoadException = new YAHOO.util.CustomEvent('loadException');
+ /**
+ * @event load
+ * Fires when new data has successfully been loaded
+ * @param {DataModel} this
+ */
+ this.events['load'] = this.onLoad;
+ /**
+ * @event beforeload
+ * Fires before a load takes place
+ * @param {DataModel} this
+ */
+ this.events['beforeload'] = new YAHOO.util.CustomEvent('beforeload');
+ /**
+ * @event loadexception
+ * Fires when there's an error loading data
+ * @param {DataModel} this
+ * @param {Exception} e The exception object or null
+ * @param {Object} response The Connect response object
+ */
+ this.events['loadexception'] = this.onLoadException;
+
+ /**@private*/
+ this.dataType = dataType;
+ /**@private*/
+ this.preprocessors = [];
+ /**@private*/
+ this.postprocessors = [];
+
+ // paging info
+ /** The active page @type Number*/
+ this.loadedPage = 1;
+ /** True to use remote sorting, initPaging automatically sets this to true @type Boolean */
+ this.remoteSort = false;
+ /** The number of records per page @type Number*/
+ this.pageSize = 0;
+ /** The script/page to call to provide paged/sorted data @type String*/
+ this.pageUrl = null;
+ /** An object of key/value pairs to be passed as parameters
+ * when loading pages/sorting @type Object*/
+ this.baseParams = {};
+ /** Maps named params to url parameters - Override to specify your own param names */
+ this.paramMap = {'page':'page', 'pageSize':'pageSize', 'sortColumn':'sortColumn', 'sortDir':'sortDir'};
+
+};
+YAHOO.extendX(YAHOO.ext.grid.LoadableDataModel, YAHOO.ext.grid.DefaultDataModel, {
+
+ /** @ignore */
+ setLoadedPage: function(pageNum, userCallback){
+ this.loadedPage = pageNum;
+ if(typeof userCallback == 'function'){
+ userCallback();
+ }
+ },
+
+ /** Returns true if this model uses paging @return Boolean */
+ isPaged: function(){
+ return this.pageSize > 0;
+ },
+
+ /** Returns the total number of records available, override if needed @return {Number} */
+ getTotalRowCount: function(){
+ return this.totalCount || this.getRowCount();
+ },
+
+ /** Returns the number of records per page @return Number */
+ getPageSize: function(){
+ return this.pageSize;
+ },
+
+ /** Returns the total number of pages available @return Number */
+ getTotalPages: function(){
+ if(this.getPageSize() == 0 || this.getTotalRowCount() == 0){
+ return 1;
+ }
+ return Math.ceil(this.getTotalRowCount()/this.getPageSize());
+ },
+
+ /** Initializes paging for this model.
+ * @param {String} url
+ * @param {Number} pageSize
+ * @param {Object} baseParams (optional) Object containing key/value pairs to add to all requests
+ */
+ initPaging: function(url, pageSize, baseParams){
+ this.pageUrl = url;
+ this.pageSize = pageSize;
+ this.remoteSort = true;
+ if(baseParams) this.baseParams = baseParams;
+ },
+
+ /** @ignore */
+ createParams: function(pageNum, sortColumn, sortDir){
+ var params = {}, map = this.paramMap;
+ for(var key in this.baseParams){
+ if(typeof this.baseParams[key] != 'function'){
+ params[key] = this.baseParams[key];
+ }
+ }
+ params[map['page']] = pageNum;
+ params[map['pageSize']] = this.getPageSize();
+ params[map['sortColumn']] = (typeof sortColumn == 'undefined' ? '' : sortColumn);
+ params[map['sortDir']] = sortDir || '';
+ return params;
+ },
+
+ /**
+ * Loads a page of data.
+ * @param {Number} pageNum Which page to load. The first page is 1.
+ * @param {Function} callback (optional) Optional callback when loading is complete
+ * @param {Boolean} keepExisting (optional) true to keep existing data and append the new data
+ */
+ loadPage: function(pageNum, callback, keepExisting){
+ var sort = this.getSortState();
+ var params = this.createParams(pageNum, sort.column, sort.direction);
+ this.load(this.pageUrl, params, this.setLoadedPage.createDelegate(this, [pageNum, callback]),
+ keepExisting ? (pageNum-1) * this.pageSize : null);
+ },
+
+ /** @ignore */
+ applySort: function(suppressEvent){
+ if(!this.remoteSort){
+ YAHOO.ext.grid.LoadableDataModel.superclass.applySort.apply(this, arguments);
+ }else if(!suppressEvent){
+ var sort = this.getSortState();
+ if(sort.column){
+ this.fireRowsSorted(sort.column, sort.direction, true);
+ }
+ }
+ },
+
+ /** @ignore */
+ resetPaging: function(){
+ this.loadedPage = 1;
+ },
+
+ /* Overridden sort method to use remote sorting if turned on */
+ sort: function(sortInfo, columnIndex, direction, suppressEvent){
+ if(!this.remoteSort){
+ YAHOO.ext.grid.LoadableDataModel.superclass.sort.apply(this, arguments);
+ }else{
+ this.sortInfo = sortInfo;
+ this.sortColumn = columnIndex;
+ this.sortDir = direction;
+ var params = this.createParams(this.loadedPage, columnIndex, direction);
+ this.load(this.pageUrl, params, this.fireRowsSorted.createDelegate(this, [columnIndex, direction, true]));
+ }
+ },
+
+ /**
+ * Initiates the loading of the data from the specified URL - Failed load attempts will
+ * fire the {@link #loadexception} event.
+ * @param {Object/String} url The url from which the data can be loaded
+ * @param {<i>String/Object</i>} params (optional) The parameters to pass as either a url encoded string "param1=1&amp;param2=2" or as an object {param1: 1, param2: 2}
+ * @param {<i>Function</i>} callback (optional) Callback when load is complete - called with signature (this, true for success, false for failure)
+ * @param {<i>Number</i>} insertIndex (optional) if present, loaded data is inserted at the specified index instead of overwriting existing data
+ */
+ load: function(url, params, callback, insertIndex){
+ this.fireEvent('beforeload', this);
+ if(params && typeof params != 'string'){ // must be object
+ var buf = [];
+ for(var key in params){
+ if(typeof params[key] != 'function'){
+ buf.push(encodeURIComponent(key), '=', encodeURIComponent(params[key]), '&');
+ }
+ }
+ delete buf[buf.length-1];
+ params = buf.join('');
+ }
+ var cb = {
+ success: this.processResponse,
+ failure: this.processException,
+ scope: this,
+ argument: {callback: callback, insertIndex: insertIndex}
+ };
+ var method = params ? 'POST' : 'GET';
+ this.transId = YAHOO.util.Connect.asyncRequest(method, url, cb, params);
+ },
+
+ /**@private*/
+ processResponse: function(response){
+ var cb = response.argument.callback;
+ var keepExisting = (typeof response.argument.insertIndex == 'number');
+ var insertIndex = response.argument.insertIndex;
+ switch(this.dataType){
+ case YAHOO.ext.grid.LoadableDataModel.XML:
+ this.loadData(response.responseXML, cb, keepExisting, insertIndex);
+ break;
+ case YAHOO.ext.grid.LoadableDataModel.JSON:
+ var rtext = response.responseText;
+ try { // this code is a modified version of Yahoo! UI DataSource JSON parsing
+ // Trim leading spaces
+ while(rtext.substring(0,1) == " ") {
+ rtext = rtext.substring(1, rtext.length);
+ }
+ // Invalid JSON response
+ if(rtext.indexOf("{") < 0) {
+ throw "Invalid JSON response";
+ }
+
+ // Empty (but not invalid) JSON response
+ if(rtext.indexOf("{}") === 0) {
+ this.loadData({}, response.argument.callback);
+ return;
+ }
+
+ // Turn the string into an object literal...
+ // ...eval is necessary here
+ var jsonObjRaw = eval("(" + rtext + ")");
+ if(!jsonObjRaw) {
+ throw "Error evaling JSON response";
+ }
+ this.loadData(jsonObjRaw, cb, keepExisting, insertIndex);
+ } catch(e) {
+ this.fireLoadException(e, response);
+ if(typeof cb == 'function'){
+ cb(this, false);
+ }
+ }
+ break;
+ case YAHOO.ext.grid.LoadableDataModel.TEXT:
+ this.loadData(response.responseText, cb, keepExisting, insertIndex);
+ break;
+ };
+ },
+
+ /**@private*/
+ processException: function(response){
+ this.fireLoadException(null, response);
+ if(typeof response.argument.callback == 'function'){
+ response.argument.callback(this, false);
+ }
+ },
+
+ fireLoadException: function(e, responseObj){
+ this.onLoadException.fireDirect(this, e, responseObj);
+ },
+
+ fireLoadEvent: function(){
+ this.fireEvent('load', this.loadedPage, this.getTotalPages());
+ },
+
+ /**
+ * Adds a preprocessor function to parse data before it is added to the Model - ie. Date.parse to parse dates.
+ * @param {Number} columnIndex
+ * @param {Function} fn
+ */
+ addPreprocessor: function(columnIndex, fn){
+ this.preprocessors[columnIndex] = fn;
+ },
+
+ /**
+ * Gets the preprocessor function for the specified column.
+ * @param {Number} columnIndex
+ * @return {Function}
+ */
+ getPreprocessor: function(columnIndex){
+ return this.preprocessors[columnIndex];
+ },
+
+ /**
+ * Removes a preprocessor function.
+ * @param {Number} columnIndex
+ */
+ removePreprocessor: function(columnIndex){
+ this.preprocessors[columnIndex] = null;
+ },
+
+ /**
+ * Adds a postprocessor function to format data before updating the underlying data source (ie. convert date to string before updating XML document).
+ * @param {Number} columnIndex
+ * @param {Function} fn
+ */
+ addPostprocessor: function(columnIndex, fn){
+ this.postprocessors[columnIndex] = fn;
+ },
+
+ /**
+ * Gets the postprocessor function for the specified column.
+ * @param {Number} columnIndex
+ * @return {Function}
+ */
+ getPostprocessor: function(columnIndex){
+ return this.postprocessors[columnIndex];
+ },
+
+ /**
+ * Removes a postprocessor function.
+ * @param {Number} columnIndex
+ */
+ removePostprocessor: function(columnIndex){
+ this.postprocessors[columnIndex] = null;
+ },
+ /**
+ * Empty interface method - Called to process the data returned by the XHR - Classes which extend LoadableDataModel should implement this method.
+ * See {@link YAHOO.ext.XMLDataModel} for an example implementation.
+ */
+ loadData: function(data, callback, keepExisting, insertIndex){
+
+ }
+});
+
+YAHOO.ext.grid.LoadableDataModel.XML = 'xml';
+YAHOO.ext.grid.LoadableDataModel.JSON = 'json';
+YAHOO.ext.grid.LoadableDataModel.TEXT = 'text';
+
+
+
+
+
diff --git a/frontend/beta/js/YUI-extensions/data/Tree.js b/frontend/beta/js/YUI-extensions/data/Tree.js
new file mode 100644
index 0000000..afa5b20
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/data/Tree.js
@@ -0,0 +1,412 @@
+YAHOO.namespace('ext.data');
+
+/**
+ * @class YAHOO.ext.data.Tree
+ * @extends YAHOO.ext.util.Observable
+ * The class represents a tree data structure and bubbles all the events for it's nodes. The nodes
+ * in the tree have most standard DOM functionality.
+ * @constructor
+ * @param {Node} root (optional) The root node
+ */
+YAHOO.ext.data.Tree = function(root){
+ this.nodeHash = {};
+ this.root = null;
+ if(root){
+ this.setRootNode(root);
+ }
+ this.events = {
+ 'append' : true,
+ 'remove' : true,
+ 'move' : true,
+ 'insert' : true,
+ 'beforeappend' : true,
+ 'beforeremove' : true,
+ 'beforemove' : true,
+ 'beforeinsert' : true
+ };
+};
+
+YAHOO.extendX(YAHOO.ext.data.Tree, YAHOO.ext.util.Observable, {
+ pathSeparator: '/',
+
+ getRootNode : function(){
+ return this.root;
+ },
+
+ setRootNode : function(node){
+ this.root = node;
+ node.ownerTree = this;
+ node.isRoot = true;
+ return node;
+ },
+
+ getNodeById : function(id){
+ return this.nodeHash[id];
+ },
+
+ registerNode : function(node){
+ this.nodeHash[node.id] = node;
+ },
+
+ unregisterNode : function(node){
+ delete this.nodeHash[node.id];
+ },
+
+ toString : function(){
+ return '[Tree'+(this.id?' '+this.id:'')+']';
+ }
+});
+
+/**
+ * @class YAHOO.ext.tree.Node
+ * @extends YAHOO.ext.util.Observable
+ * @cfg {String} text The text for this node
+ * @cfg {String} id The id for this node
+ * @constructor
+ * @param {Object} attributes The attributes/config for the node
+ */
+YAHOO.ext.data.Node = function(attributes){
+ this.attributes = attributes || {};
+ this.leaf = this.attributes.leaf;
+ this.id = this.attributes.id;
+ if(!this.id){
+ this.id = YAHOO.util.Dom.generateId(null, 'ynode-');
+ this.attributes.id = this.id;
+ }
+
+ this.childNodes = [];
+ if(!this.childNodes.indexOf){ // indexOf is a must
+ this.childNodes.indexOf = function(o){
+ for(var i = 0, len = this.length; i < len; i++){
+ if(this[i] == o) return i;
+ }
+ return -1;
+ };
+ }
+ this.parentNode = null;
+ this.firstChild = null;
+ this.lastChild = null;
+ this.previousSibling = null;
+ this.nextSibling = null;
+
+ this.events = {
+ 'append' : true,
+ 'remove' : true,
+ 'move' : true,
+ 'insert' : true,
+ 'beforeappend' : true,
+ 'beforeremove' : true,
+ 'beforemove' : true,
+ 'beforeinsert' : true
+ };
+};
+
+YAHOO.extendX(YAHOO.ext.data.Node, YAHOO.ext.util.Observable, {
+ fireEvent : function(evtName){
+ // first do standard event for this node
+ if(YAHOO.ext.data.Node.superclass.fireEvent.apply(this, arguments) === false){
+ return false;
+ }
+ // then bubble it up to the tree if the event wasn't cancelled
+ if(this.ownerTree){
+ if(this.ownerTree.fireEvent.apply(this.ownerTree, arguments) === false){
+ return false;
+ }
+ }
+ return true;
+ },
+
+ isLeaf : function(){
+ return this.leaf === true;
+ },
+
+ setFirstChild : function(node){
+ this.firstChild = node;
+ },
+
+ setLastChild : function(node){
+ this.lastChild = node;
+ },
+
+ isLast : function(){
+ return (!this.parentNode ? true : this.parentNode.lastChild == this);
+ },
+
+ isFirst : function(){
+ return (!this.parentNode ? true : this.parentNode.firstChild == this);
+ },
+
+ hasChildNodes : function(){
+ return !this.isLeaf() && this.childNodes.length > 0;
+ },
+
+ appendChild : function(node){
+ var multi = false;
+ if(node instanceof Array){
+ multi = node;
+ }else if(arguments.length > 1){
+ multi = arguments;
+ }
+ // if passed an array or multiple args do them one by one
+ if(multi){
+ for(var i = 0, len = multi.length; i < len; i++) {
+ this.appendChild(multi[i]);
+ }
+ }else{
+ if(this.fireEvent('beforeappend', this.ownerTree, this, node) === false){
+ return false;
+ }
+ var index = this.childNodes.length;
+ var oldParent = node.parentNode;
+ // it's a move, make sure we move it cleanly
+ if(oldParent){
+ if(node.fireEvent('beforemove', node.getOwnerTree(), node, oldParent, this, index) === false){
+ return false;
+ }
+ oldParent.removeChild(node);
+ }
+ var index = this.childNodes.length;
+ if(index == 0){
+ this.setFirstChild(node);
+ }
+ this.childNodes.push(node);
+ node.parentNode = this;
+ var ps = this.childNodes[index-1];
+ if(ps){
+ node.previousSibling = ps;
+ ps.nextSibling = node;
+ }
+ this.setLastChild(node);
+ node.setOwnerTree(this.getOwnerTree());
+ this.fireEvent('append', this.ownerTree, this, node, index);
+ if(oldParent){
+ node.fireEvent('move', this.ownerTree, node, oldParent, this, index);
+ }
+ return node;
+ }
+ },
+
+ removeChild : function(node){
+ var index = this.childNodes.indexOf(node);
+ if(index == -1){
+ return false;
+ }
+ if(this.fireEvent('beforeremove', this.ownerTree, this, node) === false){
+ return false;
+ }
+
+ // remove it from childNodes collection
+ this.childNodes.splice(index, 1);
+
+ // update siblings
+ if(node.previousSibling){
+ node.previousSibling.nextSibling = node.nextSibling;
+ }
+ if(node.nextSibling){
+ node.nextSibling.previousSibling = node.previousSibling;
+ }
+
+ // update child refs
+ if(this.firstChild == node){
+ this.setFirstChild(node.nextSibling);
+ }
+ if(this.lastChild == node){
+ this.setLastChild(node.previousSibling);
+ }
+
+ node.setOwnerTree(null);
+ // clear any references from the node
+ node.parentNode = null;
+ node.previousSibling = null;
+ node.nextSibling = null;
+ this.fireEvent('remove', this.ownerTree, this, node);
+ return node;
+ },
+
+ insertBefore : function(node, refNode){
+ if(!refNode){ // like standard Dom, refNode can be null for append
+ return this.appendChild(node);
+ }
+ // nothing to do
+ if(node == refNode){
+ return false;
+ }
+
+ if(this.fireEvent('beforeinsert', this.ownerTree, this, node, refNode) === false){
+ return false;
+ }
+ var index = this.childNodes.indexOf(refNode);
+ var oldParent = node.parentNode;
+ var refIndex = index;
+
+ // when moving internally, indexes will change after remove
+ if(oldParent == this && this.childNodes.indexOf(node) < index){
+ refIndex--;
+ }
+
+ // it's a move, make sure we move it cleanly
+ if(oldParent){
+ if(node.fireEvent('beforemove', node.getOwnerTree(), node, oldParent, this, index, refNode) === false){
+ return false;
+ }
+ oldParent.removeChild(node);
+ }
+ if(refIndex == 0){
+ this.setFirstChild(node);
+ }
+ this.childNodes.splice(refIndex, 0, node);
+ node.parentNode = this;
+ var ps = this.childNodes[refIndex-1];
+ if(ps){
+ node.previousSibling = ps;
+ ps.nextSibling = node;
+ }
+ node.nextSibling = refNode;
+ node.setOwnerTree(this.getOwnerTree());
+ this.fireEvent('insert', this.ownerTree, this, node, refNode);
+ if(oldParent){
+ node.fireEvent('move', this.ownerTree, node, oldParent, this, refIndex, refNode);
+ }
+ return node;
+ },
+
+ item : function(index){
+ return this.childNodes[index];
+ },
+
+ replaceChild : function(newChild, oldChild){
+ this.insertBefore(newChild, oldChild);
+ this.removeChild(oldChild);
+ return oldChild;
+ },
+
+ indexOf : function(child){
+ return this.childNodes.indexOf(child);
+ },
+
+ getOwnerTree : function(){
+ // if it doesn't have one, look for one
+ if(!this.ownerTree){
+ var p = this;
+ while(p){
+ if(p.ownerTree){
+ this.ownerTree = p.ownerTree;
+ break;
+ }
+ p = p.parentNode;
+ }
+ }
+ return this.ownerTree;
+ },
+
+ setOwnerTree : function(tree){
+ // if it's move, we need to update everyone
+ if(tree != this.ownerTree){
+ if(this.ownerTree){
+ this.ownerTree.unregisterNode(this);
+ }
+ this.ownerTree = tree;
+ var cs = this.childNodes;
+ for(var i = 0, len = cs.length; i < len; i++) {
+ cs[i].setOwnerTree(tree);
+ }
+ if(tree){
+ tree.registerNode(this);
+ }
+ }
+ },
+
+ getPath : function(attr){
+ attr = attr || 'id';
+ var p = this.parentNode;
+ var b = [this.attributes[attr]];
+ while(p){
+ b.unshift(p.attributes[attr]);
+ p = p.parentNode;
+ }
+ var sep = this.getOwnerTree().pathSeparator;
+ return sep + b.join(sep);
+ },
+
+ bubble : function(fn, scope, args){
+ var p = this;
+ while(p){
+ if(fn.call(scope || p, args || p) === false){
+ break;
+ }
+ p = p.parentNode;
+ }
+ },
+
+ cascade : function(fn, scope, args){
+ if(fn.call(scope || this, args || this) !== false){
+ var cs = this.childNodes;
+ for(var i = 0, len = cs.length; i < len; i++) {
+ cs[i].cascade(fn, scope, args);
+ }
+ }
+ },
+
+ eachChild : function(fn, scope, args){
+ var cs = this.childNodes;
+ for(var i = 0, len = cs.length; i < len; i++) {
+ if(fn.call(scope || this, args || cs[i]) === false){
+ break;
+ }
+ }
+ },
+
+ findChild : function(attribute, value){
+ var cs = this.childNodes;
+ for(var i = 0, len = cs.length; i < len; i++) {
+ if(cs[i].attributes[attribute] == value){
+ return cs[i];
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Sorts this nodes children using the supplied sort function
+ * @param {Function} fn
+ * @param {Object} scope
+ */
+ sort : function(fn, scope){
+ var cs = this.childNodes;
+ var len = cs.length;
+ if(len > 0){
+ var sortFn = scope ? function(){fn.apply(scope, arguments);} : fn;
+ cs.sort(sortFn);
+ for(var i = 0; i < len; i++){
+ var n = cs[i];
+ n.previousSibling = cs[i-1];
+ n.nextSibling = cs[i+1];
+ if(i == 0){
+ this.setFirstChild(n);
+ }
+ if(i == len-1){
+ this.setLastChild(n);
+ }
+ }
+ }
+ },
+
+ contains : function(node){
+ return node.isAncestor(this);
+ },
+
+ isAncestor : function(node){
+ var p = this.parentNode;
+ while(p){
+ if(p == node){
+ return true;
+ }
+ p = p.parentNode;
+ }
+ return false;
+ },
+
+ toString : function(){
+ return '[Node'+(this.id?' '+this.id:'')+']';
+ }
+});
diff --git a/frontend/beta/js/YUI-extensions/data/XMLDataModel.js b/frontend/beta/js/YUI-extensions/data/XMLDataModel.js
new file mode 100644
index 0000000..e312a9e
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/data/XMLDataModel.js
@@ -0,0 +1,274 @@
+/**
+ * @class YAHOO.ext.grid.XMLDataModel
+ * This is an implementation of a DataModel used by the Grid. It works
+ * with XML data.
+ * <br>Example schema from Amazon search:
+ * <pre><code>
+ * var schema = {
+ * tagName: 'Item',
+ * id: 'ASIN',
+ * fields: ['Author', 'Title', 'Manufacturer', 'ProductGroup']
+ * };
+ * </code></pre>
+ * @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 {<i>Function</i>} callback (optional) callback to call when loading is complete
+ * @param {<i>Boolean</i>} keepExisting (optional) true to keep existing data
+ * @param {<i>Number</i>} 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;
+ }
+});