text: 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;
  	}
  });
  
  }
  