New file |
0,0 → 1,600 |
if(!dojo._hasResource['dojox.grid._data.model']){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
dojo._hasResource['dojox.grid._data.model'] = true; |
dojo.provide('dojox.grid._data.model'); |
dojo.require('dojox.grid._data.fields'); |
|
dojo.declare("dojox.grid.data.Model", null, { |
// summary: |
// Base abstract grid data model. |
// Makes no assumptions about the structure of grid data. |
constructor: function(inFields, inData){ |
this.observers = []; |
this.fields = new dojox.grid.data.Fields(); |
if(inFields){ |
this.fields.set(inFields); |
} |
this.setData(inData); |
}, |
count: 0, |
updating: 0, |
// observers |
observer: function(inObserver, inPrefix){ |
this.observers.push({o: inObserver, p: inPrefix||'model' }); |
}, |
notObserver: function(inObserver){ |
for(var i=0, m, o; (o=this.observers[i]); i++){ |
if(o.o==inObserver){ |
this.observers.splice(i, 1); |
return; |
} |
} |
}, |
notify: function(inMsg, inArgs){ |
if(!this.isUpdating()){ |
var a = inArgs || []; |
for(var i=0, m, o; (o=this.observers[i]); i++){ |
m = o.p + inMsg, o = o.o; |
(m in o)&&(o[m].apply(o, a)); |
} |
} |
}, |
// updates |
clear: function(){ |
this.fields.clear(); |
this.clearData(); |
}, |
beginUpdate: function(){ |
this.updating++; |
}, |
endUpdate: function(){ |
if(this.updating){ |
this.updating--; |
} |
/*if(this.updating){ |
if(!(--this.updating)){ |
this.change(); |
} |
} |
}*/ |
}, |
isUpdating: function(){ |
return Boolean(this.updating); |
}, |
// data |
clearData: function(){ |
this.setData(null); |
}, |
// observer events |
change: function(){ |
this.notify("Change", arguments); |
}, |
insertion: function(/* index */){ |
this.notify("Insertion", arguments); |
this.notify("Change", arguments); |
}, |
removal: function(/* keys */){ |
this.notify("Removal", arguments); |
this.notify("Change", arguments); |
}, |
// insert |
insert: function(inData /*, index */){ |
if(!this._insert.apply(this, arguments)){ |
return false; |
} |
this.insertion.apply(this, dojo._toArray(arguments, 1)); |
return true; |
}, |
// remove |
remove: function(inData /*, index */){ |
if(!this._remove.apply(this, arguments)){ |
return false; |
} |
this.removal.apply(this, arguments); |
return true; |
}, |
// sort |
canSort: function(/* (+|-)column_index+1, ... */){ |
return this.sort != null; |
}, |
makeComparator: function(inIndices){ |
var idx, col, field, result = null; |
for(var i=inIndices.length-1; i>=0; i--){ |
idx = inIndices[i]; |
col = Math.abs(idx) - 1; |
if(col >= 0){ |
field = this.fields.get(col); |
result = this.generateComparator(field.compare, field.key, idx > 0, result); |
} |
} |
return result; |
}, |
sort: null, |
dummy: 0 |
}); |
|
dojo.declare("dojox.grid.data.Rows", dojox.grid.data.Model, { |
// observer events |
allChange: function(){ |
this.notify("AllChange", arguments); |
this.notify("Change", arguments); |
}, |
rowChange: function(){ |
this.notify("RowChange", arguments); |
}, |
datumChange: function(){ |
this.notify("DatumChange", arguments); |
}, |
// copyRow: function(inRowIndex); // abstract |
// update |
beginModifyRow: function(inRowIndex){ |
if(!this.cache[inRowIndex]){ |
this.cache[inRowIndex] = this.copyRow(inRowIndex); |
} |
}, |
endModifyRow: function(inRowIndex){ |
var cache = this.cache[inRowIndex]; |
if(cache){ |
var data = this.getRow(inRowIndex); |
if(!dojox.grid.arrayCompare(cache, data)){ |
this.update(cache, data, inRowIndex); |
} |
delete this.cache[inRowIndex]; |
} |
}, |
cancelModifyRow: function(inRowIndex){ |
var cache = this.cache[inRowIndex]; |
if(cache){ |
this.setRow(cache, inRowIndex); |
delete this.cache[inRowIndex]; |
} |
}, |
generateComparator: function(inCompare, inField, inTrueForAscend, inSubCompare){ |
return function(a, b){ |
var ineq = inCompare(a[inField], b[inField]); |
return ineq ? (inTrueForAscend ? ineq : -ineq) : inSubCompare && inSubCompare(a, b); |
} |
} |
}); |
|
dojo.declare("dojox.grid.data.Table", dojox.grid.data.Rows, { |
// summary: |
// Basic grid data model for static data in the form of an array of rows |
// that are arrays of cell data |
constructor: function(){ |
this.cache = []; |
}, |
colCount: 0, // tables introduce cols |
data: null, |
cache: null, |
// morphology |
measure: function(){ |
this.count = this.getRowCount(); |
this.colCount = this.getColCount(); |
this.allChange(); |
//this.notify("Measure"); |
}, |
getRowCount: function(){ |
return (this.data ? this.data.length : 0); |
}, |
getColCount: function(){ |
return (this.data && this.data.length ? this.data[0].length : this.fields.count()); |
}, |
badIndex: function(inCaller, inDescriptor){ |
console.debug('dojox.grid.data.Table: badIndex'); |
}, |
isGoodIndex: function(inRowIndex, inColIndex){ |
return (inRowIndex >= 0 && inRowIndex < this.count && (arguments.length < 2 || (inColIndex >= 0 && inColIndex < this.colCount))); |
}, |
// access |
getRow: function(inRowIndex){ |
return this.data[inRowIndex]; |
}, |
copyRow: function(inRowIndex){ |
return this.getRow(inRowIndex).slice(0); |
}, |
getDatum: function(inRowIndex, inColIndex){ |
return this.data[inRowIndex][inColIndex]; |
}, |
get: function(){ |
throw('Plain "get" no longer supported. Use "getRow" or "getDatum".'); |
}, |
setData: function(inData){ |
this.data = (inData || []); |
this.allChange(); |
}, |
setRow: function(inData, inRowIndex){ |
this.data[inRowIndex] = inData; |
this.rowChange(inData, inRowIndex); |
this.change(); |
}, |
setDatum: function(inDatum, inRowIndex, inColIndex){ |
this.data[inRowIndex][inColIndex] = inDatum; |
this.datumChange(inDatum, inRowIndex, inColIndex); |
}, |
set: function(){ |
throw('Plain "set" no longer supported. Use "setData", "setRow", or "setDatum".'); |
}, |
setRows: function(inData, inRowIndex){ |
for(var i=0, l=inData.length, r=inRowIndex; i<l; i++, r++){ |
this.setRow(inData[i], r); |
} |
}, |
// update |
update: function(inOldData, inNewData, inRowIndex){ |
//delete this.cache[inRowIndex]; |
//this.setRow(inNewData, inRowIndex); |
return true; |
}, |
// insert |
_insert: function(inData, inRowIndex){ |
dojox.grid.arrayInsert(this.data, inRowIndex, inData); |
this.count++; |
return true; |
}, |
// remove |
_remove: function(inKeys){ |
for(var i=inKeys.length-1; i>=0; i--){ |
dojox.grid.arrayRemove(this.data, inKeys[i]); |
} |
this.count -= inKeys.length; |
return true; |
}, |
// sort |
sort: function(/* (+|-)column_index+1, ... */){ |
this.data.sort(this.makeComparator(arguments)); |
}, |
swap: function(inIndexA, inIndexB){ |
dojox.grid.arraySwap(this.data, inIndexA, inIndexB); |
this.rowChange(this.getRow(inIndexA), inIndexA); |
this.rowChange(this.getRow(inIndexB), inIndexB); |
this.change(); |
}, |
dummy: 0 |
}); |
|
dojo.declare("dojox.grid.data.Objects", dojox.grid.data.Table, { |
constructor: function(inFields, inData, inKey){ |
if(!inFields){ |
this.autoAssignFields(); |
} |
}, |
autoAssignFields: function(){ |
var d = this.data[0], i = 0; |
for(var f in d){ |
this.fields.get(i++).key = f; |
} |
}, |
getDatum: function(inRowIndex, inColIndex){ |
return this.data[inRowIndex][this.fields.get(inColIndex).key]; |
} |
}); |
|
dojo.declare("dojox.grid.data.Dynamic", dojox.grid.data.Table, { |
// summary: |
// Grid data model for dynamic data such as data retrieved from a server. |
// Retrieves data automatically when requested and provides notification when data is received |
constructor: function(){ |
this.page = []; |
this.pages = []; |
}, |
page: null, |
pages: null, |
rowsPerPage: 100, |
requests: 0, |
bop: -1, |
eop: -1, |
// data |
clearData: function(){ |
this.pages = []; |
this.bop = this.eop = -1; |
this.setData([]); |
}, |
getRowCount: function(){ |
return this.count; |
}, |
getColCount: function(){ |
return this.fields.count(); |
}, |
setRowCount: function(inCount){ |
this.count = inCount; |
this.change(); |
}, |
// paging |
requestsPending: function(inBoolean){ |
}, |
rowToPage: function(inRowIndex){ |
return (this.rowsPerPage ? Math.floor(inRowIndex / this.rowsPerPage) : inRowIndex); |
}, |
pageToRow: function(inPageIndex){ |
return (this.rowsPerPage ? this.rowsPerPage * inPageIndex : inPageIndex); |
}, |
requestRows: function(inRowIndex, inCount){ |
// summary: |
// stub. Fill in to perform actual data row fetching logic. The |
// returning logic must provide the data back to the system via |
// setRow |
}, |
rowsProvided: function(inRowIndex, inCount){ |
this.requests--; |
if(this.requests == 0){ |
this.requestsPending(false); |
} |
}, |
requestPage: function(inPageIndex){ |
var row = this.pageToRow(inPageIndex); |
var count = Math.min(this.rowsPerPage, this.count - row); |
if(count > 0){ |
this.requests++; |
this.requestsPending(true); |
setTimeout(dojo.hitch(this, "requestRows", row, count), 1); |
//this.requestRows(row, count); |
} |
}, |
needPage: function(inPageIndex){ |
if(!this.pages[inPageIndex]){ |
this.pages[inPageIndex] = true; |
this.requestPage(inPageIndex); |
} |
}, |
preparePage: function(inRowIndex, inColIndex){ |
if(inRowIndex < this.bop || inRowIndex >= this.eop){ |
var pageIndex = this.rowToPage(inRowIndex); |
this.needPage(pageIndex); |
this.bop = pageIndex * this.rowsPerPage; |
this.eop = this.bop + (this.rowsPerPage || this.count); |
} |
}, |
isRowLoaded: function(inRowIndex){ |
return Boolean(this.data[inRowIndex]); |
}, |
// removal |
removePages: function(inRowIndexes){ |
for(var i=0, r; ((r=inRowIndexes[i]) != undefined); i++){ |
this.pages[this.rowToPage(r)] = false; |
} |
this.bop = this.eop =-1; |
}, |
remove: function(inRowIndexes){ |
this.removePages(inRowIndexes); |
dojox.grid.data.Table.prototype.remove.apply(this, arguments); |
}, |
// access |
getRow: function(inRowIndex){ |
var row = this.data[inRowIndex]; |
if(!row){ |
this.preparePage(inRowIndex); |
} |
return row; |
}, |
getDatum: function(inRowIndex, inColIndex){ |
var row = this.getRow(inRowIndex); |
return (row ? row[inColIndex] : this.fields.get(inColIndex).na); |
}, |
setDatum: function(inDatum, inRowIndex, inColIndex){ |
var row = this.getRow(inRowIndex); |
if(row){ |
row[inColIndex] = inDatum; |
this.datumChange(inDatum, inRowIndex, inColIndex); |
}else{ |
console.debug('[' + this.declaredClass + '] dojox.grid.data.dynamic.set: cannot set data on an non-loaded row'); |
} |
}, |
// sort |
canSort: function(){ |
return false; |
} |
}); |
|
// FIXME: deprecated: (included for backward compatibility only) |
dojox.grid.data.table = dojox.grid.data.Table; |
dojox.grid.data.dynamic = dojox.grid.data.Dyanamic; |
|
// we treat dojo.data stores as dynamic stores because no matter how they got |
// here, they should always fill that contract |
dojo.declare("dojox.grid.data.DojoData", dojox.grid.data.Dynamic, { |
// summary: |
// A grid data model for dynamic data retreived from a store which |
// implements the dojo.data API set. Retrieves data automatically when |
// requested and provides notification when data is received |
// description: |
// This store subclasses the Dynamic grid data object in order to |
// provide paginated data access support, notification and view |
// updates for stores which support those features, and simple |
// field/column mapping for all dojo.data stores. |
constructor: function(inFields, inData, args){ |
this.count = 1; |
this._rowIdentities = {}; |
if(args){ |
dojo.mixin(this, args); |
} |
if(this.store){ |
// NOTE: we assume Read and Identity APIs for all stores! |
var f = this.store.getFeatures(); |
this._canNotify = f['dojo.data.api.Notification']; |
this._canWrite = f['dojo.data.api.Write']; |
|
if(this._canNotify){ |
dojo.connect(this.store, "onSet", this, "_storeDatumChange"); |
} |
} |
}, |
markupFactory: function(args, node){ |
return new dojox.grid.data.DojoData(null, null, args); |
}, |
query: { name: "*" }, // default, stupid query |
store: null, |
_canNotify: false, |
_canWrite: false, |
_rowIdentities: {}, |
clientSort: false, |
// data |
setData: function(inData){ |
this.store = inData; |
this.data = []; |
this.allChange(); |
}, |
setRowCount: function(inCount){ |
//console.debug("inCount:", inCount); |
this.count = inCount; |
this.allChange(); |
}, |
beginReturn: function(inCount){ |
if(this.count != inCount){ |
// this.setRowCount(0); |
// this.clear(); |
// console.debug(this.count, inCount); |
this.setRowCount(inCount); |
} |
}, |
_setupFields: function(dataItem){ |
// abort if we already have setup fields |
if(this.fields._nameMaps){ |
return; |
} |
// set up field/index mappings |
var m = {}; |
//console.debug("setting up fields", m); |
var fields = dojo.map(this.store.getAttributes(dataItem), |
function(item, idx){ |
m[item] = idx; |
m[idx+".idx"] = item; |
// name == display name, key = property name |
return { name: item, key: item }; |
}, |
this |
); |
this.fields._nameMaps = m; |
// console.debug("new fields:", fields); |
this.fields.set(fields); |
this.notify("FieldsChange"); |
}, |
_getRowFromItem: function(item){ |
// gets us the row object (and row index) of an item |
}, |
processRows: function(items, store){ |
// console.debug(arguments); |
if(!items){ return; } |
this._setupFields(items[0]); |
dojo.forEach(items, function(item, idx){ |
var row = {}; |
row.__dojo_data_item = item; |
dojo.forEach(this.fields.values, function(a){ |
row[a.name] = this.store.getValue(item, a.name)||""; |
}, this); |
// FIXME: where else do we need to keep this in sync? |
this._rowIdentities[this.store.getIdentity(item)] = store.start+idx; |
this.setRow(row, store.start+idx); |
}, this); |
// FIXME: |
// Q: scott, steve, how the hell do we actually get this to update |
// the visible UI for these rows? |
// A: the goal is that Grid automatically updates to reflect changes |
// in model. In this case, setRow -> rowChanged -> (observed by) Grid -> modelRowChange -> updateRow |
}, |
// request data |
requestRows: function(inRowIndex, inCount){ |
var row = inRowIndex || 0; |
var params = { |
start: row, |
count: this.rowsPerPage, |
query: this.query, |
onBegin: dojo.hitch(this, "beginReturn"), |
// onItem: dojo.hitch(console, "debug"), |
onComplete: dojo.hitch(this, "processRows") // add to deferred? |
} |
// console.debug("requestRows:", row, this.rowsPerPage); |
this.store.fetch(params); |
}, |
getDatum: function(inRowIndex, inColIndex){ |
//console.debug("getDatum", inRowIndex, inColIndex); |
var row = this.getRow(inRowIndex); |
var field = this.fields.values[inColIndex]; |
return row && field ? row[field.name] : field ? field.na : '?'; |
//var idx = row && this.fields._nameMaps[inColIndex+".idx"]; |
//return (row ? row[idx] : this.fields.get(inColIndex).na); |
}, |
setDatum: function(inDatum, inRowIndex, inColIndex){ |
var n = this.fields._nameMaps[inColIndex+".idx"]; |
// console.debug("setDatum:", "n:"+n, inDatum, inRowIndex, inColIndex); |
if(n){ |
this.data[inRowIndex][n] = inDatum; |
this.datumChange(inDatum, inRowIndex, inColIndex); |
} |
}, |
// modification, update and store eventing |
copyRow: function(inRowIndex){ |
var row = {}; |
var backstop = {}; |
var src = this.getRow(inRowIndex); |
for(var x in src){ |
if(src[x] != backstop[x]){ |
row[x] = src[x]; |
} |
} |
return row; |
}, |
_attrCompare: function(cache, data){ |
dojo.forEach(this.fields.values, function(a){ |
if(cache[a.name] != data[a.name]){ return false; } |
}, this); |
return true; |
}, |
endModifyRow: function(inRowIndex){ |
var cache = this.cache[inRowIndex]; |
if(cache){ |
var data = this.getRow(inRowIndex); |
if(!this._attrCompare(cache, data)){ |
this.update(cache, data, inRowIndex); |
} |
delete this.cache[inRowIndex]; |
} |
}, |
cancelModifyRow: function(inRowIndex){ |
// console.debug("cancelModifyRow", arguments); |
var cache = this.cache[inRowIndex]; |
if(cache){ |
this.setRow(cache, inRowIndex); |
delete this.cache[inRowIndex]; |
} |
}, |
_storeDatumChange: function(item, attr, oldVal, newVal){ |
// the store has changed some data under us, need to update the display |
var rowId = this._rowIdentities[this.store.getIdentity(item)]; |
var row = this.getRow(rowId); |
row[attr] = newVal; |
var colId = this.fields._nameMaps[attr]; |
this.notify("DatumChange", [ newVal, rowId, colId ]); |
}, |
datumChange: function(value, rowIdx, colIdx){ |
if(this._canWrite){ |
// we're chaning some data, which means we need to write back |
var row = this.getRow(rowIdx); |
var field = this.fields._nameMaps[colIdx+".idx"]; |
this.store.setValue(row.__dojo_data_item, field, value); |
// we don't need to call DatumChange, an eventing store will tell |
// us about the row change events |
}else{ |
// we can't write back, so just go ahead and change our local copy |
// of the data |
this.notify("DatumChange", arguments); |
} |
}, |
insertion: function(/* index */){ |
console.debug("Insertion", arguments); |
this.notify("Insertion", arguments); |
this.notify("Change", arguments); |
}, |
removal: function(/* keys */){ |
console.debug("Removal", arguments); |
this.notify("Removal", arguments); |
this.notify("Change", arguments); |
}, |
// sort |
canSort: function(){ |
// Q: Return true and re-issue the queries? |
// A: Return true only. Re-issue the query in 'sort'. |
return this.clientSort; |
} |
}); |
|
} |