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");
(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 }]
// | ]}
// | ];
//
// |
// | rowCount="100" get="get"
// | structure="structure"
// | dojoType="dojox.grid._Grid">
templatePath: dojo.moduleUrl("dojox.grid","resources/_Grid.html"),
// 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: "
${loadingState}",
// errorMessage: String
// Message that shows when the grid encounters an error loading
errorMessage: "
${errorState}",
// 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);
dojo.attr(this.domNode, "align", dojo._isBodyLtr() ? 'left' : 'right');
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.set('structure', 'objVar')", "use dojox.grid._Grid.set('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();
this.focus.focusView = null;
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.set('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.set('disabled', false);");
}else{
c.menuItems[0].set('disabled', false);
}
return !c.hidden;
});
if(checked.length == 1){
dojo.forEach(checked[0].menuItems, "item.set('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.set('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';
}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);