summaryrefslogtreecommitdiff
path: root/frontend/beta/js/YUI-extensions/data
authorGiulio Cesare Solaroli <giulio.cesare@clipperz.com>2011-10-02 23:56:18 (UTC)
committer Giulio Cesare Solaroli <giulio.cesare@clipperz.com>2011-10-02 23:56:18 (UTC)
commitef68436ac04da078ffdcacd7e1f785473a303d45 (patch) (unidiff)
treec403752d66a2c4775f00affd4fa8431b29c5b68c /frontend/beta/js/YUI-extensions/data
parent597ecfbc0249d83e1b856cbd558340c01237a360 (diff)
downloadclipperz-ef68436ac04da078ffdcacd7e1f785473a303d45.zip
clipperz-ef68436ac04da078ffdcacd7e1f785473a303d45.tar.gz
clipperz-ef68436ac04da078ffdcacd7e1f785473a303d45.tar.bz2
First version of the newly restructured repository
Diffstat (limited to 'frontend/beta/js/YUI-extensions/data') (more/less context) (ignore 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 @@
1/**
2 * @class YAHOO.ext.grid.AbstractDataModel
3 * @extends YAHOO.ext.util.Observable
4 * This abstract class provides default implementations of the events required by the Grid.
5 It takes care of the creating the CustomEvents and provides some convenient methods for firing the events. <br><br>
6 * @constructor
7*/
8YAHOO.ext.grid.AbstractDataModel = function(){
9 /** Fires when a cell is updated - fireDirect sig: (this, rowIndex, columnIndex)
10 * @private
11 * @type YAHOO.util.CustomEvent
12 * @deprecated Use addListener instead of accessing directly
13 */
14 this.onCellUpdated = new YAHOO.util.CustomEvent('onCellUpdated');
15 /** Fires when all data needs to be revalidated - fireDirect sig: (thisd)
16 * @private
17 * @type YAHOO.util.CustomEvent
18 * @deprecated Use addListener instead of accessing directly
19 */
20 this.onTableDataChanged = new YAHOO.util.CustomEvent('onTableDataChanged');
21 /** Fires when rows are deleted - fireDirect sig: (this, firstRowIndex, lastRowIndex)
22 * @private
23 * @type YAHOO.util.CustomEvent
24 * @deprecated Use addListener instead of accessing directly
25 */
26 this.onRowsDeleted = new YAHOO.util.CustomEvent('onRowsDeleted');
27 /** Fires when a rows are inserted - fireDirect sig: (this, firstRowIndex, lastRowIndex)
28 * @private
29 * @type YAHOO.util.CustomEvent
30 * @deprecated Use addListener instead of accessing directly
31 */
32 this.onRowsInserted = new YAHOO.util.CustomEvent('onRowsInserted');
33 /** Fires when a rows are updated - fireDirect sig: (this, firstRowIndex, lastRowIndex)
34 * @private
35 * @type YAHOO.util.CustomEvent
36 * @deprecated Use addListener instead of accessing directly
37 */
38 this.onRowsUpdated = new YAHOO.util.CustomEvent('onRowsUpdated');
39 /** Fires when a sort has reordered the rows - fireDirect sig: (this, sortColumnIndex,
40 * @private
41 * sortDirection = 'ASC' or 'DESC')
42 * @type YAHOO.util.CustomEvent
43 * @deprecated Use addListener instead of accessing directly
44 */
45 this.onRowsSorted = new YAHOO.util.CustomEvent('onRowsSorted');
46
47 this.events = {
48 /**
49 * @event cellupdated
50 * Fires when a cell is updated
51 * @param {DataModel} this
52 * @param {Number} rowIndex
53 * @param {Number} columnIndex
54 */
55 'cellupdated' : this.onCellUpdated,
56 /**
57 * @event datachanged
58 * Fires when the entire data structure has changed
59 * @param {DataModel} this
60 */
61 'datachanged' : this.onTableDataChanged,
62 /**
63 * @event rowsdeleted
64 * Fires when a range of rows have been deleted
65 * @param {DataModel} this
66 * @param {Number} firstRowIndex
67 * @param {Number} lastRowIndex
68 */
69 'rowsdeleted' : this.onRowsDeleted,
70 /**
71 * @event rowsinserted
72 * Fires when a range of rows have been inserted
73 * @param {DataModel} this
74 * @param {Number} firstRowIndex
75 * @param {Number} lastRowIndex
76 */
77 'rowsinserted' : this.onRowsInserted,
78 /**
79 * @event rowsupdated
80 * Fires when a range of rows have been updated
81 * @param {DataModel} this
82 * @param {Number} firstRowIndex
83 * @param {Number} lastRowIndex
84 */
85 'rowsupdated' : this.onRowsUpdated,
86 /**
87 * @event rowssorted
88 * Fires when the data has been sorted
89 * @param {DataModel} this
90 */
91 'rowssorted' : this.onRowsSorted
92 };
93};
94
95YAHOO.ext.grid.AbstractDataModel.prototype = {
96
97 fireEvent : YAHOO.ext.util.Observable.prototype.fireEvent,
98 on : YAHOO.ext.util.Observable.prototype.on,
99 addListener : YAHOO.ext.util.Observable.prototype.addListener,
100 delayedListener : YAHOO.ext.util.Observable.prototype.delayedListener,
101 removeListener : YAHOO.ext.util.Observable.prototype.removeListener,
102 purgeListeners : YAHOO.ext.util.Observable.prototype.purgeListeners,
103 bufferedListener : YAHOO.ext.util.Observable.prototype.bufferedListener,
104
105 /**
106 * Notifies listeners that the value of the cell at [row, col] has been updated
107 * @deprecated
108 * @private
109 */
110 fireCellUpdated : function(row, col){
111 this.onCellUpdated.fireDirect(this, row, col);
112 },
113
114 /**
115 * Notifies listeners that all data for the grid may have changed - use as a last resort. This
116 * also wipes out all selections a user might have made.
117 * @deprecated
118 * @private
119 */
120 fireTableDataChanged : function(){
121 this.onTableDataChanged.fireDirect(this);
122 },
123
124 /**
125 * Notifies listeners that rows in the range [firstRow, lastRow], inclusive, have been deleted
126 * @deprecated
127 * @private
128 */
129 fireRowsDeleted : function(firstRow, lastRow){
130 this.onRowsDeleted.fireDirect(this, firstRow, lastRow);
131 },
132
133 /**
134 * Notifies listeners that rows in the range [firstRow, lastRow], inclusive, have been inserted
135 * @deprecated
136 * @private
137 */
138 fireRowsInserted : function(firstRow, lastRow){
139 this.onRowsInserted.fireDirect(this, firstRow, lastRow);
140 },
141
142 /**
143 * Notifies listeners that rows in the range [firstRow, lastRow], inclusive, have been updated
144 * @deprecated
145 * @private
146 */
147 fireRowsUpdated : function(firstRow, lastRow){
148 this.onRowsUpdated.fireDirect(this, firstRow, lastRow);
149 },
150
151 /**
152 * Notifies listeners that rows have been sorted and any indexes may be invalid
153 * @deprecated
154 * @private
155 */
156 fireRowsSorted : function(sortColumnIndex, sortDir, noRefresh){
157 this.onRowsSorted.fireDirect(this, sortColumnIndex, sortDir, noRefresh);
158 },
159
160 /**
161 * Empty interface method - Classes which extend AbstractDataModel should implement this method.
162 * See {@link YAHOO.ext.DefaultDataModel#sort} for an example implementation.
163 * @private
164 */
165 sort : function(sortInfo, columnIndex, direction, suppressEvent){
166
167 },
168
169 /**
170 * Interface method to supply info regarding the Grid's current sort state - if overridden,
171 * this should return an object like this {column: this.sortColumn, direction: this.sortDir}.
172 * @return {Object}
173 */
174 getSortState : function(){
175 return {column: this.sortColumn, direction: this.sortDir};
176 },
177
178 /**
179 * Empty interface method - Classes which extend AbstractDataModel should implement this method.
180 * See {@link YAHOO.ext.DefaultDataModel} for an example implementation.
181 * @private
182 */
183 getRowCount : function(){
184
185 },
186
187 /**
188 * Empty interface method - Classes which extend AbstractDataModel should implement this method to support virtual row counts.
189 * @private
190 */
191 getTotalRowCount : function(){
192 return this.getRowCount();
193 },
194
195
196 /**
197 * Empty interface method - Classes which extend AbstractDataModel should implement this method.
198 * See {@link YAHOO.ext.DefaultDataModel} for an example implementation.
199 * @private
200 */
201 getRowId : function(rowIndex){
202
203 },
204
205 /**
206 * Empty interface method - Classes which extend AbstractDataModel should implement this method.
207 * See {@link YAHOO.ext.DefaultDataModel} for an example implementation.
208 * @private
209 */
210 getValueAt : function(rowIndex, colIndex){
211
212 },
213
214 /**
215 * Empty interface method - Classes which extend AbstractDataModel should implement this method.
216 * See {@link YAHOO.ext.DefaultDataModel} for an example implementation.
217 * @private
218 */
219 setValueAt : function(value, rowIndex, colIndex){
220
221 },
222
223 isPaged : function(){
224 return false;
225 }
226};
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 @@
1
2/**
3 * @class YAHOO.ext.grid.DefaultDataModel
4 * This is the default implementation of a DataModel used by the Grid. It works
5 * with multi-dimensional array based data. Using the event system in the base class
6 * {@link YAHOO.ext.grid.AbstractDataModel}, all updates to this DataModel are automatically
7 * reflected in the user interface.
8 * <br>Usage:<br>
9 * <pre><code>
10 * var myData = [
11 ["MSFT","Microsoft Corporation", "314,571.156", "32,187.000", "55000"],
12 ["ORCL", "Oracle Corporation", "62,615.266", "9,519.000", "40650"]
13 * ];
14 * var dataModel = new YAHOO.ext.grid.DefaultDataModel(myData);
15 * </code></pre>
16 * @extends YAHOO.ext.grid.AbstractDataModel
17 * @constructor
18 * @param {Array} data
19*/
20YAHOO.ext.grid.DefaultDataModel = function(data){
21 YAHOO.ext.grid.DefaultDataModel.superclass.constructor.call(this);
22 /**@private*/
23 this.data = data;
24};
25YAHOO.extendX(YAHOO.ext.grid.DefaultDataModel, YAHOO.ext.grid.AbstractDataModel, {
26 /**
27 * Returns the number of rows in the dataset
28 * @return {Number}
29 */
30 getRowCount : function(){
31 return this.data.length;
32 },
33
34 /**
35 * Returns the ID of the specified row. By default it return the value of the first column.
36 * Override to provide more advanced ID handling.
37 * @return {Number}
38 */
39 getRowId : function(rowIndex){
40 return this.data[rowIndex][0];
41 },
42
43 /**
44 * Returns the column data for the specified row.
45 * @return {Array}
46 */
47 getRow : function(rowIndex){
48 return this.data[rowIndex];
49 },
50
51 /**
52 * Returns the column data for the specified rows as a
53 * multi-dimensional array: rows[3][0] would give you the value of row 4, column 0.
54 * @param {Array} indexes The row indexes to fetch
55 * @return {Array}
56 */
57 getRows : function(indexes){
58 var data = this.data;
59 var r = [];
60 for(var i = 0; i < indexes.length; i++){
61 r.push(data[indexes[i]]);
62 }
63 return r;
64 },
65
66 /**
67 * Returns the value at the specified data position
68 * @param {Number} rowIndex
69 * @param {Number} colIndex
70 * @return {Object}
71 */
72 getValueAt : function(rowIndex, colIndex){
73 return this.data[rowIndex][colIndex];
74 },
75
76 /**
77 * Sets the specified value at the specified data position
78 * @param {Object} value The new value
79 * @param {Number} rowIndex
80 * @param {Number} colIndex
81 */
82 setValueAt: function(value, rowIndex, colIndex){
83 this.data[rowIndex][colIndex] = value;
84 this.fireCellUpdated(rowIndex, colIndex);
85 },
86
87 /**
88 * @private
89 * Removes the specified range of rows.
90 * @param {Number} startIndex
91 * @param {<i>Number</i>} endIndex (optional) Defaults to startIndex
92 */
93 removeRows: function(startIndex, endIndex){
94 endIndex = endIndex || startIndex;
95 this.data.splice(startIndex, endIndex-startIndex+1);
96 this.fireRowsDeleted(startIndex, endIndex);
97 },
98
99 /**
100 * Remove a row.
101 * @param {Number} index
102 */
103 removeRow: function(index){
104 this.data.splice(index, 1);
105 this.fireRowsDeleted(index, index);
106 },
107
108 /**
109 * @private
110 * Removes all rows.
111 */
112 removeAll: function(){
113 var count = this.getRowCount();
114 if(count > 0){
115 this.removeRows(0, count-1);
116 }
117 },
118
119 /**
120 * Query the DataModel rows by the filters defined in spec, for example...
121 * <pre><code>
122 * // column 1 starts with Jack, column 2 filtered by myFcn, column 3 equals 'Fred'
123 * dataModel.filter({1: /^Jack.+/i}, 2: myFcn, 3: 'Fred'});
124 * </code></pre>
125 * @param {Object} spec The spec is generally an object literal consisting of
126 * column index and filter type. The filter type can be a string/number (exact match),
127 * a regular expression to test using String.search() or a function to call. If it's a function,
128 * it will be called with the value for the specified column and an array of the all column
129 * values for that row: yourFcn(value, columnData). If it returns anything other than true,
130 * the row is not a match. If you have modified Object.prototype this method may fail.
131 * @param {Boolean} returnUnmatched True to return rows which <b>don't</b> match the query instead
132 * of rows that do match
133 * @return {Array} An array of row indexes that match
134 */
135 query: function(spec, returnUnmatched){
136 var d = this.data;
137 var r = [];
138 for(var i = 0; i < d.length; i++){
139 var row = d[i];
140 var isMatch = true;
141 for(var col in spec){
142 //if(typeof spec[col] != 'function'){
143 if(!isMatch) continue;
144 var filter = spec[col];
145 switch(typeof filter){
146 case 'string':
147 case 'number':
148 case 'boolean':
149 if(row[col] != filter){
150 isMatch = false;
151 }
152 break;
153 case 'function':
154 if(!filter(row[col], row)){
155 isMatch = false;
156 }
157 break;
158 case 'object':
159 if(filter instanceof RegExp){
160 if(String(row[col]).search(filter) === -1){
161 isMatch = false;
162 }
163 }
164 break;
165 }
166 //}
167 }
168 if(isMatch && !returnUnmatched){
169 r.push(i);
170 }else if(!isMatch && returnUnmatched){
171 r.push(i);
172 }
173 }
174 return r;
175 },
176
177 /**
178 * Filter the DataModel rows by the query defined in spec, see {@link #query} for more details
179 * on the query spec.
180 * @param {Object} query The query spec {@link #query}
181 * @return {Number} The number of rows removed
182 */
183 filter: function(query){
184 var matches = this.query(query, true);
185 var data = this.data;
186 // go thru the data setting matches to deleted
187 // while not disturbing row indexes
188 for(var i = 0; i < matches.length; i++){
189 data[matches[i]]._deleted = true;
190 }
191 for(var i = 0; i < data.length; i++){
192 while(data[i] && data[i]._deleted === true){
193 this.removeRow(i);
194 }
195 }
196 return matches.length;
197 },
198
199 /**
200 * Adds a row to the dataset.
201 * @param {Array} cellValues The array of values for the new row
202 * @return {Number} The index of the added row
203 */
204 addRow: function(cellValues){
205 this.data.push(cellValues);
206 var newIndex = this.data.length-1;
207 this.fireRowsInserted(newIndex, newIndex);
208 this.applySort();
209 return newIndex;
210 },
211
212 /**
213 * @private
214 * Adds a set of rows.
215 * @param {Array} rowData This should be an array of arrays like the constructor takes
216 */
217 addRows: function(rowData){
218 this.data = this.data.concat(rowData);
219 var firstIndex = this.data.length-rowData.length;
220 this.fireRowsInserted(firstIndex, firstIndex+rowData.length-1);
221 this.applySort();
222 },
223
224 /**
225 * Inserts a row a the specified location in the dataset.
226 * @param {Number} index The index where the row should be inserted
227 * @param {Array} cellValues The array of values for the new row
228 * @return {Number} The index the row was inserted in
229 */
230 insertRow: function(index, cellValues){
231 this.data.splice(index, 0, cellValues);
232 this.fireRowsInserted(index, index);
233 this.applySort();
234 return index;
235 },
236
237 /**
238 * @private
239 * Inserts a set of rows.
240 * @param {Number} index The index where the rows should be inserted
241 * @param {Array} rowData This should be an array of arrays like the constructor takes
242 */
243 insertRows: function(index, rowData){
244 /*
245 if(index == this.data.length){ // try these two first since they are faster
246 this.data = this.data.concat(rowData);
247 }else if(index == 0){
248 this.data = rowData.concat(this.data);
249 }else{
250 var newData = this.data.slice(0, index);
251 newData.concat(rowData);
252 newData.concat(this.data.slice(index));
253 this.data = newData;
254 }*/
255 var args = rowData.concat();
256 args.splice(0, 0, index, 0);
257 this.data.splice.apply(this.data, args);
258 this.fireRowsInserted(index, index+rowData.length-1);
259 this.applySort();
260 },
261
262 /**
263 * Applies the last used sort to the current data.
264 */
265 applySort: function(suppressEvent){
266 if(typeof this.sortColumn != 'undefined'){
267 this.sort(this.sortInfo, this.sortColumn, this.sortDir, suppressEvent);
268 }
269 },
270
271 /**
272 * Sets the default sort info. Note: this function does not actually apply the sort.
273 * @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
274 * a grid column model.
275 * @param {Number} columnIndex The column index to sort by
276 * @param {String} direction The direction of the sort ('DESC' or 'ASC')
277 */
278 setDefaultSort: function(sortInfo, columnIndex, direction){
279 this.sortInfo = sortInfo;
280 this.sortColumn = columnIndex;
281 this.sortDir = direction;
282 },
283 /**
284 * Sorts the data by the specified column - Uses the sortType specified for the column in the passed columnModel.
285 * @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
286 * a grid column model.
287 * @param {Number} columnIndex The column index to sort by
288 * @param {String} direction The direction of the sort ('DESC' or 'ASC')
289 */
290 sort: function(sortInfo, columnIndex, direction, suppressEvent){
291 // store these so we can maintain sorting when we load new data
292 this.sortInfo = sortInfo;
293 this.sortColumn = columnIndex;
294 this.sortDir = direction;
295
296 var dsc = (direction && direction.toUpperCase() == 'DESC');
297 var sortType = null;
298 if(sortInfo != null){
299 if(typeof sortInfo == 'function'){
300 sortType = sortInfo;
301 }else if(typeof sortInfo == 'object'){
302 sortType = sortInfo.getSortType(columnIndex);;
303 }
304 }
305 var fn = function(cells, cells2){
306 var v1 = sortType ? sortType(cells[columnIndex], cells) : cells[columnIndex];
307 var v2 = sortType ? sortType(cells2[columnIndex], cells2) : cells2[columnIndex];
308 if(v1 < v2)
309 return dsc ? +1 : -1;
310 if(v1 > v2)
311 return dsc ? -1 : +1;
312 return 0;
313 };
314 this.data.sort(fn);
315 if(!suppressEvent){
316 this.fireRowsSorted(columnIndex, direction);
317 }
318 },
319
320 /**
321 * Calls passed function with each rows data - if the function returns false it stops.
322 * @param {Function} fn
323 * @param {Object} scope (optional)
324 */
325 each: function(fn, scope){
326 var d = this.data;
327 for(var i = 0, len = d.length; i < len; i++){
328 if(fn.call(scope || window, d[i], i) === false) break;
329 }
330 }
331});
332
333/**
334 * Alias to YAHOO.ext.grid.DefaultColumnModel.sortTypes
335 * @static
336 */
337if(YAHOO.ext.grid.DefaultColumnModel){
338 YAHOO.ext.grid.DefaultDataModel.sortTypes = YAHOO.ext.grid.DefaultColumnModel.sortTypes;
339}
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 @@
1
2/**
3 * @class YAHOO.ext.grid.JSONDataModel
4 * This is an implementation of a DataModel used by the Grid. It works
5 * with JSON data.
6 * <br>Example schema:
7 * <pre><code>
8 * var schema = {
9 * root: 'Results.Result',
10 * id: 'ASIN',
11 * fields: ['Author', 'Title', 'Manufacturer', 'ProductGroup']
12 * };
13 * </code></pre>
14 * @extends YAHOO.ext.grid.LoadableDataModel
15 * @constructor
16*/
17YAHOO.ext.grid.JSONDataModel = function(schema){
18 YAHOO.ext.grid.JSONDataModel.superclass.constructor.call(this, YAHOO.ext.grid.LoadableDataModel.JSON);
19 /**@private*/
20 this.schema = schema;
21};
22YAHOO.extendX(YAHOO.ext.grid.JSONDataModel, YAHOO.ext.grid.LoadableDataModel, {
23 /**
24 * Overrides loadData in LoadableDataModel to process JSON data
25 * @param {Object} data The JSON object to load
26 * @param {Function} callback
27 */
28 loadData : function(data, callback, keepExisting){
29 var idField = this.schema.id;
30 var fields = this.schema.fields;
31 try{
32 if(this.schema.totalProperty){
33 var v = parseInt(eval('data.' + this.schema.totalProperty), 10);
34 if(!isNaN(v)){
35 this.totalCount = v;
36 }
37 }
38 var rowData = [];
39 var root = eval('data.' + this.schema.root);
40 for(var i = 0; i < root.length; i++){
41 var node = root[i];
42 var colData = [];
43 colData.node = node;
44 colData.id = (typeof node[idField] != 'undefined' && node[idField] !== '' ? node[idField] : String(i));
45 for(var j = 0; j < fields.length; j++) {
46 var val = node[fields[j]];
47 if(typeof val == 'undefined'){
48 val = '';
49 }
50 if(this.preprocessors[j]){
51 val = this.preprocessors[j](val);
52 }
53 colData.push(val);
54 }
55 rowData.push(colData);
56 }
57 if(keepExisting !== true){
58 this.removeAll();
59 }
60 this.addRows(rowData);
61 if(typeof callback == 'function'){
62 callback(this, true);
63 }
64 this.fireLoadEvent();
65 }catch(e){
66 this.fireLoadException(e, null);
67 if(typeof callback == 'function'){
68 callback(this, false);
69 }
70 }
71 },
72
73 /**
74 * Overrides getRowId in DefaultDataModel to return the ID value of the specified node.
75 * @param {Number} rowIndex
76 * @return {Number}
77 */
78 getRowId : function(rowIndex){
79 return this.data[rowIndex].id;
80 }
81});
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 @@
1/**
2 * @class YAHOO.ext.grid.LoadableDataModel
3 * 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>
4 * @extends YAHOO.ext.grid.DefaultDataModel
5 * @constructor
6 * @param {String} dataType YAHOO.ext.grid.LoadableDataModel.XML, YAHOO.ext.grid.LoadableDataModel.TEXT or YAHOO.ext.grid.JSON
7*/
8YAHOO.ext.grid.LoadableDataModel = function(dataType){
9 YAHOO.ext.grid.LoadableDataModel.superclass.constructor.call(this, []);
10
11 /** Fires when a successful load is completed - fireDirect sig: (this)
12 * @type YAHOO.util.CustomEvent
13 * @deprecated Use addListener instead of accessing directly
14 * @private
15 */
16 this.onLoad = new YAHOO.util.CustomEvent('load');
17 /** Fires when a load fails - fireDirect sig: (this, errorMsg, responseObj)
18 * @type YAHOO.util.CustomEvent
19 * @deprecated Use addListener instead of accessing directly
20 * @private
21 */
22 this.onLoadException = new YAHOO.util.CustomEvent('loadException');
23 /**
24 * @event load
25 * Fires when new data has successfully been loaded
26 * @param {DataModel} this
27 */
28 this.events['load'] = this.onLoad;
29 /**
30 * @event beforeload
31 * Fires before a load takes place
32 * @param {DataModel} this
33 */
34 this.events['beforeload'] = new YAHOO.util.CustomEvent('beforeload');
35 /**
36 * @event loadexception
37 * Fires when there's an error loading data
38 * @param {DataModel} this
39 * @param {Exception} e The exception object or null
40 * @param {Object} response The Connect response object
41 */
42 this.events['loadexception'] = this.onLoadException;
43
44 /**@private*/
45 this.dataType = dataType;
46 /**@private*/
47 this.preprocessors = [];
48 /**@private*/
49 this.postprocessors = [];
50
51 // paging info
52 /** The active page @type Number*/
53 this.loadedPage = 1;
54 /** True to use remote sorting, initPaging automatically sets this to true @type Boolean */
55 this.remoteSort = false;
56 /** The number of records per page @type Number*/
57 this.pageSize = 0;
58 /** The script/page to call to provide paged/sorted data @type String*/
59 this.pageUrl = null;
60 /** An object of key/value pairs to be passed as parameters
61 * when loading pages/sorting @type Object*/
62 this.baseParams = {};
63 /** Maps named params to url parameters - Override to specify your own param names */
64 this.paramMap = {'page':'page', 'pageSize':'pageSize', 'sortColumn':'sortColumn', 'sortDir':'sortDir'};
65
66};
67YAHOO.extendX(YAHOO.ext.grid.LoadableDataModel, YAHOO.ext.grid.DefaultDataModel, {
68
69 /** @ignore */
70 setLoadedPage: function(pageNum, userCallback){
71 this.loadedPage = pageNum;
72 if(typeof userCallback == 'function'){
73 userCallback();
74 }
75 },
76
77 /** Returns true if this model uses paging @return Boolean */
78 isPaged: function(){
79 return this.pageSize > 0;
80 },
81
82 /** Returns the total number of records available, override if needed @return {Number} */
83 getTotalRowCount: function(){
84 return this.totalCount || this.getRowCount();
85 },
86
87 /** Returns the number of records per page @return Number */
88 getPageSize: function(){
89 return this.pageSize;
90 },
91
92 /** Returns the total number of pages available @return Number */
93 getTotalPages: function(){
94 if(this.getPageSize() == 0 || this.getTotalRowCount() == 0){
95 return 1;
96 }
97 return Math.ceil(this.getTotalRowCount()/this.getPageSize());
98 },
99
100 /** Initializes paging for this model.
101 * @param {String} url
102 * @param {Number} pageSize
103 * @param {Object} baseParams (optional) Object containing key/value pairs to add to all requests
104 */
105 initPaging: function(url, pageSize, baseParams){
106 this.pageUrl = url;
107 this.pageSize = pageSize;
108 this.remoteSort = true;
109 if(baseParams) this.baseParams = baseParams;
110 },
111
112 /** @ignore */
113 createParams: function(pageNum, sortColumn, sortDir){
114 var params = {}, map = this.paramMap;
115 for(var key in this.baseParams){
116 if(typeof this.baseParams[key] != 'function'){
117 params[key] = this.baseParams[key];
118 }
119 }
120 params[map['page']] = pageNum;
121 params[map['pageSize']] = this.getPageSize();
122 params[map['sortColumn']] = (typeof sortColumn == 'undefined' ? '' : sortColumn);
123 params[map['sortDir']] = sortDir || '';
124 return params;
125 },
126
127 /**
128 * Loads a page of data.
129 * @param {Number} pageNum Which page to load. The first page is 1.
130 * @param {Function} callback (optional) Optional callback when loading is complete
131 * @param {Boolean} keepExisting (optional) true to keep existing data and append the new data
132 */
133 loadPage: function(pageNum, callback, keepExisting){
134 var sort = this.getSortState();
135 var params = this.createParams(pageNum, sort.column, sort.direction);
136 this.load(this.pageUrl, params, this.setLoadedPage.createDelegate(this, [pageNum, callback]),
137 keepExisting ? (pageNum-1) * this.pageSize : null);
138 },
139
140 /** @ignore */
141 applySort: function(suppressEvent){
142 if(!this.remoteSort){
143 YAHOO.ext.grid.LoadableDataModel.superclass.applySort.apply(this, arguments);
144 }else if(!suppressEvent){
145 var sort = this.getSortState();
146 if(sort.column){
147 this.fireRowsSorted(sort.column, sort.direction, true);
148 }
149 }
150 },
151
152 /** @ignore */
153 resetPaging: function(){
154 this.loadedPage = 1;
155 },
156
157 /* Overridden sort method to use remote sorting if turned on */
158 sort: function(sortInfo, columnIndex, direction, suppressEvent){
159 if(!this.remoteSort){
160 YAHOO.ext.grid.LoadableDataModel.superclass.sort.apply(this, arguments);
161 }else{
162 this.sortInfo = sortInfo;
163 this.sortColumn = columnIndex;
164 this.sortDir = direction;
165 var params = this.createParams(this.loadedPage, columnIndex, direction);
166 this.load(this.pageUrl, params, this.fireRowsSorted.createDelegate(this, [columnIndex, direction, true]));
167 }
168 },
169
170 /**
171 * Initiates the loading of the data from the specified URL - Failed load attempts will
172 * fire the {@link #loadexception} event.
173 * @param {Object/String} url The url from which the data can be loaded
174 * @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}
175 * @param {<i>Function</i>} callback (optional) Callback when load is complete - called with signature (this, true for success, false for failure)
176 * @param {<i>Number</i>} insertIndex (optional) if present, loaded data is inserted at the specified index instead of overwriting existing data
177 */
178 load: function(url, params, callback, insertIndex){
179 this.fireEvent('beforeload', this);
180 if(params && typeof params != 'string'){ // must be object
181 var buf = [];
182 for(var key in params){
183 if(typeof params[key] != 'function'){
184 buf.push(encodeURIComponent(key), '=', encodeURIComponent(params[key]), '&');
185 }
186 }
187 delete buf[buf.length-1];
188 params = buf.join('');
189 }
190 var cb = {
191 success: this.processResponse,
192 failure: this.processException,
193 scope: this,
194 argument: {callback: callback, insertIndex: insertIndex}
195 };
196 var method = params ? 'POST' : 'GET';
197 this.transId = YAHOO.util.Connect.asyncRequest(method, url, cb, params);
198 },
199
200 /**@private*/
201 processResponse: function(response){
202 var cb = response.argument.callback;
203 var keepExisting = (typeof response.argument.insertIndex == 'number');
204 var insertIndex = response.argument.insertIndex;
205 switch(this.dataType){
206 case YAHOO.ext.grid.LoadableDataModel.XML:
207 this.loadData(response.responseXML, cb, keepExisting, insertIndex);
208 break;
209 case YAHOO.ext.grid.LoadableDataModel.JSON:
210 var rtext = response.responseText;
211 try { // this code is a modified version of Yahoo! UI DataSource JSON parsing
212 // Trim leading spaces
213 while(rtext.substring(0,1) == " ") {
214 rtext = rtext.substring(1, rtext.length);
215 }
216 // Invalid JSON response
217 if(rtext.indexOf("{") < 0) {
218 throw "Invalid JSON response";
219 }
220
221 // Empty (but not invalid) JSON response
222 if(rtext.indexOf("{}") === 0) {
223 this.loadData({}, response.argument.callback);
224 return;
225 }
226
227 // Turn the string into an object literal...
228 // ...eval is necessary here
229 var jsonObjRaw = eval("(" + rtext + ")");
230 if(!jsonObjRaw) {
231 throw "Error evaling JSON response";
232 }
233 this.loadData(jsonObjRaw, cb, keepExisting, insertIndex);
234 } catch(e) {
235 this.fireLoadException(e, response);
236 if(typeof cb == 'function'){
237 cb(this, false);
238 }
239 }
240 break;
241 case YAHOO.ext.grid.LoadableDataModel.TEXT:
242 this.loadData(response.responseText, cb, keepExisting, insertIndex);
243 break;
244 };
245 },
246
247 /**@private*/
248 processException: function(response){
249 this.fireLoadException(null, response);
250 if(typeof response.argument.callback == 'function'){
251 response.argument.callback(this, false);
252 }
253 },
254
255 fireLoadException: function(e, responseObj){
256 this.onLoadException.fireDirect(this, e, responseObj);
257 },
258
259 fireLoadEvent: function(){
260 this.fireEvent('load', this.loadedPage, this.getTotalPages());
261 },
262
263 /**
264 * Adds a preprocessor function to parse data before it is added to the Model - ie. Date.parse to parse dates.
265 * @param {Number} columnIndex
266 * @param {Function} fn
267 */
268 addPreprocessor: function(columnIndex, fn){
269 this.preprocessors[columnIndex] = fn;
270 },
271
272 /**
273 * Gets the preprocessor function for the specified column.
274 * @param {Number} columnIndex
275 * @return {Function}
276 */
277 getPreprocessor: function(columnIndex){
278 return this.preprocessors[columnIndex];
279 },
280
281 /**
282 * Removes a preprocessor function.
283 * @param {Number} columnIndex
284 */
285 removePreprocessor: function(columnIndex){
286 this.preprocessors[columnIndex] = null;
287 },
288
289 /**
290 * Adds a postprocessor function to format data before updating the underlying data source (ie. convert date to string before updating XML document).
291 * @param {Number} columnIndex
292 * @param {Function} fn
293 */
294 addPostprocessor: function(columnIndex, fn){
295 this.postprocessors[columnIndex] = fn;
296 },
297
298 /**
299 * Gets the postprocessor function for the specified column.
300 * @param {Number} columnIndex
301 * @return {Function}
302 */
303 getPostprocessor: function(columnIndex){
304 return this.postprocessors[columnIndex];
305 },
306
307 /**
308 * Removes a postprocessor function.
309 * @param {Number} columnIndex
310 */
311 removePostprocessor: function(columnIndex){
312 this.postprocessors[columnIndex] = null;
313 },
314 /**
315 * Empty interface method - Called to process the data returned by the XHR - Classes which extend LoadableDataModel should implement this method.
316 * See {@link YAHOO.ext.XMLDataModel} for an example implementation.
317 */
318 loadData: function(data, callback, keepExisting, insertIndex){
319
320 }
321});
322
323YAHOO.ext.grid.LoadableDataModel.XML = 'xml';
324YAHOO.ext.grid.LoadableDataModel.JSON = 'json';
325YAHOO.ext.grid.LoadableDataModel.TEXT = 'text';
326
327
328
329
330
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 @@
1YAHOO.namespace('ext.data');
2
3/**
4 * @class YAHOO.ext.data.Tree
5 * @extends YAHOO.ext.util.Observable
6 * The class represents a tree data structure and bubbles all the events for it's nodes. The nodes
7 * in the tree have most standard DOM functionality.
8 * @constructor
9 * @param {Node} root (optional) The root node
10 */
11YAHOO.ext.data.Tree = function(root){
12 this.nodeHash = {};
13 this.root = null;
14 if(root){
15 this.setRootNode(root);
16 }
17 this.events = {
18 'append' : true,
19 'remove' : true,
20 'move' : true,
21 'insert' : true,
22 'beforeappend' : true,
23 'beforeremove' : true,
24 'beforemove' : true,
25 'beforeinsert' : true
26 };
27};
28
29YAHOO.extendX(YAHOO.ext.data.Tree, YAHOO.ext.util.Observable, {
30 pathSeparator: '/',
31
32 getRootNode : function(){
33 return this.root;
34 },
35
36 setRootNode : function(node){
37 this.root = node;
38 node.ownerTree = this;
39 node.isRoot = true;
40 return node;
41 },
42
43 getNodeById : function(id){
44 return this.nodeHash[id];
45 },
46
47 registerNode : function(node){
48 this.nodeHash[node.id] = node;
49 },
50
51 unregisterNode : function(node){
52 delete this.nodeHash[node.id];
53 },
54
55 toString : function(){
56 return '[Tree'+(this.id?' '+this.id:'')+']';
57 }
58});
59
60/**
61 * @class YAHOO.ext.tree.Node
62 * @extends YAHOO.ext.util.Observable
63 * @cfg {String} text The text for this node
64 * @cfg {String} id The id for this node
65 * @constructor
66 * @param {Object} attributes The attributes/config for the node
67 */
68YAHOO.ext.data.Node = function(attributes){
69 this.attributes = attributes || {};
70 this.leaf = this.attributes.leaf;
71 this.id = this.attributes.id;
72 if(!this.id){
73 this.id = YAHOO.util.Dom.generateId(null, 'ynode-');
74 this.attributes.id = this.id;
75 }
76
77 this.childNodes = [];
78 if(!this.childNodes.indexOf){ // indexOf is a must
79 this.childNodes.indexOf = function(o){
80 for(var i = 0, len = this.length; i < len; i++){
81 if(this[i] == o) return i;
82 }
83 return -1;
84 };
85 }
86 this.parentNode = null;
87 this.firstChild = null;
88 this.lastChild = null;
89 this.previousSibling = null;
90 this.nextSibling = null;
91
92 this.events = {
93 'append' : true,
94 'remove' : true,
95 'move' : true,
96 'insert' : true,
97 'beforeappend' : true,
98 'beforeremove' : true,
99 'beforemove' : true,
100 'beforeinsert' : true
101 };
102};
103
104YAHOO.extendX(YAHOO.ext.data.Node, YAHOO.ext.util.Observable, {
105 fireEvent : function(evtName){
106 // first do standard event for this node
107 if(YAHOO.ext.data.Node.superclass.fireEvent.apply(this, arguments) === false){
108 return false;
109 }
110 // then bubble it up to the tree if the event wasn't cancelled
111 if(this.ownerTree){
112 if(this.ownerTree.fireEvent.apply(this.ownerTree, arguments) === false){
113 return false;
114 }
115 }
116 return true;
117 },
118
119 isLeaf : function(){
120 return this.leaf === true;
121 },
122
123 setFirstChild : function(node){
124 this.firstChild = node;
125 },
126
127 setLastChild : function(node){
128 this.lastChild = node;
129 },
130
131 isLast : function(){
132 return (!this.parentNode ? true : this.parentNode.lastChild == this);
133 },
134
135 isFirst : function(){
136 return (!this.parentNode ? true : this.parentNode.firstChild == this);
137 },
138
139 hasChildNodes : function(){
140 return !this.isLeaf() && this.childNodes.length > 0;
141 },
142
143 appendChild : function(node){
144 var multi = false;
145 if(node instanceof Array){
146 multi = node;
147 }else if(arguments.length > 1){
148 multi = arguments;
149 }
150 // if passed an array or multiple args do them one by one
151 if(multi){
152 for(var i = 0, len = multi.length; i < len; i++) {
153 this.appendChild(multi[i]);
154 }
155 }else{
156 if(this.fireEvent('beforeappend', this.ownerTree, this, node) === false){
157 return false;
158 }
159 var index = this.childNodes.length;
160 var oldParent = node.parentNode;
161 // it's a move, make sure we move it cleanly
162 if(oldParent){
163 if(node.fireEvent('beforemove', node.getOwnerTree(), node, oldParent, this, index) === false){
164 return false;
165 }
166 oldParent.removeChild(node);
167 }
168 var index = this.childNodes.length;
169 if(index == 0){
170 this.setFirstChild(node);
171 }
172 this.childNodes.push(node);
173 node.parentNode = this;
174 var ps = this.childNodes[index-1];
175 if(ps){
176 node.previousSibling = ps;
177 ps.nextSibling = node;
178 }
179 this.setLastChild(node);
180 node.setOwnerTree(this.getOwnerTree());
181 this.fireEvent('append', this.ownerTree, this, node, index);
182 if(oldParent){
183 node.fireEvent('move', this.ownerTree, node, oldParent, this, index);
184 }
185 return node;
186 }
187 },
188
189 removeChild : function(node){
190 var index = this.childNodes.indexOf(node);
191 if(index == -1){
192 return false;
193 }
194 if(this.fireEvent('beforeremove', this.ownerTree, this, node) === false){
195 return false;
196 }
197
198 // remove it from childNodes collection
199 this.childNodes.splice(index, 1);
200
201 // update siblings
202 if(node.previousSibling){
203 node.previousSibling.nextSibling = node.nextSibling;
204 }
205 if(node.nextSibling){
206 node.nextSibling.previousSibling = node.previousSibling;
207 }
208
209 // update child refs
210 if(this.firstChild == node){
211 this.setFirstChild(node.nextSibling);
212 }
213 if(this.lastChild == node){
214 this.setLastChild(node.previousSibling);
215 }
216
217 node.setOwnerTree(null);
218 // clear any references from the node
219 node.parentNode = null;
220 node.previousSibling = null;
221 node.nextSibling = null;
222 this.fireEvent('remove', this.ownerTree, this, node);
223 return node;
224 },
225
226 insertBefore : function(node, refNode){
227 if(!refNode){ // like standard Dom, refNode can be null for append
228 return this.appendChild(node);
229 }
230 // nothing to do
231 if(node == refNode){
232 return false;
233 }
234
235 if(this.fireEvent('beforeinsert', this.ownerTree, this, node, refNode) === false){
236 return false;
237 }
238 var index = this.childNodes.indexOf(refNode);
239 var oldParent = node.parentNode;
240 var refIndex = index;
241
242 // when moving internally, indexes will change after remove
243 if(oldParent == this && this.childNodes.indexOf(node) < index){
244 refIndex--;
245 }
246
247 // it's a move, make sure we move it cleanly
248 if(oldParent){
249 if(node.fireEvent('beforemove', node.getOwnerTree(), node, oldParent, this, index, refNode) === false){
250 return false;
251 }
252 oldParent.removeChild(node);
253 }
254 if(refIndex == 0){
255 this.setFirstChild(node);
256 }
257 this.childNodes.splice(refIndex, 0, node);
258 node.parentNode = this;
259 var ps = this.childNodes[refIndex-1];
260 if(ps){
261 node.previousSibling = ps;
262 ps.nextSibling = node;
263 }
264 node.nextSibling = refNode;
265 node.setOwnerTree(this.getOwnerTree());
266 this.fireEvent('insert', this.ownerTree, this, node, refNode);
267 if(oldParent){
268 node.fireEvent('move', this.ownerTree, node, oldParent, this, refIndex, refNode);
269 }
270 return node;
271 },
272
273 item : function(index){
274 return this.childNodes[index];
275 },
276
277 replaceChild : function(newChild, oldChild){
278 this.insertBefore(newChild, oldChild);
279 this.removeChild(oldChild);
280 return oldChild;
281 },
282
283 indexOf : function(child){
284 return this.childNodes.indexOf(child);
285 },
286
287 getOwnerTree : function(){
288 // if it doesn't have one, look for one
289 if(!this.ownerTree){
290 var p = this;
291 while(p){
292 if(p.ownerTree){
293 this.ownerTree = p.ownerTree;
294 break;
295 }
296 p = p.parentNode;
297 }
298 }
299 return this.ownerTree;
300 },
301
302 setOwnerTree : function(tree){
303 // if it's move, we need to update everyone
304 if(tree != this.ownerTree){
305 if(this.ownerTree){
306 this.ownerTree.unregisterNode(this);
307 }
308 this.ownerTree = tree;
309 var cs = this.childNodes;
310 for(var i = 0, len = cs.length; i < len; i++) {
311 cs[i].setOwnerTree(tree);
312 }
313 if(tree){
314 tree.registerNode(this);
315 }
316 }
317 },
318
319 getPath : function(attr){
320 attr = attr || 'id';
321 var p = this.parentNode;
322 var b = [this.attributes[attr]];
323 while(p){
324 b.unshift(p.attributes[attr]);
325 p = p.parentNode;
326 }
327 var sep = this.getOwnerTree().pathSeparator;
328 return sep + b.join(sep);
329 },
330
331 bubble : function(fn, scope, args){
332 var p = this;
333 while(p){
334 if(fn.call(scope || p, args || p) === false){
335 break;
336 }
337 p = p.parentNode;
338 }
339 },
340
341 cascade : function(fn, scope, args){
342 if(fn.call(scope || this, args || this) !== false){
343 var cs = this.childNodes;
344 for(var i = 0, len = cs.length; i < len; i++) {
345 cs[i].cascade(fn, scope, args);
346 }
347 }
348 },
349
350 eachChild : function(fn, scope, args){
351 var cs = this.childNodes;
352 for(var i = 0, len = cs.length; i < len; i++) {
353 if(fn.call(scope || this, args || cs[i]) === false){
354 break;
355 }
356 }
357 },
358
359 findChild : function(attribute, value){
360 var cs = this.childNodes;
361 for(var i = 0, len = cs.length; i < len; i++) {
362 if(cs[i].attributes[attribute] == value){
363 return cs[i];
364 }
365 }
366 return null;
367 },
368
369 /**
370 * Sorts this nodes children using the supplied sort function
371 * @param {Function} fn
372 * @param {Object} scope
373 */
374 sort : function(fn, scope){
375 var cs = this.childNodes;
376 var len = cs.length;
377 if(len > 0){
378 var sortFn = scope ? function(){fn.apply(scope, arguments);} : fn;
379 cs.sort(sortFn);
380 for(var i = 0; i < len; i++){
381 var n = cs[i];
382 n.previousSibling = cs[i-1];
383 n.nextSibling = cs[i+1];
384 if(i == 0){
385 this.setFirstChild(n);
386 }
387 if(i == len-1){
388 this.setLastChild(n);
389 }
390 }
391 }
392 },
393
394 contains : function(node){
395 return node.isAncestor(this);
396 },
397
398 isAncestor : function(node){
399 var p = this.parentNode;
400 while(p){
401 if(p == node){
402 return true;
403 }
404 p = p.parentNode;
405 }
406 return false;
407 },
408
409 toString : function(){
410 return '[Node'+(this.id?' '+this.id:'')+']';
411 }
412});
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 @@
1/**
2 * @class YAHOO.ext.grid.XMLDataModel
3 * This is an implementation of a DataModel used by the Grid. It works
4 * with XML data.
5 * <br>Example schema from Amazon search:
6 * <pre><code>
7 * var schema = {
8 * tagName: 'Item',
9 * id: 'ASIN',
10 * fields: ['Author', 'Title', 'Manufacturer', 'ProductGroup']
11 * };
12 * </code></pre>
13 * @extends YAHOO.ext.grid.LoadableDataModel
14 * @constructor
15 * @param {Object} schema The schema to use
16 * @param {XMLDocument} xml An XML document to load immediately
17*/
18YAHOO.ext.grid.XMLDataModel = function(schema, xml){
19 YAHOO.ext.grid.XMLDataModel.superclass.constructor.call(this, YAHOO.ext.grid.LoadableDataModel.XML);
20 /**@private*/
21 this.schema = schema;
22 this.xml = xml;
23 if(xml){
24 this.loadData(xml);
25 }
26 this.idSeed = 0;
27};
28YAHOO.extendX(YAHOO.ext.grid.XMLDataModel, YAHOO.ext.grid.LoadableDataModel, {
29
30 getDocument: function(){
31 return this.xml;
32 },
33
34 /**
35 * Overrides loadData in LoadableDataModel to process XML
36 * @param {XMLDocument} doc The document to load
37 * @param {<i>Function</i>} callback (optional) callback to call when loading is complete
38 * @param {<i>Boolean</i>} keepExisting (optional) true to keep existing data
39 * @param {<i>Number</i>} insertIndex (optional) if present, loaded data is inserted at the specified index instead of overwriting existing data
40 */
41 loadData: function(doc, callback, keepExisting, insertIndex){
42 this.xml = doc;
43 var idField = this.schema.id;
44 var fields = this.schema.fields;
45 if(this.schema.totalTag){
46 this.totalCount = null;
47 var totalNode = doc.getElementsByTagName(this.schema.totalTag);
48 if(totalNode && totalNode.item(0) && totalNode.item(0).firstChild) {
49 var v = parseInt(totalNode.item(0).firstChild.nodeValue, 10);
50 if(!isNaN(v)){
51 this.totalCount = v;
52 }
53 }
54 }
55 var rowData = [];
56 var nodes = doc.getElementsByTagName(this.schema.tagName);
57 if(nodes && nodes.length > 0) {
58 for(var i = 0; i < nodes.length; i++) {
59 var node = nodes.item(i);
60 var colData = [];
61 colData.node = node;
62 colData.id = this.getNamedValue(node, idField, String(++this.idSeed));
63 for(var j = 0; j < fields.length; j++) {
64 var val = this.getNamedValue(node, fields[j], "");
65 if(this.preprocessors[j]){
66 val = this.preprocessors[j](val);
67 }
68 colData.push(val);
69 }
70 rowData.push(colData);
71 }
72 }
73 if(keepExisting !== true){
74 YAHOO.ext.grid.XMLDataModel.superclass.removeAll.call(this);
75 }
76 if(typeof insertIndex != 'number'){
77 insertIndex = this.getRowCount();
78 }
79 YAHOO.ext.grid.XMLDataModel.superclass.insertRows.call(this, insertIndex, rowData);
80 if(typeof callback == 'function'){
81 callback(this, true);
82 }
83 this.fireLoadEvent();
84 },
85
86 /**
87 * Adds a row to this DataModel and syncs the XML document
88 * @param {String} id The id of the row, if null the next row index is used
89 * @param {Array} cellValues The cell values for this row
90 * @return {Number} The index of the new row (if the model is sorted this index may not be accurate)
91 */
92 addRow: function(id, cellValues){
93 var node = this.createNode(this.xml, id, cellValues);
94 cellValues.id = id || ++this.idSeed;
95 cellValues.node = node;
96 return YAHOO.ext.grid.XMLDataModel.superclass.addRow.call(this, cellValues);
97 },
98
99 /**
100 * Inserts a row into this DataModel and syncs the XML document
101 * @param {Number} index The index to insert the row
102 * @param {String} id The id of the row, if null the next row index is used
103 * @param {Array} cellValues The cell values for this row
104 * @return {Number} The index of the new row (if the model is sorted this index may not be accurate)
105 */
106 insertRow: function(index, id, cellValues){
107 var node = this.createNode(this.xml, id, cellValues);
108 cellValues.id = id || ++this.idSeed;
109 cellValues.node = node;
110 return YAHOO.ext.grid.XMLDataModel.superclass.insertRow.call(this, index, cellValues);
111 },
112
113 /**
114 * Removes the row from DataModel and syncs the XML document
115 * @param {Number} index The index of the row to remove
116 */
117 removeRow: function(index){
118 var node = this.data[index].node;
119 node.parentNode.removeChild(node);
120 YAHOO.ext.grid.XMLDataModel.superclass.removeRow.call(this, index, index);
121 },
122
123 getNode: function(rowIndex){
124 return this.data[rowIndex].node;
125 },
126
127 /**
128 * Override this method to define your own node creation routine for when new rows are added.
129 * By default this method clones the first node and sets the column values in the newly cloned node.
130 * In many instances this will not work and you will have to create the node manually.
131 * @param {XMLDocument} xmlDoc The xml document being used by this model
132 * @param {String/Number} id The row id
133 * @param {Array} colData The column data for the new node
134 * @return {XMLNode} The created node
135 */
136 createNode: function(xmlDoc, id, colData){
137 var template = this.data[0].node;
138 var newNode = template.cloneNode(true);
139 var fields = this.schema.fields;
140 for(var i = 0, len = fields.length; i < len; i++){
141 var nodeValue = colData[i];
142 if(this.postprocessors[i]){
143 nodeValue = this.postprocessors[i](nodeValue);
144 }
145 this.setNamedValue(newNode, fields[i], nodeValue);
146 }
147 if(id){
148 this.setNamedValue(newNode, this.schema.idField, id);
149 }
150 template.parentNode.appendChild(newNode);
151 return newNode;
152 },
153
154 /**
155 * @private
156 * Convenience function looks for value in attributes, then in children tags - also
157 * normalizes namespace matches (ie matches ns:tag, FireFox matches tag and not ns:tag).
158 */
159 getNamedValue: function(node, name, defaultValue){
160 if(!node || !name){
161 return defaultValue;
162 }
163 var nodeValue = defaultValue;
164 var attrNode = node.attributes.getNamedItem(name);
165 if(attrNode) {
166 nodeValue = attrNode.value;
167 } else {
168 var childNode = node.getElementsByTagName(name);
169 if(childNode && childNode.item(0) && childNode.item(0).firstChild) {
170 nodeValue = childNode.item(0).firstChild.nodeValue;
171 }else{
172 // try to strip namespace for FireFox
173 var index = name.indexOf(':');
174 if(index > 0){
175 return this.getNamedValue(node, name.substr(index+1), defaultValue);
176 }
177 }
178 }
179 return nodeValue;
180 },
181
182 /**
183 * @private
184 * Convenience function set a value in the underlying xml node.
185 */
186 setNamedValue: function(node, name, value){
187 if(!node || !name){
188 return;
189 }
190 var attrNode = node.attributes.getNamedItem(name);
191 if(attrNode) {
192 attrNode.value = value;
193 return;
194 }
195 var childNode = node.getElementsByTagName(name);
196 if(childNode && childNode.item(0) && childNode.item(0).firstChild) {
197 childNode.item(0).firstChild.nodeValue = value;
198 }else{
199 // try to strip namespace for FireFox
200 var index = name.indexOf(':');
201 if(index > 0){
202 this.setNamedValue(node, name.substr(index+1), value);
203 }
204 }
205 },
206
207 /**
208 * Overrides DefaultDataModel.setValueAt to update the underlying XML Document
209 * @param {Object} value The new value
210 * @param {Number} rowIndex
211 * @param {Number} colIndex
212 */
213 setValueAt: function(value, rowIndex, colIndex){
214 var node = this.data[rowIndex].node;
215 if(node){
216 var nodeValue = value;
217 if(this.postprocessors[colIndex]){
218 nodeValue = this.postprocessors[colIndex](value);
219 }
220 this.setNamedValue(node, this.schema.fields[colIndex], nodeValue);
221 }
222 YAHOO.ext.grid.XMLDataModel.superclass.setValueAt.call(this, value, rowIndex, colIndex);
223 },
224
225 /**
226 * Overrides getRowId in DefaultDataModel to return the ID value of the specified node.
227 * @param {Number} rowIndex
228 * @return {Number}
229 */
230 getRowId: function(rowIndex){
231 return this.data[rowIndex].id;
232 },
233
234 addRows : function(rowData){
235 for(var j = 0, len = rowData.length; j < len; j++){
236 var cellValues = rowData[j];
237 var id = ++this.idSeed;
238 var node = this.createNode(this.xml, id, cellValues);
239 cellValues.node=node;
240 cellValues.id = cellValues.id || id;
241 YAHOO.ext.grid.XMLDataModel.superclass.addRow.call(this,cellValues);
242 }
243 },
244
245 insertRows : function(index, rowData){
246 // copy original array so it is not reversed
247 rowData = rowData.slice(0).reverse();
248 for(var j = 0, len = rowData.length; j < len; j++){
249 var cellValues = rowData[j];
250 var id = ++this.idSeed;
251 var node = this.createNode(this.xml, id, cellValues);
252 cellValues.id = cellValues.id || id;
253 cellValues.node = node;
254 YAHOO.ext.grid.XMLDataModel.superclass.insertRow.call(this, index, cellValues);
255 }
256 }
257});
258
259YAHOO.ext.grid.XMLQueryDataModel = function(){
260 YAHOO.ext.grid.XMLQueryDataModel.superclass.constructor.apply(this, arguments);
261};
262YAHOO.extendX(YAHOO.ext.grid.XMLQueryDataModel, YAHOO.ext.grid.XMLDataModel, {
263 getNamedValue: function(node, name, defaultValue){
264 if(!node || !name){
265 return defaultValue;
266 }
267 var nodeValue = defaultValue;
268 var childNode = cssQuery(name, node);
269 if(childNode && childNode[0]) {
270 nodeValue = childNode[0].firstChild.nodeValue;
271 }
272 return nodeValue;
273 }
274});