text: /*
  	Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved.
  	Available via Academic Free License >= 2.1 OR the modified BSD license.
  	see: http://dojotoolkit.org/license for details
  */
  
  
  if(!dojo._hasResource["dojox.grid._Grid"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  dojo._hasResource["dojox.grid._Grid"] = true;
  dojo.provide("dojox.grid._Grid");
  
  dojo.require("dijit.dijit");
  dojo.require("dijit.Menu");
  
  dojo.require("dojox.html.metrics");
  dojo.require("dojox.grid.util");
  dojo.require("dojox.grid._Scroller");
  dojo.require("dojox.grid._Layout");
  dojo.require("dojox.grid._View");
  dojo.require("dojox.grid._ViewManager");
  dojo.require("dojox.grid._RowManager");
  dojo.require("dojox.grid._FocusManager");
  dojo.require("dojox.grid._EditManager");
  dojo.require("dojox.grid.Selection");
  dojo.require("dojox.grid._RowSelector");
  dojo.require("dojox.grid._Events");
  
  
  dojo.requireLocalization("dijit", "loading", null, "ROOT,ar,ca,cs,da,de,el,es,fi,fr,he,hu,it,ja,ko,nb,nl,pl,pt,pt-pt,ro,ru,sk,sl,sv,th,tr,zh,zh-tw");
  
  (function(){
  	// NOTE: this is for backwards compatibility with Dojo 1.3
  	if(!dojo.isCopyKey){
  		dojo.isCopyKey = dojo.dnd.getCopyKeyState;
  	}
  	/*=====
  	dojox.grid.__CellDef = function(){
  		//	name: String?
  		//		The text to use in the header of the grid for this cell.
  		//	get: Function?
  		//		function(rowIndex){} rowIndex is of type Integer.  This
  		//		function will be called when a cell	requests data.  Returns the
  		//		unformatted data for the cell.
  		//	value: String?
  		//		If "get" is not specified, this is used as the data for the cell.
  		//	defaultValue: String?
  		//		If "get" and "value" aren't specified or if "get" returns an undefined
  		//		value, this is used as the data for the cell.  "formatter" is not run
  		//		on this if "get" returns an undefined value.
  		//	formatter: Function?
  		//		function(data, rowIndex){} data is of type anything, rowIndex
  		//		is of type Integer.  This function will be called after the cell
  		//		has its data but before it passes it back to the grid to render.
  		//		Returns the formatted version of the cell's data.
  		//	type: dojox.grid.cells._Base|Function?
  		//		TODO
  		//	editable: Boolean?
  		//		Whether this cell should be editable or not.
  		//	hidden: Boolean?
  		//		If true, the cell will not be displayed.
  		//	noresize: Boolean?
  		//		If true, the cell will not be able to be resized.
  		//	width: Integer|String?
  		//		A CSS size.  If it's an Integer, the width will be in em's.
  		//	colSpan: Integer?
  		//		How many columns to span this cell.  Will not work in the first
  		//		sub-row of cells.
  		//	rowSpan: Integer?
  		//		How many sub-rows to span this cell.
  		//	styles: String?
  		//		A string of styles to apply to both the header cell and main
  		//		grid cells.  Must end in a ';'.
  		//	headerStyles: String?
  		//		A string of styles to apply to just the header cell.  Must end
  		//		in a ';'
  		//	cellStyles: String?
  		//		A string of styles to apply to just the main grid cells.  Must
  		//		end in a ';'
  		//	classes: String?
  		//		A space separated list of classes to apply to both the header
  		//		cell and the main grid cells.
  		//	headerClasses: String?
  		//		A space separated list of classes to apply to just the header
  		//		cell.
  		//	cellClasses: String?
  		//		A space separated list of classes to apply to just the main
  		//		grid cells.
  		//	attrs: String?
  		//		A space separated string of attribute='value' pairs to add to
  		//		the header cell element and main grid cell elements.
  		this.name = name;
  		this.value = value;
  		this.get = get;
  		this.formatter = formatter;
  		this.type = type;
  		this.editable = editable;
  		this.hidden = hidden;
  		this.width = width;
  		this.colSpan = colSpan;
  		this.rowSpan = rowSpan;
  		this.styles = styles;
  		this.headerStyles = headerStyles;
  		this.cellStyles = cellStyles;
  		this.classes = classes;
  		this.headerClasses = headerClasses;
  		this.cellClasses = cellClasses;
  		this.attrs = attrs;
  	}
  	=====*/
  
  	/*=====
  	dojox.grid.__ViewDef = function(){
  		//	noscroll: Boolean?
  		//		If true, no scrollbars will be rendered without scrollbars.
  		//	width: Integer|String?
  		//		A CSS size.  If it's an Integer, the width will be in em's. If
  		//		"noscroll" is true, this value is ignored.
  		//	cells: dojox.grid.__CellDef[]|Array[dojox.grid.__CellDef[]]?
  		//		The structure of the cells within this grid.
  		//	type: String?
  		//		A string containing the constructor of a subclass of
  		//		dojox.grid._View.  If this is not specified, dojox.grid._View
  		//		is used.
  		//	defaultCell: dojox.grid.__CellDef?
  		//		A cell definition with default values for all cells in this view.  If
  		//		a property is defined in a cell definition in the "cells" array and
  		//		this property, the cell definition's property will override this
  		//		property's property.
  		//	onBeforeRow: Function?
  		//		function(rowIndex, cells){} rowIndex is of type Integer, cells
  		//		is of type Array[dojox.grid.__CellDef[]].  This function is called
  		//		before each row of data is rendered.  Before the header is
  		//		rendered, rowIndex will be -1.  "cells" is a reference to the
  		//		internal structure of this view's cells so any changes you make to
  		//		it will persist between calls.
  		//	onAfterRow: Function?
  		//		function(rowIndex, cells, rowNode){} rowIndex is of type Integer, cells
  		//		is of type Array[dojox.grid.__CellDef[]], rowNode is of type DOMNode.
  		//		This function is called	after each row of data is rendered.  After the
  		//		header is rendered, rowIndex will be -1.  "cells" is a reference to the
  		//		internal structure of this view's cells so any changes you make to
  		//		it will persist between calls.
  		this.noscroll = noscroll;
  		this.width = width;
  		this.cells = cells;
  		this.type = type;
  		this.defaultCell = defaultCell;
  		this.onBeforeRow = onBeforeRow;
  		this.onAfterRow = onAfterRow;
  	}
  	=====*/
  
  	dojo.declare('dojox.grid._Grid',
  		[ dijit._Widget, dijit._Templated, dojox.grid._Events ],
  		{
  		// summary:
  		// 		A grid widget with virtual scrolling, cell editing, complex rows,
  		// 		sorting, fixed columns, sizeable columns, etc.
  		//
  		//	description:
  		//		_Grid provides the full set of grid features without any
  		//		direct connection to a data store.
  		//
  		//		The grid exposes a get function for the grid, or optionally
  		//		individual columns, to populate cell contents.
  		//
  		//		The grid is rendered based on its structure, an object describing
  		//		column and cell layout.
  		//
  		//	example:
  		//		A quick sample:
  		//
  		//		define a get function
  		//	|	function get(inRowIndex){ // called in cell context
  		//	|		return [this.index, inRowIndex].join(', ');
  		//	|	}
  		//
  		//		define the grid structure:
  		//	|	var structure = [ // array of view objects
  		//	|		{ cells: [// array of rows, a row is an array of cells
  		//	|			[
  		//	|				{ name: "Alpha", width: 6 },
  		//	|				{ name: "Beta" },
  		//	|				{ name: "Gamma", get: get }]
  		//	|		]}
  		//	|	];
  		//
  		//	|	<div id="grid"
  		//	|		rowCount="100" get="get"
  		//	|		structure="structure"
  		//	|		dojoType="dojox.grid._Grid"></div>
  
  		templateString:"<div hidefocus=\"hidefocus\" wairole=\"grid\" dojoAttachEvent=\"onmouseout:_mouseOut\">\r\n\t<div class=\"dojoxGridMasterHeader\" dojoAttachPoint=\"viewsHeaderNode\" wairole=\"presentation\"></div>\r\n\t<div class=\"dojoxGridMasterView\" dojoAttachPoint=\"viewsNode\" wairole=\"presentation\"></div>\r\n\t<div class=\"dojoxGridMasterMessages\" style=\"display: none;\" dojoAttachPoint=\"messagesNode\"></div>\r\n\t<span dojoAttachPoint=\"lastFocusNode\" tabindex=\"0\"></span>\r\n</div>\r\n",
  
  		// classTag: String
  		// 		CSS class applied to the grid's domNode
  		classTag: 'dojoxGrid',
  
  		// settings
  		// rowCount: Integer
  		//		Number of rows to display.
  		rowCount: 5,
  
  		// keepRows: Integer
  		//		Number of rows to keep in the rendering cache.
  		keepRows: 75,
  
  		// rowsPerPage: Integer
  		//		Number of rows to render at a time.
  		rowsPerPage: 25,
  
  		// autoWidth: Boolean
  		//		If autoWidth is true, grid width is automatically set to fit the data.
  		autoWidth: false,
  		
  		// initialWidth: String
  		//		A css string to use to set our initial width (only used if autoWidth
  		//		is true).  The first rendering of the grid will be this width, any
  		//		resizing of columns, etc will result in the grid switching to 
  		//		autoWidth mode.  Note, this width will override any styling in a
  		//		stylesheet or directly on the node.
  		initialWidth: "",
  
  		// autoHeight: Boolean|Integer
  		//		If autoHeight is true, grid height is automatically set to fit the data.
  		//		If it is an integer, the height will be automatically set to fit the data
  		//		if there are fewer than that many rows - and the height will be set to show
  		//		that many rows if there are more
  		autoHeight: '',
  
  		// rowHeight: Integer
  		//		If rowHeight is set to a positive number, it will define the height of the rows
  		//		in pixels. This can provide a significant performance advantage, since it
  		//		eliminates the need to measure row sizes during rendering, which is one
  		// 		the primary bottlenecks in the DataGrid's performance. 
  		rowHeight: 0,
  		
  		// autoRender: Boolean
  		//		If autoRender is true, grid will render itself after initialization.
  		autoRender: true,
  
  		// defaultHeight: String
  		//		default height of the grid, measured in any valid css unit.
  		defaultHeight: '15em',
  		
  		// height: String
  		//		explicit height of the grid, measured in any valid css unit.  This will be populated (and overridden)
  		//		if the height: css attribute exists on the source node.
  		height: '',
  
  		// structure: dojox.grid.__ViewDef|dojox.grid.__ViewDef[]|dojox.grid.__CellDef[]|Array[dojox.grid.__CellDef[]]
  		//		View layout defintion.
  		structure: null,
  
  		// elasticView: Integer
  		//	Override defaults and make the indexed grid view elastic, thus filling available horizontal space.
  		elasticView: -1,
  
  		// singleClickEdit: boolean
  		//		Single-click starts editing. Default is double-click
  		singleClickEdit: false,
  
  		// selectionMode: String
  		//		Set the selection mode of grid's Selection.  Value must be 'single', 'multiple',
  		//		or 'extended'.  Default is 'extended'.
  		selectionMode: 'extended',
  
  		// rowSelector: Boolean|String
  		// 		If set to true, will add a row selector view to this grid.  If set to a CSS width, will add
  		// 		a row selector of that width to this grid.
  		rowSelector: '',
  
  		// columnReordering: Boolean
  		// 		If set to true, will add drag and drop reordering to views with one row of columns.
  		columnReordering: false,
  
  		// headerMenu: dijit.Menu
  		// 		If set to a dijit.Menu, will use this as a context menu for the grid headers.
  		headerMenu: null,
  
  		// placeholderLabel: String
  		// 		Label of placeholders to search for in the header menu to replace with column toggling
  		// 		menu items.
  		placeholderLabel: "GridColumns",
  		
  		// selectable: Boolean
  		//		Set to true if you want to be able to select the text within the grid.
  		selectable: false,
  		
  		// Used to store the last two clicks, to ensure double-clicking occurs based on the intended row
  		_click: null,
  		
  		// loadingMessage: String
  		//  Message that shows while the grid is loading
  		loadingMessage: "<span class='dojoxGridLoading'>${loadingState}</span>",
  
  		// errorMessage: String
  		//  Message that shows when the grid encounters an error loading
  		errorMessage: "<span class='dojoxGridError'>${errorState}</span>",
  
  		// noDataMessage: String
  		//  Message that shows if the grid has no data - wrap it in a 
  		//  span with class 'dojoxGridNoData' if you want it to be
  		//  styled similar to the loading and error messages
  		noDataMessage: "",
  		
  		// escapeHTMLInData: Boolean
  		//		This will escape HTML brackets from the data to prevent HTML from 
  		// 		user-inputted data being rendered with may contain JavaScript and result in 
  		// 		XSS attacks. This is true by default, and it is recommended that it remain 
  		// 		true. Setting this to false will allow data to be displayed in the grid without 
  		// 		filtering, and should be only used if it is known that the data won't contain 
  		// 		malicious scripts. If HTML is needed in grid cells, it is recommended that 
  		// 		you use the formatter function to generate the HTML (the output of 
  		// 		formatter functions is not filtered, even with escapeHTMLInData set to true).
  		escapeHTMLInData: true,	
  		
  		// formatterScope: Object
  		//		An object to execute format functions within.  If not set, the
  		//		format functions will execute within the scope of the cell that
  		//		has a format function.
  		formatterScope: null,
  		
  		// editable: boolean
  		// indicates if the grid contains editable cells, default is false
  		// set to true if editable cell encountered during rendering 
  		editable: false,
  		
  		// private
  		sortInfo: 0,
  		themeable: true,
  		_placeholders: null,
  
  		// _layoutClass: Object
  		//	The class to use for our layout - can be overridden by grid subclasses
  		_layoutClass: dojox.grid._Layout,
  
  		// initialization
  		buildRendering: function(){
  			this.inherited(arguments);
  			if(!this.domNode.getAttribute('tabIndex')){
  				this.domNode.tabIndex = "0";
  			}
  			this.createScroller();
  			this.createLayout();
  			this.createViews();
  			this.createManagers();
  
  			this.createSelection();
  
  			this.connect(this.selection, "onSelected", "onSelected");
  			this.connect(this.selection, "onDeselected", "onDeselected");
  			this.connect(this.selection, "onChanged", "onSelectionChanged");
  
  			dojox.html.metrics.initOnFontResize();
  			this.connect(dojox.html.metrics, "onFontResize", "textSizeChanged");
  			dojox.grid.util.funnelEvents(this.domNode, this, 'doKeyEvent', dojox.grid.util.keyEvents);
  			if (this.selectionMode != "none") {
  				dojo.attr(this.domNode, "aria-multiselectable", this.selectionMode == "single" ? "false" : "true");
  			}
  
  			dojo.addClass(this.domNode, this.classTag);
  			if(!this.isLeftToRight()){
  				dojo.addClass(this.domNode, this.classTag+"Rtl");
  			}
  		},
  		
  		postMixInProperties: function(){
  			this.inherited(arguments);
  			var messages = dojo.i18n.getLocalization("dijit", "loading", this.lang);
  			this.loadingMessage = dojo.string.substitute(this.loadingMessage, messages);
  			this.errorMessage = dojo.string.substitute(this.errorMessage, messages);
  			if(this.srcNodeRef && this.srcNodeRef.style.height){
  				this.height = this.srcNodeRef.style.height;
  			}
  			// Call this to update our autoheight to start out
  			this._setAutoHeightAttr(this.autoHeight, true);
  			this.lastScrollTop = this.scrollTop = 0;
  		},
  		
  		postCreate: function(){
  			this._placeholders = [];
  			this._setHeaderMenuAttr(this.headerMenu);
  			this._setStructureAttr(this.structure);
  			this._click = [];
  			this.inherited(arguments);
  			if(this.domNode && this.autoWidth && this.initialWidth){
  				this.domNode.style.width = this.initialWidth;
  			}
  			if (this.domNode && !this.editable){
  				// default value for aria-readonly is false, set to true if grid is not editable
  				dojo.attr(this.domNode,"aria-readonly", "true");
  			}
  		},
  
  		destroy: function(){
  			this.domNode.onReveal = null;
  			this.domNode.onSizeChange = null;
  
  			// Fixes IE domNode leak
  			delete this._click;
  
  			this.edit.destroy();
  			delete this.edit;
  
  			this.views.destroyViews();
  			if(this.scroller){
  				this.scroller.destroy();
  				delete this.scroller;
  			}
  			if(this.focus){
  				this.focus.destroy();
  				delete this.focus;
  			}
  			if(this.headerMenu&&this._placeholders.length){
  				dojo.forEach(this._placeholders, function(p){ p.unReplace(true); });
  				this.headerMenu.unBindDomNode(this.viewsHeaderNode);
  			}
  			this.inherited(arguments);
  		},
  
  		_setAutoHeightAttr: function(ah, skipRender){
  			// Calculate our autoheight - turn it into a boolean or an integer
  			if(typeof ah == "string"){
  				if(!ah || ah == "false"){
  					ah = false;
  				}else if (ah == "true"){
  					ah = true;
  				}else{
  					ah = window.parseInt(ah, 10);
  				}
  			}
  			if(typeof ah == "number"){
  				if(isNaN(ah)){
  					ah = false;
  				}
  				// Autoheight must be at least 1, if it's a number.  If it's
  				// less than 0, we'll take that to mean "all" rows (same as 
  				// autoHeight=true - if it is equal to zero, we'll take that
  				// to mean autoHeight=false
  				if(ah < 0){
  					ah = true;
  				}else if (ah === 0){
  					ah = false;
  				}
  			}
  			this.autoHeight = ah;
  			if(typeof ah == "boolean"){
  				this._autoHeight = ah;
  			}else if(typeof ah == "number"){
  				this._autoHeight = (ah >= this.get('rowCount'));
  			}else{
  				this._autoHeight = false;
  			}
  			if(this._started && !skipRender){
  				this.render();
  			}
  		},
  
  		_getRowCountAttr: function(){
  			return this.updating && this.invalidated && this.invalidated.rowCount != undefined ?
  				this.invalidated.rowCount : this.rowCount;
  		},
  		
  		textSizeChanged: function(){
  			this.render();
  		},
  
  		sizeChange: function(){
  			this.update();
  		},
  
  		createManagers: function(){
  			// summary:
  			//		create grid managers for various tasks including rows, focus, selection, editing
  
  			// row manager
  			this.rows = new dojox.grid._RowManager(this);
  			// focus manager
  			this.focus = new dojox.grid._FocusManager(this);
  			// edit manager
  			this.edit = new dojox.grid._EditManager(this);
  		},
  
  		createSelection: function(){
  			// summary:	Creates a new Grid selection manager.
  
  			// selection manager
  			this.selection = new dojox.grid.Selection(this);
  		},
  
  		createScroller: function(){
  			// summary: Creates a new virtual scroller
  			this.scroller = new dojox.grid._Scroller();
  			this.scroller.grid = this;
  			this.scroller.renderRow = dojo.hitch(this, "renderRow");
  			this.scroller.removeRow = dojo.hitch(this, "rowRemoved");
  		},
  
  		createLayout: function(){
  			// summary: Creates a new Grid layout
  			this.layout = new this._layoutClass(this);
  			this.connect(this.layout, "moveColumn", "onMoveColumn");
  		},
  
  		onMoveColumn: function(){
  			this.render();
  		},
  		
  		onResizeColumn: function(/*int*/ cellIdx){
  			// Called when a column is resized.
  		},
  
  		// views
  		createViews: function(){
  			this.views = new dojox.grid._ViewManager(this);
  			this.views.createView = dojo.hitch(this, "createView");
  		},
  
  		createView: function(inClass, idx){
  			var c = dojo.getObject(inClass);
  			var view = new c({ grid: this, index: idx });
  			this.viewsNode.appendChild(view.domNode);
  			this.viewsHeaderNode.appendChild(view.headerNode);
  			this.views.addView(view);
  			return view;
  		},
  
  		buildViews: function(){
  			for(var i=0, vs; (vs=this.layout.structure[i]); i++){
  				this.createView(vs.type || dojox._scopeName + ".grid._View", i).setStructure(vs);
  			}
  			this.scroller.setContentNodes(this.views.getContentNodes());
  		},
  
  		_setStructureAttr: function(structure){
  			var s = structure;
  			if(s && dojo.isString(s)){
  				dojo.deprecated("dojox.grid._Grid.attr('structure', 'objVar')", "use dojox.grid._Grid.attr('structure', objVar) instead", "2.0");
  				s=dojo.getObject(s);
  			}
  			this.structure = s;
  			if(!s){
  				if(this.layout.structure){
  					s = this.layout.structure;
  				}else{
  					return;
  				}
  			}
  			this.views.destroyViews();
  			if(s !== this.layout.structure){
  				this.layout.setStructure(s);
  			}
  			this._structureChanged();
  		},
  
  		setStructure: function(/* dojox.grid.__ViewDef|dojox.grid.__ViewDef[]|dojox.grid.__CellDef[]|Array[dojox.grid.__CellDef[]] */ inStructure){
  			// summary:
  			//		Install a new structure and rebuild the grid.
  			dojo.deprecated("dojox.grid._Grid.setStructure(obj)", "use dojox.grid._Grid.attr('structure', obj) instead.", "2.0");
  			this._setStructureAttr(inStructure);
  		},
  		
  		getColumnTogglingItems: function(){
  			// Summary: returns an array of dijit.CheckedMenuItem widgets that can be
  			//		added to a menu for toggling columns on and off.
  			return dojo.map(this.layout.cells, function(cell){
  				if(!cell.menuItems){ cell.menuItems = []; }
  
  				var self = this;
  				var item = new dijit.CheckedMenuItem({
  					label: cell.name,
  					checked: !cell.hidden,
  					_gridCell: cell,
  					onChange: function(checked){
  						if(self.layout.setColumnVisibility(this._gridCell.index, checked)){
  							var items = this._gridCell.menuItems;
  							if(items.length > 1){
  								dojo.forEach(items, function(item){
  									if(item !== this){
  										item.setAttribute("checked", checked);
  									}
  								}, this);
  							}
  							checked = dojo.filter(self.layout.cells, function(c){
  								if(c.menuItems.length > 1){
  									dojo.forEach(c.menuItems, "item.attr('disabled', false);");
  								}else{
  									c.menuItems[0].attr('disabled', false);
  								}
  								return !c.hidden;
  							});
  							if(checked.length == 1){
  								dojo.forEach(checked[0].menuItems, "item.attr('disabled', true);");
  							}
  						}
  					},
  					destroy: function(){
  						var index = dojo.indexOf(this._gridCell.menuItems, this);
  						this._gridCell.menuItems.splice(index, 1);
  						delete this._gridCell;
  						dijit.CheckedMenuItem.prototype.destroy.apply(this, arguments);
  					}
  				});
  				cell.menuItems.push(item);
  				return item;
  			}, this); // dijit.CheckedMenuItem[]
  		},
  
  		_setHeaderMenuAttr: function(menu){
  			if(this._placeholders && this._placeholders.length){
  				dojo.forEach(this._placeholders, function(p){
  					p.unReplace(true);
  				});
  				this._placeholders = [];
  			}
  			if(this.headerMenu){
  				this.headerMenu.unBindDomNode(this.viewsHeaderNode);
  			}
  			this.headerMenu = menu;
  			if(!menu){ return; }
  
  			this.headerMenu.bindDomNode(this.viewsHeaderNode);
  			if(this.headerMenu.getPlaceholders){
  				this._placeholders = this.headerMenu.getPlaceholders(this.placeholderLabel);
  			}
  		},
  
  		setHeaderMenu: function(/* dijit.Menu */ menu){
  			dojo.deprecated("dojox.grid._Grid.setHeaderMenu(obj)", "use dojox.grid._Grid.attr('headerMenu', obj) instead.", "2.0");
  			this._setHeaderMenuAttr(menu);
  		},
  		
  		setupHeaderMenu: function(){
  			if(this._placeholders && this._placeholders.length){
  				dojo.forEach(this._placeholders, function(p){
  					if(p._replaced){
  						p.unReplace(true);
  					}
  					p.replace(this.getColumnTogglingItems());
  				}, this);
  			}
  		},
  
  		_fetch: function(start){
  			this.setScrollTop(0);
  		},
  
  		getItem: function(inRowIndex){
  			return null;
  		},
  		
  		showMessage: function(message){
  			if(message){
  				this.messagesNode.innerHTML = message;
  				this.messagesNode.style.display = "";
  			}else{
  				this.messagesNode.innerHTML = "";
  				this.messagesNode.style.display = "none";
  			}
  		},
  
  		_structureChanged: function() {
  			this.buildViews();
  			if(this.autoRender && this._started){
  				this.render();
  			}
  		},
  
  		hasLayout: function() {
  			return this.layout.cells.length;
  		},
  
  		// sizing
  		resize: function(changeSize, resultSize){
  			// summary:
  			//		Update the grid's rendering dimensions and resize it
  			
  			// Calling sizeChange calls update() which calls _resize...so let's
  			// save our input values, if any, and use them there when it gets 
  			// called.  This saves us an extra call to _resize(), which can
  			// get kind of heavy.
  			this._pendingChangeSize = changeSize;
  			this._pendingResultSize = resultSize;
  			this.sizeChange();
  		},
  
  		_getPadBorder: function() {
  			this._padBorder = this._padBorder || dojo._getPadBorderExtents(this.domNode);
  			return this._padBorder;
  		},
  
  		_getHeaderHeight: function(){
  			var vns = this.viewsHeaderNode.style, t = vns.display == "none" ? 0 : this.views.measureHeader();
  			vns.height = t + 'px';
  			// header heights are reset during measuring so must be normalized after measuring.
  			this.views.normalizeHeaderNodeHeight();
  			return t;
  		},
  		
  		_resize: function(changeSize, resultSize){
  			// Restore our pending values, if any
  			changeSize = changeSize || this._pendingChangeSize;
  			resultSize = resultSize || this._pendingResultSize;
  			delete this._pendingChangeSize;
  			delete this._pendingResultSize;
  			// if we have set up everything except the DOM, we cannot resize
  			if(!this.domNode){ return; }
  			var pn = this.domNode.parentNode;
  			if(!pn || pn.nodeType != 1 || !this.hasLayout() || pn.style.visibility == "hidden" || pn.style.display == "none"){
  				return;
  			}
  			// useful measurement
  			var padBorder = this._getPadBorder();
  			var hh = undefined;
  			var h;
  			// grid height
  			if(this._autoHeight){
  				this.domNode.style.height = 'auto';
  				this.viewsNode.style.height = '';
  			}else if(typeof this.autoHeight == "number"){
  				h = hh = this._getHeaderHeight();
  				h += (this.scroller.averageRowHeight * this.autoHeight);
  				this.domNode.style.height = h + "px";
  			}else if(this.domNode.clientHeight <= padBorder.h){
  				if(pn == document.body){
  					this.domNode.style.height = this.defaultHeight;
  				}else if(this.height){
  					this.domNode.style.height = this.height;
  				}else{
  					this.fitTo = "parent";
  				}
  			}
  			// if we are given dimensions, size the grid's domNode to those dimensions
  			if(resultSize){
  				changeSize = resultSize;
  			}
  			if(changeSize){
  				dojo.marginBox(this.domNode, changeSize);
  				this.height = this.domNode.style.height;
  				delete this.fitTo;
  			}else if(this.fitTo == "parent"){
  				h = this._parentContentBoxHeight = this._parentContentBoxHeight || dojo._getContentBox(pn).h;
  				this.domNode.style.height = Math.max(0, h) + "px";
  			}
  			
  			var hasFlex = dojo.some(this.views.views, function(v){ return v.flexCells; });
  
  			if(!this._autoHeight && (h || dojo._getContentBox(this.domNode).h) === 0){
  				// We need to hide the header, since the Grid is essentially hidden.
  				this.viewsHeaderNode.style.display = "none";
  			}else{
  				// Otherwise, show the header and give it an appropriate height.
  				this.viewsHeaderNode.style.display = "block";
  				if(!hasFlex && hh === undefined){
  					hh = this._getHeaderHeight();
  				}
  			}
  			if(hasFlex){
  				hh = undefined;
  			}
  
  			// NOTE: it is essential that width be applied before height
  			// Header height can only be calculated properly after view widths have been set.
  			// This is because flex column width is naturally 0 in Firefox.
  			// Therefore prior to width sizing flex columns with spaces are maximally wrapped
  			// and calculated to be too tall.
  			this.adaptWidth();
  			this.adaptHeight(hh);
  
  			this.postresize();
  		},
  
  		adaptWidth: function() {
  			// private: sets width and position for views and update grid width if necessary
  			var doAutoWidth = (!this.initialWidth && this.autoWidth);
  			var w = doAutoWidth ? 0 : this.domNode.clientWidth || (this.domNode.offsetWidth - this._getPadBorder().w),
  				vw = this.views.arrange(1, w);
  			this.views.onEach("adaptWidth");
  			if(doAutoWidth){
  				this.domNode.style.width = vw + "px";
  			}
  		},
  
  		adaptHeight: function(inHeaderHeight){
  			// private: measures and normalizes header height, then sets view heights, and then updates scroller
  			// content extent
  			var t = inHeaderHeight === undefined ? this._getHeaderHeight() : inHeaderHeight;
  			var h = (this._autoHeight ? -1 : Math.max(this.domNode.clientHeight - t, 0) || 0);
  			this.views.onEach('setSize', [0, h]);
  			this.views.onEach('adaptHeight');
  			if(!this._autoHeight){
  				var numScroll = 0, numNoScroll = 0;
  				var noScrolls = dojo.filter(this.views.views, function(v){
  					var has = v.hasHScrollbar();
  					if(has){ numScroll++; }else{ numNoScroll++; }
  					return (!has);
  				});
  				if(numScroll > 0 && numNoScroll > 0){
  					dojo.forEach(noScrolls, function(v){
  						v.adaptHeight(true);
  					});
  				}
  			}
  			if(this.autoHeight === true || h != -1 || (typeof this.autoHeight == "number" && this.autoHeight >= this.get('rowCount'))){
  				this.scroller.windowHeight = h;
  			}else{
  				this.scroller.windowHeight = Math.max(this.domNode.clientHeight - t, 0);
  			}
  		},
  
  		// startup
  		startup: function(){
  			if(this._started){return;}
  			this.inherited(arguments);
  			if(this.autoRender){
  				this.render();
  			}
  		},
  
  		// render
  		render: function(){
  			// summary:
  			//	Render the grid, headers, and views. Edit and scrolling states are reset. To retain edit and
  			// scrolling states, see Update.
  
  			if(!this.domNode){return;}
  			if(!this._started){return;}
  
  			if(!this.hasLayout()) {
  				this.scroller.init(0, this.keepRows, this.rowsPerPage);
  				return;
  			}
  			//
  			this.update = this.defaultUpdate;
  			this._render();
  		},
  
  		_render: function(){
  			this.scroller.init(this.get('rowCount'), this.keepRows, this.rowsPerPage);
  			this.prerender();
  			this.setScrollTop(0);
  			this.postrender();
  		},
  
  		prerender: function(){
  			// if autoHeight, make sure scroller knows not to virtualize; everything must be rendered.
  			this.keepRows = this._autoHeight ? 0 : this.keepRows;
  			this.scroller.setKeepInfo(this.keepRows);
  			this.views.render();
  			this._resize();
  		},
  
  		postrender: function(){
  			this.postresize();
  			this.focus.initFocusView();
  			// make rows unselectable
  			dojo.setSelectable(this.domNode, this.selectable);
  		},
  
  		postresize: function(){
  			// views are position absolute, so they do not inflate the parent
  			if(this._autoHeight){
  				var size = Math.max(this.views.measureContent()) + 'px';
  				
  				this.viewsNode.style.height = size;
  			}
  		},
  
  		renderRow: function(inRowIndex, inNodes){
  			// summary: private, used internally to render rows
  			this.views.renderRow(inRowIndex, inNodes, this._skipRowRenormalize);
  		},
  
  		rowRemoved: function(inRowIndex){
  			// summary: private, used internally to remove rows
  			this.views.rowRemoved(inRowIndex);
  		},
  
  		invalidated: null,
  
  		updating: false,
  
  		beginUpdate: function(){
  			// summary:
  			//		Use to make multiple changes to rows while queueing row updating.
  			// NOTE: not currently supporting nested begin/endUpdate calls
  			this.invalidated = [];
  			this.updating = true;
  		},
  
  		endUpdate: function(){
  			// summary:
  			//		Use after calling beginUpdate to render any changes made to rows.
  			this.updating = false;
  			var i = this.invalidated, r;
  			if(i.all){
  				this.update();
  			}else if(i.rowCount != undefined){
  				this.updateRowCount(i.rowCount);
  			}else{
  				for(r in i){
  					this.updateRow(Number(r));
  				}
  			}
  			this.invalidated = [];
  		},
  
  		// update
  		defaultUpdate: function(){
  			// note: initial update calls render and subsequently this function.
  			if(!this.domNode){return;}
  			if(this.updating){
  				this.invalidated.all = true;
  				return;
  			}
  			//this.edit.saveState(inRowIndex);
  			this.lastScrollTop = this.scrollTop;
  			this.prerender();
  			this.scroller.invalidateNodes();
  			this.setScrollTop(this.lastScrollTop);
  			this.postrender();
  			//this.edit.restoreState(inRowIndex);
  		},
  
  		update: function(){
  			// summary:
  			//		Update the grid, retaining edit and scrolling states.
  			this.render();
  		},
  
  		updateRow: function(inRowIndex){
  			// summary:
  			//		Render a single row.
  			// inRowIndex: Integer
  			//		Index of the row to render
  			inRowIndex = Number(inRowIndex);
  			if(this.updating){
  				this.invalidated[inRowIndex]=true;
  			}else{
  				this.views.updateRow(inRowIndex);
  				this.scroller.rowHeightChanged(inRowIndex);
  			}
  		},
  
  		updateRows: function(startIndex, howMany){
  			// summary:
  			//		Render consecutive rows at once.
  			// startIndex: Integer
  			//		Index of the starting row to render
  			// howMany: Integer
  			//		How many rows to update.
  			startIndex = Number(startIndex);
  			howMany = Number(howMany);
  			var i;
  			if(this.updating){
  				for(i=0; i<howMany; i++){
  					this.invalidated[i+startIndex]=true;
  				}
  			}else{
  				for(i=0; i<howMany; i++){
  					this.views.updateRow(i+startIndex, this._skipRowRenormalize);
  				}
  				this.scroller.rowHeightChanged(startIndex);
  			}
  		},
  
  		updateRowCount: function(inRowCount){
  			//summary:
  			//	Change the number of rows.
  			// inRowCount: int
  			//	Number of rows in the grid.
  			if(this.updating){
  				this.invalidated.rowCount = inRowCount;
  			}else{
  				this.rowCount = inRowCount;
  				this._setAutoHeightAttr(this.autoHeight, true);
  				if(this.layout.cells.length){
  					this.scroller.updateRowCount(inRowCount);
  				}
  				this._resize();				
  				if(this.layout.cells.length){
  					this.setScrollTop(this.scrollTop);
  				}
  			}
  		},
  
  		updateRowStyles: function(inRowIndex){
  			// summary:
  			//		Update the styles for a row after it's state has changed.
  			this.views.updateRowStyles(inRowIndex);
  		},
  		getRowNode: function(inRowIndex){
  			// summary:
  			//		find the rowNode that is not a rowSelector
  			if (this.focus.focusView && !(this.focus.focusView instanceof dojox.grid._RowSelector)){
  					return this.focus.focusView.rowNodes[inRowIndex];
  			}else{ // search through views
  				for (var i = 0, cView; (cView = this.views.views[i]); i++) {
  					if (!(cView instanceof dojox.grid._RowSelector)) {
  						return cView.rowNodes[inRowIndex];
  					}
  				}
  			}
  			return null;
  		},
  		rowHeightChanged: function(inRowIndex){
  			// summary:
  			//		Update grid when the height of a row has changed. Row height is handled automatically as rows
  			//		are rendered. Use this function only to update a row's height outside the normal rendering process.
  			// inRowIndex: Integer
  			// 		index of the row that has changed height
  
  			this.views.renormalizeRow(inRowIndex);
  			this.scroller.rowHeightChanged(inRowIndex);
  		},
  
  		// fastScroll: Boolean
  		//		flag modifies vertical scrolling behavior. Defaults to true but set to false for slower
  		//		scroll performance but more immediate scrolling feedback
  		fastScroll: true,
  
  		delayScroll: false,
  
  		// scrollRedrawThreshold: int
  		//	pixel distance a user must scroll vertically to trigger grid scrolling.
  		scrollRedrawThreshold: (dojo.isIE ? 100 : 50),
  
  		// scroll methods
  		scrollTo: function(inTop){
  			// summary:
  			//		Vertically scroll the grid to a given pixel position
  			// inTop: Integer
  			//		vertical position of the grid in pixels
  			if(!this.fastScroll){
  				this.setScrollTop(inTop);
  				return;
  			}
  			var delta = Math.abs(this.lastScrollTop - inTop);
  			this.lastScrollTop = inTop;
  			if(delta > this.scrollRedrawThreshold || this.delayScroll){
  				this.delayScroll = true;
  				this.scrollTop = inTop;
  				this.views.setScrollTop(inTop);
  				if(this._pendingScroll){
  					window.clearTimeout(this._pendingScroll);
  				}
  				var _this = this;
  				this._pendingScroll = window.setTimeout(function(){
  					delete _this._pendingScroll;
  					_this.finishScrollJob();
  				}, 200);
  			}else{
  				this.setScrollTop(inTop);
  			}
  		},
  
  		finishScrollJob: function(){
  			this.delayScroll = false;
  			this.setScrollTop(this.scrollTop);
  		},
  
  		setScrollTop: function(inTop){
  			this.scroller.scroll(this.views.setScrollTop(inTop));
  		},
  
  		scrollToRow: function(inRowIndex){
  			// summary:
  			//		Scroll the grid to a specific row.
  			// inRowIndex: Integer
  			// 		grid row index
  			this.setScrollTop(this.scroller.findScrollTop(inRowIndex) + 1);
  		},
  
  		// styling (private, used internally to style individual parts of a row)
  		styleRowNode: function(inRowIndex, inRowNode){
  			if(inRowNode){
  				this.rows.styleRowNode(inRowIndex, inRowNode);
  			}
  		},
  		
  		// called when the mouse leaves the grid so we can deselect all hover rows
  		_mouseOut: function(e){
  			this.rows.setOverRow(-2);
  		},
  	
  		// cells
  		getCell: function(inIndex){
  			// summary:
  			//		Retrieves the cell object for a given grid column.
  			// inIndex: Integer
  			// 		Grid column index of cell to retrieve
  			// returns:
  			//		a grid cell
  			return this.layout.cells[inIndex];
  		},
  
  		setCellWidth: function(inIndex, inUnitWidth){
  			this.getCell(inIndex).unitWidth = inUnitWidth;
  		},
  
  		getCellName: function(inCell){
  			// summary: Returns the cell name of a passed cell
  			return "Cell " + inCell.index; // String
  		},
  
  		// sorting
  		canSort: function(inSortInfo){
  			// summary:
  			//		Determines if the grid can be sorted
  			// inSortInfo: Integer
  			//		Sort information, 1-based index of column on which to sort, positive for an ascending sort
  			// 		and negative for a descending sort
  			// returns: Boolean
  			//		True if grid can be sorted on the given column in the given direction
  		},
  
  		sort: function(){
  		},
  
  		getSortAsc: function(inSortInfo){
  			// summary:
  			//		Returns true if grid is sorted in an ascending direction.
  			inSortInfo = inSortInfo == undefined ? this.sortInfo : inSortInfo;
  			return Boolean(inSortInfo > 0); // Boolean
  		},
  
  		getSortIndex: function(inSortInfo){
  			// summary:
  			//		Returns the index of the column on which the grid is sorted
  			inSortInfo = inSortInfo == undefined ? this.sortInfo : inSortInfo;
  			return Math.abs(inSortInfo) - 1; // Integer
  		},
  
  		setSortIndex: function(inIndex, inAsc){
  			// summary:
  			// 		Sort the grid on a column in a specified direction
  			// inIndex: Integer
  			// 		Column index on which to sort.
  			// inAsc: Boolean
  			// 		If true, sort the grid in ascending order, otherwise in descending order
  			var si = inIndex +1;
  			if(inAsc != undefined){
  				si *= (inAsc ? 1 : -1);
  			} else if(this.getSortIndex() == inIndex){
  				si = -this.sortInfo;
  			}
  			this.setSortInfo(si);
  		},
  
  		setSortInfo: function(inSortInfo){
  			if(this.canSort(inSortInfo)){
  				this.sortInfo = inSortInfo;
  				this.sort();
  				this.update();
  			}
  		},
  
  		// DOM event handler
  		doKeyEvent: function(e){
  			e.dispatch = 'do' + e.type;
  			this.onKeyEvent(e);
  		},
  
  		// event dispatch
  		//: protected
  		_dispatch: function(m, e){
  			if(m in this){
  				return this[m](e);
  			}
  			return false;
  		},
  
  		dispatchKeyEvent: function(e){
  			this._dispatch(e.dispatch, e);
  		},
  
  		dispatchContentEvent: function(e){
  			this.edit.dispatchEvent(e) || e.sourceView.dispatchContentEvent(e) || this._dispatch(e.dispatch, e);
  		},
  
  		dispatchHeaderEvent: function(e){
  			e.sourceView.dispatchHeaderEvent(e) || this._dispatch('doheader' + e.type, e);
  		},
  
  		dokeydown: function(e){
  			this.onKeyDown(e);
  		},
  
  		doclick: function(e){
  			if(e.cellNode){
  				this.onCellClick(e);
  			}else{
  				this.onRowClick(e);
  			}
  		},
  
  		dodblclick: function(e){
  			if(e.cellNode){
  				this.onCellDblClick(e);
  			}else{
  				this.onRowDblClick(e);
  			}
  		},
  
  		docontextmenu: function(e){
  			if(e.cellNode){
  				this.onCellContextMenu(e);
  			}else{
  				this.onRowContextMenu(e);
  			}
  		},
  
  		doheaderclick: function(e){
  			if(e.cellNode){
  				this.onHeaderCellClick(e);
  			}else{
  				this.onHeaderClick(e);
  			}
  		},
  
  		doheaderdblclick: function(e){
  			if(e.cellNode){
  				this.onHeaderCellDblClick(e);
  			}else{
  				this.onHeaderDblClick(e);
  			}
  		},
  
  		doheadercontextmenu: function(e){
  			if(e.cellNode){
  				this.onHeaderCellContextMenu(e);
  			}else{
  				this.onHeaderContextMenu(e);
  			}
  		},
  
  		// override to modify editing process
  		doStartEdit: function(inCell, inRowIndex){
  			this.onStartEdit(inCell, inRowIndex);
  		},
  
  		doApplyCellEdit: function(inValue, inRowIndex, inFieldIndex){
  			this.onApplyCellEdit(inValue, inRowIndex, inFieldIndex);
  		},
  
  		doCancelEdit: function(inRowIndex){
  			this.onCancelEdit(inRowIndex);
  		},
  
  		doApplyEdit: function(inRowIndex){
  			this.onApplyEdit(inRowIndex);
  		},
  
  		// row editing
  		addRow: function(){
  			// summary:
  			//		Add a row to the grid.
  			this.updateRowCount(this.get('rowCount')+1);
  		},
  
  		removeSelectedRows: function(){
  			// summary:
  			//		Remove the selected rows from the grid.
  			if(this.allItemsSelected){
  				this.updateRowCount(0);
  			}else{
  				this.updateRowCount(Math.max(0, this.get('rowCount') - this.selection.getSelected().length));
  			}
  			this.selection.clear();
  		}
  
  	});
  
  	dojox.grid._Grid.markupFactory = function(props, node, ctor, cellFunc){
  		var d = dojo;
  		var widthFromAttr = function(n){
  			var w = d.attr(n, "width")||"auto";
  			if((w != "auto")&&(w.slice(-2) != "em")&&(w.slice(-1) != "%")){
  				w = parseInt(w, 10)+"px";
  			}
  			return w;
  		};
  		// if(!props.store){ console.debug("no store!"); }
  		// if a structure isn't referenced, do we have enough
  		// data to try to build one automatically?
  		if(	!props.structure &&
  			node.nodeName.toLowerCase() == "table"){
  
  			// try to discover a structure
  			props.structure = d.query("> colgroup", node).map(function(cg){
  				var sv = d.attr(cg, "span");
  				var v = {
  					noscroll: (d.attr(cg, "noscroll") == "true") ? true : false,
  					__span: (!!sv ? parseInt(sv, 10) : 1),
  					cells: []
  				};
  				if(d.hasAttr(cg, "width")){
  					v.width = widthFromAttr(cg);
  				}
  				return v; // for vendetta
  			});
  			if(!props.structure.length){
  				props.structure.push({
  					__span: Infinity,
  					cells: [] // catch-all view
  				});
  			}
  			// check to see if we're gonna have more than one view
  
  			// for each tr in our th, create a row of cells
  			d.query("thead > tr", node).forEach(function(tr, tr_idx){
  				var cellCount = 0;
  				var viewIdx = 0;
  				var lastViewIdx;
  				var cView = null;
  				d.query("> th", tr).map(function(th){
  					// what view will this cell go into?
  
  					// NOTE:
  					//		to prevent extraneous iteration, we start counters over
  					//		for each row, incrementing over the surface area of the
  					//		structure that colgroup processing generates and
  					//		creating cell objects for each <th> to place into those
  					//		cell groups.  There's a lot of state-keepking logic
  					//		here, but it is what it has to be.
  					if(!cView){ // current view book keeping
  						lastViewIdx = 0;
  						cView = props.structure[0];
  					}else if(cellCount >= (lastViewIdx+cView.__span)){
  						viewIdx++;
  						// move to allocating things into the next view
  						lastViewIdx += cView.__span;
  						var lastView = cView;
  						cView = props.structure[viewIdx];
  					}
  
  					// actually define the cell from what markup hands us
  					var cell = {
  						name: d.trim(d.attr(th, "name")||th.innerHTML),
  						colSpan: parseInt(d.attr(th, "colspan")||1, 10),
  						type: d.trim(d.attr(th, "cellType")||""),
  						id: d.trim(d.attr(th,"id")||"")
  					};
  					cellCount += cell.colSpan;
  					var rowSpan = d.attr(th, "rowspan");
  					if(rowSpan){
  						cell.rowSpan = rowSpan;
  					}
  					if(d.hasAttr(th, "width")){
  						cell.width = widthFromAttr(th);
  					}
  					if(d.hasAttr(th, "relWidth")){
  						cell.relWidth = window.parseInt(dojo.attr(th, "relWidth"), 10);
  					}
  					if(d.hasAttr(th, "hidden")){
  						cell.hidden = d.attr(th, "hidden") == "true";
  					}
  
  					if(cellFunc){
  						cellFunc(th, cell);
  					}
  
  					cell.type = cell.type ? dojo.getObject(cell.type) : dojox.grid.cells.Cell;
  
  					if(cell.type && cell.type.markupFactory){
  						cell.type.markupFactory(th, cell);
  					}
  
  					if(!cView.cells[tr_idx]){
  						cView.cells[tr_idx] = [];
  					}
  					cView.cells[tr_idx].push(cell);
  				});
  			});
  		}
  
  		return new ctor(props, node);
  	};
  })();
  
  }
  