diff options
Diffstat (limited to 'js/dojo/dojox/grid/_Grid.js')
| -rw-r--r-- | js/dojo/dojox/grid/_Grid.js | 1402 |
1 files changed, 1402 insertions, 0 deletions
diff --git a/js/dojo/dojox/grid/_Grid.js b/js/dojo/dojox/grid/_Grid.js new file mode 100644 index 0000000..8c677b5 --- /dev/null +++ b/js/dojo/dojox/grid/_Grid.js @@ -0,0 +1,1402 @@ +//>>built +require({cache:{ +'url:dojox/grid/resources/_Grid.html':"<div hidefocus=\"hidefocus\" role=\"grid\" dojoAttachEvent=\"onmouseout:_mouseOut\">\n\t<div class=\"dojoxGridMasterHeader\" dojoAttachPoint=\"viewsHeaderNode\" role=\"presentation\"></div>\n\t<div class=\"dojoxGridMasterView\" dojoAttachPoint=\"viewsNode\" role=\"presentation\"></div>\n\t<div class=\"dojoxGridMasterMessages\" style=\"display: none;\" dojoAttachPoint=\"messagesNode\"></div>\n\t<span dojoAttachPoint=\"lastFocusNode\" tabindex=\"0\"></span>\n</div>\n"}}); +define("dojox/grid/_Grid", [ + "dojo/_base/kernel", + "../main", + "dojo/_base/declare", + "./_Events", + "./_Scroller", + "./_Layout", + "./_View", + "./_ViewManager", + "./_RowManager", + "./_FocusManager", + "./_EditManager", + "./Selection", + "./_RowSelector", + "./util", + "dijit/_Widget", + "dijit/_TemplatedMixin", + "dijit/CheckedMenuItem", + "dojo/text!./resources/_Grid.html", + "dojo/string", + "dojo/_base/array", + "dojo/_base/lang", + "dojo/_base/sniff", + "dojox/html/metrics", + "dojo/_base/html", + "dojo/query", + "dojo/dnd/common", + "dojo/i18n!dijit/nls/loading" +], function(dojo, dojox, declare, _Events, _Scroller, _Layout, _View, _ViewManager, + _RowManager, _FocusManager, _EditManager, Selection, _RowSelector, util, _Widget, + _TemplatedMixin, CheckedMenuItem, template, string, array, lang, has, metrics, html, query){ + + // 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; + } + =====*/ + + var _Grid = declare('dojox.grid._Grid', + [ _Widget, _TemplatedMixin, _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: template, + + // 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: _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"); + + metrics.initOnFontResize(); + this.connect(metrics, "onFontResize", "textSizeChanged"); + util.funnelEvents(this.domNode, this, 'doKeyEvent', util.keyEvents); + if (this.selectionMode != "none") { + this.domNode.setAttribute("aria-multiselectable", this.selectionMode == "single" ? "false" : "true"); + } + + html.addClass(this.domNode, this.classTag); + if(!this.isLeftToRight()){ + html.addClass(this.domNode, this.classTag+"Rtl"); + } + }, + + postMixInProperties: function(){ + this.inherited(arguments); + var messages = dojo.i18n.getLocalization("dijit", "loading", this.lang); + this.loadingMessage = string.substitute(this.loadingMessage, messages); + this.errorMessage = 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 + html.attr(this.domNode,"aria-readonly", "true"); + } + }, + + destroy: function(){ + this.domNode.onReveal = null; + this.domNode.onSizeChange = null; + + // Fixes IE domNode leak + delete this._click; + + if(this.scroller){ + this.scroller.destroy(); + delete this.scroller; + } + this.edit.destroy(); + delete this.edit; + this.views.destroyViews(); + if(this.focus){ + this.focus.destroy(); + delete this.focus; + } + if(this.headerMenu&&this._placeholders.length){ + array.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 _RowManager(this); + // focus manager + this.focus = new _FocusManager(this); + // edit manager + this.edit = new _EditManager(this); + }, + + createSelection: function(){ + // summary: Creates a new Grid selection manager. + + // selection manager + this.selection = new Selection(this); + }, + + createScroller: function(){ + // summary: Creates a new virtual scroller + this.scroller = new _Scroller(); + this.scroller.grid = this; + this.scroller.renderRow = lang.hitch(this, "renderRow"); + this.scroller.removeRow = lang.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 _ViewManager(this); + this.views.createView = lang.hitch(this, "createView"); + }, + + createView: function(inClass, idx){ + var c = lang.getObject(inClass); + var view = new c({ grid: this, index: idx }); + this.viewsNode.appendChild(view.domNode); + this.viewsHeaderNode.appendChild(view.headerNode); + this.views.addView(view); + html.attr(this.domNode, "align", this.isLeftToRight() ? '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 && lang.isString(s)){ + dojo.deprecated("dojox.grid._Grid.set('structure', 'objVar')", "use dojox.grid._Grid.set('structure', objVar) instead", "2.0"); + s=lang.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. + var items, checkedItems = []; + items = array.map(this.layout.cells, function(cell){ + if(!cell.menuItems){ cell.menuItems = []; } + + var self = this; + var item = new 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){ + array.forEach(items, function(item){ + if(item !== this){ + item.setAttribute("checked", checked); + } + }, this); + } + checked = array.filter(self.layout.cells, function(c){ + if(c.menuItems.length > 1){ + array.forEach(c.menuItems, "item.set('disabled', false);"); + }else{ + c.menuItems[0].set('disabled', false); + } + return !c.hidden; + }); + if(checked.length == 1){ + array.forEach(checked[0].menuItems, "item.set('disabled', true);"); + } + } + }, + destroy: function(){ + var index = array.indexOf(this._gridCell.menuItems, this); + this._gridCell.menuItems.splice(index, 1); + delete this._gridCell; + CheckedMenuItem.prototype.destroy.apply(this, arguments); + } + }); + cell.menuItems.push(item); + if(!cell.hidden) { + checkedItems.push(item); + } + return item; + }, this); // dijit.CheckedMenuItem[] + if(checkedItems.length == 1) { + checkedItems[0].set('disabled', true); + } + return items; + }, + + _setHeaderMenuAttr: function(menu){ + if(this._placeholders && this._placeholders.length){ + array.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){ + array.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. + + // fixes #11101, should ignore resize when in autoheight mode(IE) to avoid a deadlock + // e.g when an autoheight editable grid put in dijit.form.Form or other similar containers, + // grid switch to editing mode --> grid height change --> From height change + // ---> Form call grid.resize() ---> grid height change --> deaklock + if(dojo.isIE && !changeSize && !resultSize && this._autoHeight){ + return; + } + this._pendingChangeSize = changeSize; + this._pendingResultSize = resultSize; + this.sizeChange(); + }, + + _getPadBorder: function() { + this._padBorder = this._padBorder || html._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(!this._autoHeight && changeSize){ + html.marginBox(this.domNode, changeSize); + this.height = this.domNode.style.height; + delete this.fitTo; + }else if(this.fitTo == "parent"){ + h = this._parentContentBoxHeight = this._parentContentBoxHeight || html._getContentBox(pn).h; + this.domNode.style.height = Math.max(0, h) + "px"; + } + + var hasFlex = array.some(this.views.views, function(v){ return v.flexCells; }); + + if(!this._autoHeight && (h || html._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 = array.filter(this.views.views, function(v){ + var has = v.hasHScrollbar(); + if(has){ numScroll++; }else{ numNoScroll++; } + return (!has); + }); + if(numScroll > 0 && numNoScroll > 0){ + array.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 + html.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 _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 _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: (has("ie") ? 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(); + } + + }); + + _Grid.markupFactory = function(props, node, ctor, cellFunc){ + var widthFromAttr = function(n){ + var w = html.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 = query("> colgroup", node).map(function(cg){ + var sv = html.attr(cg, "span"); + var v = { + noscroll: (html.attr(cg, "noscroll") == "true") ? true : false, + __span: (!!sv ? parseInt(sv, 10) : 1), + cells: [] + }; + if(html.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 + query("thead > tr", node).forEach(function(tr, tr_idx){ + var cellCount = 0; + var viewIdx = 0; + var lastViewIdx; + var cView = null; + 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: lang.trim(html.attr(th, "name")||th.innerHTML), + colSpan: parseInt(html.attr(th, "colspan")||1, 10), + type: lang.trim(html.attr(th, "cellType")||""), + id: lang.trim(html.attr(th,"id")||"") + }; + cellCount += cell.colSpan; + var rowSpan = html.attr(th, "rowspan"); + if(rowSpan){ + cell.rowSpan = rowSpan; + } + if(html.hasAttr(th, "width")){ + cell.width = widthFromAttr(th); + } + if(html.hasAttr(th, "relWidth")){ + cell.relWidth = window.parseInt(html.attr(th, "relWidth"), 10); + } + if(html.hasAttr(th, "hidden")){ + cell.hidden = (html.attr(th, "hidden") == "true" || html.attr(th, "hidden") === true/*always boolean true in Chrome*/); + } + + if(cellFunc){ + cellFunc(th, cell); + } + + cell.type = cell.type ? lang.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); + }; + + return _Grid; + +});
\ No newline at end of file |
