_Grid.js 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394
  1. require({cache:{
  2. '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"}});
  3. define("dojox/grid/_Grid", [
  4. "dojo/_base/kernel",
  5. "../main",
  6. "dojo/_base/declare",
  7. "./_Events",
  8. "./_Scroller",
  9. "./_Layout",
  10. "./_View",
  11. "./_ViewManager",
  12. "./_RowManager",
  13. "./_FocusManager",
  14. "./_EditManager",
  15. "./Selection",
  16. "./_RowSelector",
  17. "./util",
  18. "dijit/_Widget",
  19. "dijit/_TemplatedMixin",
  20. "dijit/CheckedMenuItem",
  21. "dojo/text!./resources/_Grid.html",
  22. "dojo/string",
  23. "dojo/_base/array",
  24. "dojo/_base/lang",
  25. "dojo/_base/sniff",
  26. "dojox/html/metrics",
  27. "dojo/_base/html",
  28. "dojo/query",
  29. "dojo/dnd/common",
  30. "dojo/i18n!dijit/nls/loading"
  31. ], function(dojo, dojox, declare, _Events, _Scroller, _Layout, _View, _ViewManager,
  32. _RowManager, _FocusManager, _EditManager, Selection, _RowSelector, util, _Widget,
  33. _TemplatedMixin, CheckedMenuItem, template, string, array, lang, has, metrics, html, query){
  34. // NOTE: this is for backwards compatibility with Dojo 1.3
  35. if(!dojo.isCopyKey){
  36. dojo.isCopyKey = dojo.dnd.getCopyKeyState;
  37. }
  38. /*=====
  39. dojox.grid.__CellDef = function(){
  40. // name: String?
  41. // The text to use in the header of the grid for this cell.
  42. // get: Function?
  43. // function(rowIndex){} rowIndex is of type Integer. This
  44. // function will be called when a cell requests data. Returns the
  45. // unformatted data for the cell.
  46. // value: String?
  47. // If "get" is not specified, this is used as the data for the cell.
  48. // defaultValue: String?
  49. // If "get" and "value" aren't specified or if "get" returns an undefined
  50. // value, this is used as the data for the cell. "formatter" is not run
  51. // on this if "get" returns an undefined value.
  52. // formatter: Function?
  53. // function(data, rowIndex){} data is of type anything, rowIndex
  54. // is of type Integer. This function will be called after the cell
  55. // has its data but before it passes it back to the grid to render.
  56. // Returns the formatted version of the cell's data.
  57. // type: dojox.grid.cells._Base|Function?
  58. // TODO
  59. // editable: Boolean?
  60. // Whether this cell should be editable or not.
  61. // hidden: Boolean?
  62. // If true, the cell will not be displayed.
  63. // noresize: Boolean?
  64. // If true, the cell will not be able to be resized.
  65. // width: Integer|String?
  66. // A CSS size. If it's an Integer, the width will be in em's.
  67. // colSpan: Integer?
  68. // How many columns to span this cell. Will not work in the first
  69. // sub-row of cells.
  70. // rowSpan: Integer?
  71. // How many sub-rows to span this cell.
  72. // styles: String?
  73. // A string of styles to apply to both the header cell and main
  74. // grid cells. Must end in a ';'.
  75. // headerStyles: String?
  76. // A string of styles to apply to just the header cell. Must end
  77. // in a ';'
  78. // cellStyles: String?
  79. // A string of styles to apply to just the main grid cells. Must
  80. // end in a ';'
  81. // classes: String?
  82. // A space separated list of classes to apply to both the header
  83. // cell and the main grid cells.
  84. // headerClasses: String?
  85. // A space separated list of classes to apply to just the header
  86. // cell.
  87. // cellClasses: String?
  88. // A space separated list of classes to apply to just the main
  89. // grid cells.
  90. // attrs: String?
  91. // A space separated string of attribute='value' pairs to add to
  92. // the header cell element and main grid cell elements.
  93. this.name = name;
  94. this.value = value;
  95. this.get = get;
  96. this.formatter = formatter;
  97. this.type = type;
  98. this.editable = editable;
  99. this.hidden = hidden;
  100. this.width = width;
  101. this.colSpan = colSpan;
  102. this.rowSpan = rowSpan;
  103. this.styles = styles;
  104. this.headerStyles = headerStyles;
  105. this.cellStyles = cellStyles;
  106. this.classes = classes;
  107. this.headerClasses = headerClasses;
  108. this.cellClasses = cellClasses;
  109. this.attrs = attrs;
  110. }
  111. =====*/
  112. /*=====
  113. dojox.grid.__ViewDef = function(){
  114. // noscroll: Boolean?
  115. // If true, no scrollbars will be rendered without scrollbars.
  116. // width: Integer|String?
  117. // A CSS size. If it's an Integer, the width will be in em's. If
  118. // "noscroll" is true, this value is ignored.
  119. // cells: dojox.grid.__CellDef[]|Array[dojox.grid.__CellDef[]]?
  120. // The structure of the cells within this grid.
  121. // type: String?
  122. // A string containing the constructor of a subclass of
  123. // dojox.grid._View. If this is not specified, dojox.grid._View
  124. // is used.
  125. // defaultCell: dojox.grid.__CellDef?
  126. // A cell definition with default values for all cells in this view. If
  127. // a property is defined in a cell definition in the "cells" array and
  128. // this property, the cell definition's property will override this
  129. // property's property.
  130. // onBeforeRow: Function?
  131. // function(rowIndex, cells){} rowIndex is of type Integer, cells
  132. // is of type Array[dojox.grid.__CellDef[]]. This function is called
  133. // before each row of data is rendered. Before the header is
  134. // rendered, rowIndex will be -1. "cells" is a reference to the
  135. // internal structure of this view's cells so any changes you make to
  136. // it will persist between calls.
  137. // onAfterRow: Function?
  138. // function(rowIndex, cells, rowNode){} rowIndex is of type Integer, cells
  139. // is of type Array[dojox.grid.__CellDef[]], rowNode is of type DOMNode.
  140. // This function is called after each row of data is rendered. After the
  141. // header is rendered, rowIndex will be -1. "cells" is a reference to the
  142. // internal structure of this view's cells so any changes you make to
  143. // it will persist between calls.
  144. this.noscroll = noscroll;
  145. this.width = width;
  146. this.cells = cells;
  147. this.type = type;
  148. this.defaultCell = defaultCell;
  149. this.onBeforeRow = onBeforeRow;
  150. this.onAfterRow = onAfterRow;
  151. }
  152. =====*/
  153. var _Grid = declare('dojox.grid._Grid',
  154. [ _Widget, _TemplatedMixin, _Events ],
  155. {
  156. // summary:
  157. // A grid widget with virtual scrolling, cell editing, complex rows,
  158. // sorting, fixed columns, sizeable columns, etc.
  159. //
  160. // description:
  161. // _Grid provides the full set of grid features without any
  162. // direct connection to a data store.
  163. //
  164. // The grid exposes a get function for the grid, or optionally
  165. // individual columns, to populate cell contents.
  166. //
  167. // The grid is rendered based on its structure, an object describing
  168. // column and cell layout.
  169. //
  170. // example:
  171. // A quick sample:
  172. //
  173. // define a get function
  174. // | function get(inRowIndex){ // called in cell context
  175. // | return [this.index, inRowIndex].join(', ');
  176. // | }
  177. //
  178. // define the grid structure:
  179. // | var structure = [ // array of view objects
  180. // | { cells: [// array of rows, a row is an array of cells
  181. // | [
  182. // | { name: "Alpha", width: 6 },
  183. // | { name: "Beta" },
  184. // | { name: "Gamma", get: get }]
  185. // | ]}
  186. // | ];
  187. //
  188. // | <div id="grid"
  189. // | rowCount="100" get="get"
  190. // | structure="structure"
  191. // | dojoType="dojox.grid._Grid"></div>
  192. templateString: template,
  193. // classTag: String
  194. // CSS class applied to the grid's domNode
  195. classTag: 'dojoxGrid',
  196. // settings
  197. // rowCount: Integer
  198. // Number of rows to display.
  199. rowCount: 5,
  200. // keepRows: Integer
  201. // Number of rows to keep in the rendering cache.
  202. keepRows: 75,
  203. // rowsPerPage: Integer
  204. // Number of rows to render at a time.
  205. rowsPerPage: 25,
  206. // autoWidth: Boolean
  207. // If autoWidth is true, grid width is automatically set to fit the data.
  208. autoWidth: false,
  209. // initialWidth: String
  210. // A css string to use to set our initial width (only used if autoWidth
  211. // is true). The first rendering of the grid will be this width, any
  212. // resizing of columns, etc will result in the grid switching to
  213. // autoWidth mode. Note, this width will override any styling in a
  214. // stylesheet or directly on the node.
  215. initialWidth: "",
  216. // autoHeight: Boolean|Integer
  217. // If autoHeight is true, grid height is automatically set to fit the data.
  218. // If it is an integer, the height will be automatically set to fit the data
  219. // if there are fewer than that many rows - and the height will be set to show
  220. // that many rows if there are more
  221. autoHeight: '',
  222. // rowHeight: Integer
  223. // If rowHeight is set to a positive number, it will define the height of the rows
  224. // in pixels. This can provide a significant performance advantage, since it
  225. // eliminates the need to measure row sizes during rendering, which is one
  226. // the primary bottlenecks in the DataGrid's performance.
  227. rowHeight: 0,
  228. // autoRender: Boolean
  229. // If autoRender is true, grid will render itself after initialization.
  230. autoRender: true,
  231. // defaultHeight: String
  232. // default height of the grid, measured in any valid css unit.
  233. defaultHeight: '15em',
  234. // height: String
  235. // explicit height of the grid, measured in any valid css unit. This will be populated (and overridden)
  236. // if the height: css attribute exists on the source node.
  237. height: '',
  238. // structure: dojox.grid.__ViewDef|dojox.grid.__ViewDef[]|dojox.grid.__CellDef[]|Array[dojox.grid.__CellDef[]]
  239. // View layout defintion.
  240. structure: null,
  241. // elasticView: Integer
  242. // Override defaults and make the indexed grid view elastic, thus filling available horizontal space.
  243. elasticView: -1,
  244. // singleClickEdit: boolean
  245. // Single-click starts editing. Default is double-click
  246. singleClickEdit: false,
  247. // selectionMode: String
  248. // Set the selection mode of grid's Selection. Value must be 'single', 'multiple',
  249. // or 'extended'. Default is 'extended'.
  250. selectionMode: 'extended',
  251. // rowSelector: Boolean|String
  252. // If set to true, will add a row selector view to this grid. If set to a CSS width, will add
  253. // a row selector of that width to this grid.
  254. rowSelector: '',
  255. // columnReordering: Boolean
  256. // If set to true, will add drag and drop reordering to views with one row of columns.
  257. columnReordering: false,
  258. // headerMenu: dijit.Menu
  259. // If set to a dijit.Menu, will use this as a context menu for the grid headers.
  260. headerMenu: null,
  261. // placeholderLabel: String
  262. // Label of placeholders to search for in the header menu to replace with column toggling
  263. // menu items.
  264. placeholderLabel: "GridColumns",
  265. // selectable: Boolean
  266. // Set to true if you want to be able to select the text within the grid.
  267. selectable: false,
  268. // Used to store the last two clicks, to ensure double-clicking occurs based on the intended row
  269. _click: null,
  270. // loadingMessage: String
  271. // Message that shows while the grid is loading
  272. loadingMessage: "<span class='dojoxGridLoading'>${loadingState}</span>",
  273. // errorMessage: String
  274. // Message that shows when the grid encounters an error loading
  275. errorMessage: "<span class='dojoxGridError'>${errorState}</span>",
  276. // noDataMessage: String
  277. // Message that shows if the grid has no data - wrap it in a
  278. // span with class 'dojoxGridNoData' if you want it to be
  279. // styled similar to the loading and error messages
  280. noDataMessage: "",
  281. // escapeHTMLInData: Boolean
  282. // This will escape HTML brackets from the data to prevent HTML from
  283. // user-inputted data being rendered with may contain JavaScript and result in
  284. // XSS attacks. This is true by default, and it is recommended that it remain
  285. // true. Setting this to false will allow data to be displayed in the grid without
  286. // filtering, and should be only used if it is known that the data won't contain
  287. // malicious scripts. If HTML is needed in grid cells, it is recommended that
  288. // you use the formatter function to generate the HTML (the output of
  289. // formatter functions is not filtered, even with escapeHTMLInData set to true).
  290. escapeHTMLInData: true,
  291. // formatterScope: Object
  292. // An object to execute format functions within. If not set, the
  293. // format functions will execute within the scope of the cell that
  294. // has a format function.
  295. formatterScope: null,
  296. // editable: boolean
  297. // indicates if the grid contains editable cells, default is false
  298. // set to true if editable cell encountered during rendering
  299. editable: false,
  300. // private
  301. sortInfo: 0,
  302. themeable: true,
  303. _placeholders: null,
  304. // _layoutClass: Object
  305. // The class to use for our layout - can be overridden by grid subclasses
  306. _layoutClass: _Layout,
  307. // initialization
  308. buildRendering: function(){
  309. this.inherited(arguments);
  310. if(!this.domNode.getAttribute('tabIndex')){
  311. this.domNode.tabIndex = "0";
  312. }
  313. this.createScroller();
  314. this.createLayout();
  315. this.createViews();
  316. this.createManagers();
  317. this.createSelection();
  318. this.connect(this.selection, "onSelected", "onSelected");
  319. this.connect(this.selection, "onDeselected", "onDeselected");
  320. this.connect(this.selection, "onChanged", "onSelectionChanged");
  321. metrics.initOnFontResize();
  322. this.connect(metrics, "onFontResize", "textSizeChanged");
  323. util.funnelEvents(this.domNode, this, 'doKeyEvent', util.keyEvents);
  324. if (this.selectionMode != "none") {
  325. this.domNode.setAttribute("aria-multiselectable", this.selectionMode == "single" ? "false" : "true");
  326. }
  327. html.addClass(this.domNode, this.classTag);
  328. if(!this.isLeftToRight()){
  329. html.addClass(this.domNode, this.classTag+"Rtl");
  330. }
  331. },
  332. postMixInProperties: function(){
  333. this.inherited(arguments);
  334. var messages = dojo.i18n.getLocalization("dijit", "loading", this.lang);
  335. this.loadingMessage = string.substitute(this.loadingMessage, messages);
  336. this.errorMessage = string.substitute(this.errorMessage, messages);
  337. if(this.srcNodeRef && this.srcNodeRef.style.height){
  338. this.height = this.srcNodeRef.style.height;
  339. }
  340. // Call this to update our autoheight to start out
  341. this._setAutoHeightAttr(this.autoHeight, true);
  342. this.lastScrollTop = this.scrollTop = 0;
  343. },
  344. postCreate: function(){
  345. this._placeholders = [];
  346. this._setHeaderMenuAttr(this.headerMenu);
  347. this._setStructureAttr(this.structure);
  348. this._click = [];
  349. this.inherited(arguments);
  350. if(this.domNode && this.autoWidth && this.initialWidth){
  351. this.domNode.style.width = this.initialWidth;
  352. }
  353. if (this.domNode && !this.editable){
  354. // default value for aria-readonly is false, set to true if grid is not editable
  355. html.attr(this.domNode,"aria-readonly", "true");
  356. }
  357. },
  358. destroy: function(){
  359. this.domNode.onReveal = null;
  360. this.domNode.onSizeChange = null;
  361. // Fixes IE domNode leak
  362. delete this._click;
  363. if(this.scroller){
  364. this.scroller.destroy();
  365. delete this.scroller;
  366. }
  367. this.edit.destroy();
  368. delete this.edit;
  369. this.views.destroyViews();
  370. if(this.focus){
  371. this.focus.destroy();
  372. delete this.focus;
  373. }
  374. if(this.headerMenu&&this._placeholders.length){
  375. array.forEach(this._placeholders, function(p){ p.unReplace(true); });
  376. this.headerMenu.unBindDomNode(this.viewsHeaderNode);
  377. }
  378. this.inherited(arguments);
  379. },
  380. _setAutoHeightAttr: function(ah, skipRender){
  381. // Calculate our autoheight - turn it into a boolean or an integer
  382. if(typeof ah == "string"){
  383. if(!ah || ah == "false"){
  384. ah = false;
  385. }else if (ah == "true"){
  386. ah = true;
  387. }else{
  388. ah = window.parseInt(ah, 10);
  389. }
  390. }
  391. if(typeof ah == "number"){
  392. if(isNaN(ah)){
  393. ah = false;
  394. }
  395. // Autoheight must be at least 1, if it's a number. If it's
  396. // less than 0, we'll take that to mean "all" rows (same as
  397. // autoHeight=true - if it is equal to zero, we'll take that
  398. // to mean autoHeight=false
  399. if(ah < 0){
  400. ah = true;
  401. }else if (ah === 0){
  402. ah = false;
  403. }
  404. }
  405. this.autoHeight = ah;
  406. if(typeof ah == "boolean"){
  407. this._autoHeight = ah;
  408. }else if(typeof ah == "number"){
  409. this._autoHeight = (ah >= this.get('rowCount'));
  410. }else{
  411. this._autoHeight = false;
  412. }
  413. if(this._started && !skipRender){
  414. this.render();
  415. }
  416. },
  417. _getRowCountAttr: function(){
  418. return this.updating && this.invalidated && this.invalidated.rowCount != undefined ?
  419. this.invalidated.rowCount : this.rowCount;
  420. },
  421. textSizeChanged: function(){
  422. this.render();
  423. },
  424. sizeChange: function(){
  425. this.update();
  426. },
  427. createManagers: function(){
  428. // summary:
  429. // create grid managers for various tasks including rows, focus, selection, editing
  430. // row manager
  431. this.rows = new _RowManager(this);
  432. // focus manager
  433. this.focus = new _FocusManager(this);
  434. // edit manager
  435. this.edit = new _EditManager(this);
  436. },
  437. createSelection: function(){
  438. // summary: Creates a new Grid selection manager.
  439. // selection manager
  440. this.selection = new Selection(this);
  441. },
  442. createScroller: function(){
  443. // summary: Creates a new virtual scroller
  444. this.scroller = new _Scroller();
  445. this.scroller.grid = this;
  446. this.scroller.renderRow = lang.hitch(this, "renderRow");
  447. this.scroller.removeRow = lang.hitch(this, "rowRemoved");
  448. },
  449. createLayout: function(){
  450. // summary: Creates a new Grid layout
  451. this.layout = new this._layoutClass(this);
  452. this.connect(this.layout, "moveColumn", "onMoveColumn");
  453. },
  454. onMoveColumn: function(){
  455. this.update();
  456. },
  457. onResizeColumn: function(/*int*/ cellIdx){
  458. // Called when a column is resized.
  459. },
  460. // views
  461. createViews: function(){
  462. this.views = new _ViewManager(this);
  463. this.views.createView = lang.hitch(this, "createView");
  464. },
  465. createView: function(inClass, idx){
  466. var c = lang.getObject(inClass);
  467. var view = new c({ grid: this, index: idx });
  468. this.viewsNode.appendChild(view.domNode);
  469. this.viewsHeaderNode.appendChild(view.headerNode);
  470. this.views.addView(view);
  471. html.attr(this.domNode, "align", this.isLeftToRight() ? 'left' : 'right');
  472. return view;
  473. },
  474. buildViews: function(){
  475. for(var i=0, vs; (vs=this.layout.structure[i]); i++){
  476. this.createView(vs.type || dojox._scopeName + ".grid._View", i).setStructure(vs);
  477. }
  478. this.scroller.setContentNodes(this.views.getContentNodes());
  479. },
  480. _setStructureAttr: function(structure){
  481. var s = structure;
  482. if(s && lang.isString(s)){
  483. dojo.deprecated("dojox.grid._Grid.set('structure', 'objVar')", "use dojox.grid._Grid.set('structure', objVar) instead", "2.0");
  484. s=lang.getObject(s);
  485. }
  486. this.structure = s;
  487. if(!s){
  488. if(this.layout.structure){
  489. s = this.layout.structure;
  490. }else{
  491. return;
  492. }
  493. }
  494. this.views.destroyViews();
  495. this.focus.focusView = null;
  496. if(s !== this.layout.structure){
  497. this.layout.setStructure(s);
  498. }
  499. this._structureChanged();
  500. },
  501. setStructure: function(/* dojox.grid.__ViewDef|dojox.grid.__ViewDef[]|dojox.grid.__CellDef[]|Array[dojox.grid.__CellDef[]] */ inStructure){
  502. // summary:
  503. // Install a new structure and rebuild the grid.
  504. dojo.deprecated("dojox.grid._Grid.setStructure(obj)", "use dojox.grid._Grid.set('structure', obj) instead.", "2.0");
  505. this._setStructureAttr(inStructure);
  506. },
  507. getColumnTogglingItems: function(){
  508. // Summary: returns an array of dijit.CheckedMenuItem widgets that can be
  509. // added to a menu for toggling columns on and off.
  510. var items, checkedItems = [];
  511. items = array.map(this.layout.cells, function(cell){
  512. if(!cell.menuItems){ cell.menuItems = []; }
  513. var self = this;
  514. var item = new CheckedMenuItem({
  515. label: cell.name,
  516. checked: !cell.hidden,
  517. _gridCell: cell,
  518. onChange: function(checked){
  519. if(self.layout.setColumnVisibility(this._gridCell.index, checked)){
  520. var items = this._gridCell.menuItems;
  521. if(items.length > 1){
  522. array.forEach(items, function(item){
  523. if(item !== this){
  524. item.setAttribute("checked", checked);
  525. }
  526. }, this);
  527. }
  528. checked = array.filter(self.layout.cells, function(c){
  529. if(c.menuItems.length > 1){
  530. array.forEach(c.menuItems, "item.set('disabled', false);");
  531. }else{
  532. c.menuItems[0].set('disabled', false);
  533. }
  534. return !c.hidden;
  535. });
  536. if(checked.length == 1){
  537. array.forEach(checked[0].menuItems, "item.set('disabled', true);");
  538. }
  539. }
  540. },
  541. destroy: function(){
  542. var index = array.indexOf(this._gridCell.menuItems, this);
  543. this._gridCell.menuItems.splice(index, 1);
  544. delete this._gridCell;
  545. CheckedMenuItem.prototype.destroy.apply(this, arguments);
  546. }
  547. });
  548. cell.menuItems.push(item);
  549. if(!cell.hidden) {
  550. checkedItems.push(item);
  551. }
  552. return item;
  553. }, this); // dijit.CheckedMenuItem[]
  554. if(checkedItems.length == 1) {
  555. checkedItems[0].set('disabled', true);
  556. }
  557. return items;
  558. },
  559. _setHeaderMenuAttr: function(menu){
  560. if(this._placeholders && this._placeholders.length){
  561. array.forEach(this._placeholders, function(p){
  562. p.unReplace(true);
  563. });
  564. this._placeholders = [];
  565. }
  566. if(this.headerMenu){
  567. this.headerMenu.unBindDomNode(this.viewsHeaderNode);
  568. }
  569. this.headerMenu = menu;
  570. if(!menu){ return; }
  571. this.headerMenu.bindDomNode(this.viewsHeaderNode);
  572. if(this.headerMenu.getPlaceholders){
  573. this._placeholders = this.headerMenu.getPlaceholders(this.placeholderLabel);
  574. }
  575. },
  576. setHeaderMenu: function(/* dijit.Menu */ menu){
  577. dojo.deprecated("dojox.grid._Grid.setHeaderMenu(obj)", "use dojox.grid._Grid.set('headerMenu', obj) instead.", "2.0");
  578. this._setHeaderMenuAttr(menu);
  579. },
  580. setupHeaderMenu: function(){
  581. if(this._placeholders && this._placeholders.length){
  582. array.forEach(this._placeholders, function(p){
  583. if(p._replaced){
  584. p.unReplace(true);
  585. }
  586. p.replace(this.getColumnTogglingItems());
  587. }, this);
  588. }
  589. },
  590. _fetch: function(start){
  591. this.setScrollTop(0);
  592. },
  593. getItem: function(inRowIndex){
  594. return null;
  595. },
  596. showMessage: function(message){
  597. if(message){
  598. this.messagesNode.innerHTML = message;
  599. this.messagesNode.style.display = "";
  600. }else{
  601. this.messagesNode.innerHTML = "";
  602. this.messagesNode.style.display = "none";
  603. }
  604. },
  605. _structureChanged: function() {
  606. this.buildViews();
  607. if(this.autoRender && this._started){
  608. this.render();
  609. }
  610. },
  611. hasLayout: function() {
  612. return this.layout.cells.length;
  613. },
  614. // sizing
  615. resize: function(changeSize, resultSize){
  616. // summary:
  617. // Update the grid's rendering dimensions and resize it
  618. // Calling sizeChange calls update() which calls _resize...so let's
  619. // save our input values, if any, and use them there when it gets
  620. // called. This saves us an extra call to _resize(), which can
  621. // get kind of heavy.
  622. this._pendingChangeSize = changeSize;
  623. this._pendingResultSize = resultSize;
  624. this.sizeChange();
  625. },
  626. _getPadBorder: function() {
  627. this._padBorder = this._padBorder || html._getPadBorderExtents(this.domNode);
  628. return this._padBorder;
  629. },
  630. _getHeaderHeight: function(){
  631. var vns = this.viewsHeaderNode.style, t = vns.display == "none" ? 0 : this.views.measureHeader();
  632. vns.height = t + 'px';
  633. // header heights are reset during measuring so must be normalized after measuring.
  634. this.views.normalizeHeaderNodeHeight();
  635. return t;
  636. },
  637. _resize: function(changeSize, resultSize){
  638. // Restore our pending values, if any
  639. changeSize = changeSize || this._pendingChangeSize;
  640. resultSize = resultSize || this._pendingResultSize;
  641. delete this._pendingChangeSize;
  642. delete this._pendingResultSize;
  643. // if we have set up everything except the DOM, we cannot resize
  644. if(!this.domNode){ return; }
  645. var pn = this.domNode.parentNode;
  646. if(!pn || pn.nodeType != 1 || !this.hasLayout() || pn.style.visibility == "hidden" || pn.style.display == "none"){
  647. return;
  648. }
  649. // useful measurement
  650. var padBorder = this._getPadBorder();
  651. var hh = undefined;
  652. var h;
  653. // grid height
  654. if(this._autoHeight){
  655. this.domNode.style.height = 'auto';
  656. }else if(typeof this.autoHeight == "number"){
  657. h = hh = this._getHeaderHeight();
  658. h += (this.scroller.averageRowHeight * this.autoHeight);
  659. this.domNode.style.height = h + "px";
  660. }else if(this.domNode.clientHeight <= padBorder.h){
  661. if(pn == document.body){
  662. this.domNode.style.height = this.defaultHeight;
  663. }else if(this.height){
  664. this.domNode.style.height = this.height;
  665. }else{
  666. this.fitTo = "parent";
  667. }
  668. }
  669. // if we are given dimensions, size the grid's domNode to those dimensions
  670. if(resultSize){
  671. changeSize = resultSize;
  672. }
  673. if(!this._autoHeight && changeSize){
  674. html.marginBox(this.domNode, changeSize);
  675. this.height = this.domNode.style.height;
  676. delete this.fitTo;
  677. }else if(this.fitTo == "parent"){
  678. h = this._parentContentBoxHeight = (this._parentContentBoxHeight > 0 ? this._parentContentBoxHeight : html._getContentBox(pn).h);
  679. this.domNode.style.height = Math.max(0, h) + "px";
  680. }
  681. var hasFlex = array.some(this.views.views, function(v){ return v.flexCells; });
  682. if(!this._autoHeight && (h || html._getContentBox(this.domNode).h) === 0){
  683. // We need to hide the header, since the Grid is essentially hidden.
  684. this.viewsHeaderNode.style.display = "none";
  685. }else{
  686. // Otherwise, show the header and give it an appropriate height.
  687. this.viewsHeaderNode.style.display = "block";
  688. if(!hasFlex && hh === undefined){
  689. hh = this._getHeaderHeight();
  690. }
  691. }
  692. if(hasFlex){
  693. hh = undefined;
  694. }
  695. // NOTE: it is essential that width be applied before height
  696. // Header height can only be calculated properly after view widths have been set.
  697. // This is because flex column width is naturally 0 in Firefox.
  698. // Therefore prior to width sizing flex columns with spaces are maximally wrapped
  699. // and calculated to be too tall.
  700. this.adaptWidth();
  701. this.adaptHeight(hh);
  702. this.postresize();
  703. },
  704. adaptWidth: function() {
  705. // private: sets width and position for views and update grid width if necessary
  706. var doAutoWidth = (!this.initialWidth && this.autoWidth);
  707. var w = doAutoWidth ? 0 : this.domNode.clientWidth || (this.domNode.offsetWidth - this._getPadBorder().w),
  708. vw = this.views.arrange(1, w);
  709. this.views.onEach("adaptWidth");
  710. if(doAutoWidth){
  711. this.domNode.style.width = vw + "px";
  712. }
  713. },
  714. adaptHeight: function(inHeaderHeight){
  715. // private: measures and normalizes header height, then sets view heights, and then updates scroller
  716. // content extent
  717. var t = inHeaderHeight === undefined ? this._getHeaderHeight() : inHeaderHeight;
  718. var h = (this._autoHeight ? -1 : Math.max(this.domNode.clientHeight - t, 0) || 0);
  719. this.views.onEach('setSize', [0, h]);
  720. this.views.onEach('adaptHeight');
  721. if(!this._autoHeight){
  722. var numScroll = 0, numNoScroll = 0;
  723. var noScrolls = array.filter(this.views.views, function(v){
  724. var has = v.hasHScrollbar();
  725. if(has){ numScroll++; }else{ numNoScroll++; }
  726. return (!has);
  727. });
  728. if(numScroll > 0 && numNoScroll > 0){
  729. array.forEach(noScrolls, function(v){
  730. v.adaptHeight(true);
  731. });
  732. }
  733. }
  734. if(this.autoHeight === true || h != -1 || (typeof this.autoHeight == "number" && this.autoHeight >= this.get('rowCount'))){
  735. this.scroller.windowHeight = h;
  736. }else{
  737. this.scroller.windowHeight = Math.max(this.domNode.clientHeight - t, 0);
  738. }
  739. },
  740. // startup
  741. startup: function(){
  742. if(this._started){return;}
  743. this.inherited(arguments);
  744. if(this.autoRender){
  745. this.render();
  746. }
  747. },
  748. // render
  749. render: function(){
  750. // summary:
  751. // Render the grid, headers, and views. Edit and scrolling states are reset. To retain edit and
  752. // scrolling states, see Update.
  753. if(!this.domNode){return;}
  754. if(!this._started){return;}
  755. if(!this.hasLayout()) {
  756. this.scroller.init(0, this.keepRows, this.rowsPerPage);
  757. return;
  758. }
  759. //
  760. this.update = this.defaultUpdate;
  761. this._render();
  762. },
  763. _render: function(){
  764. this.scroller.init(this.get('rowCount'), this.keepRows, this.rowsPerPage);
  765. this.prerender();
  766. this.setScrollTop(0);
  767. this.postrender();
  768. },
  769. prerender: function(){
  770. // if autoHeight, make sure scroller knows not to virtualize; everything must be rendered.
  771. this.keepRows = this._autoHeight ? 0 : this.keepRows;
  772. this.scroller.setKeepInfo(this.keepRows);
  773. this.views.render();
  774. this._resize();
  775. },
  776. postrender: function(){
  777. this.postresize();
  778. this.focus.initFocusView();
  779. // make rows unselectable
  780. html.setSelectable(this.domNode, this.selectable);
  781. },
  782. postresize: function(){
  783. // views are position absolute, so they do not inflate the parent
  784. if(this._autoHeight){
  785. var size = Math.max(this.views.measureContent()) + 'px';
  786. this.viewsNode.style.height = size;
  787. }
  788. },
  789. renderRow: function(inRowIndex, inNodes){
  790. // summary: private, used internally to render rows
  791. this.views.renderRow(inRowIndex, inNodes, this._skipRowRenormalize);
  792. },
  793. rowRemoved: function(inRowIndex){
  794. // summary: private, used internally to remove rows
  795. this.views.rowRemoved(inRowIndex);
  796. },
  797. invalidated: null,
  798. updating: false,
  799. beginUpdate: function(){
  800. // summary:
  801. // Use to make multiple changes to rows while queueing row updating.
  802. // NOTE: not currently supporting nested begin/endUpdate calls
  803. this.invalidated = [];
  804. this.updating = true;
  805. },
  806. endUpdate: function(){
  807. // summary:
  808. // Use after calling beginUpdate to render any changes made to rows.
  809. this.updating = false;
  810. var i = this.invalidated, r;
  811. if(i.all){
  812. this.update();
  813. }else if(i.rowCount != undefined){
  814. this.updateRowCount(i.rowCount);
  815. }else{
  816. for(r in i){
  817. this.updateRow(Number(r));
  818. }
  819. }
  820. this.invalidated = [];
  821. },
  822. // update
  823. defaultUpdate: function(){
  824. // note: initial update calls render and subsequently this function.
  825. if(!this.domNode || this.edit._noUpdate){return;}
  826. if(this.updating){
  827. this.invalidated.all = true;
  828. return;
  829. }
  830. //this.edit.saveState(inRowIndex);
  831. this.lastScrollTop = this.scrollTop;
  832. this.prerender();
  833. this.scroller.invalidateNodes();
  834. this.setScrollTop(this.lastScrollTop);
  835. this.postrender();
  836. //this.edit.restoreState(inRowIndex);
  837. },
  838. update: function(){
  839. // summary:
  840. // Update the grid, retaining edit and scrolling states.
  841. this.render();
  842. },
  843. updateRow: function(inRowIndex){
  844. // summary:
  845. // Render a single row.
  846. // inRowIndex: Integer
  847. // Index of the row to render
  848. inRowIndex = Number(inRowIndex);
  849. if(this.updating){
  850. this.invalidated[inRowIndex]=true;
  851. }else{
  852. this.views.updateRow(inRowIndex);
  853. this.scroller.rowHeightChanged(inRowIndex);
  854. }
  855. },
  856. updateRows: function(startIndex, howMany){
  857. // summary:
  858. // Render consecutive rows at once.
  859. // startIndex: Integer
  860. // Index of the starting row to render
  861. // howMany: Integer
  862. // How many rows to update.
  863. startIndex = Number(startIndex);
  864. howMany = Number(howMany);
  865. var i;
  866. if(this.updating){
  867. for(i=0; i<howMany; i++){
  868. this.invalidated[i+startIndex]=true;
  869. }
  870. }else{
  871. for(i=0; i<howMany; i++){
  872. this.views.updateRow(i+startIndex, this._skipRowRenormalize);
  873. }
  874. this.scroller.rowHeightChanged(startIndex);
  875. }
  876. },
  877. updateRowCount: function(inRowCount){
  878. //summary:
  879. // Change the number of rows.
  880. // inRowCount: int
  881. // Number of rows in the grid.
  882. if(this.updating){
  883. this.invalidated.rowCount = inRowCount;
  884. }else{
  885. this.rowCount = inRowCount;
  886. this._setAutoHeightAttr(this.autoHeight, true);
  887. if(this.layout.cells.length){
  888. this.scroller.updateRowCount(inRowCount);
  889. }
  890. this._resize();
  891. if(this.layout.cells.length){
  892. this.setScrollTop(this.scrollTop);
  893. }
  894. }
  895. },
  896. updateRowStyles: function(inRowIndex){
  897. // summary:
  898. // Update the styles for a row after it's state has changed.
  899. this.views.updateRowStyles(inRowIndex);
  900. },
  901. getRowNode: function(inRowIndex){
  902. // summary:
  903. // find the rowNode that is not a rowSelector
  904. if (this.focus.focusView && !(this.focus.focusView instanceof _RowSelector)){
  905. return this.focus.focusView.rowNodes[inRowIndex];
  906. }else{ // search through views
  907. for (var i = 0, cView; (cView = this.views.views[i]); i++) {
  908. if (!(cView instanceof _RowSelector)) {
  909. return cView.rowNodes[inRowIndex];
  910. }
  911. }
  912. }
  913. return null;
  914. },
  915. rowHeightChanged: function(inRowIndex){
  916. // summary:
  917. // Update grid when the height of a row has changed. Row height is handled automatically as rows
  918. // are rendered. Use this function only to update a row's height outside the normal rendering process.
  919. // inRowIndex: Integer
  920. // index of the row that has changed height
  921. this.views.renormalizeRow(inRowIndex);
  922. this.scroller.rowHeightChanged(inRowIndex);
  923. },
  924. // fastScroll: Boolean
  925. // flag modifies vertical scrolling behavior. Defaults to true but set to false for slower
  926. // scroll performance but more immediate scrolling feedback
  927. fastScroll: true,
  928. delayScroll: false,
  929. // scrollRedrawThreshold: int
  930. // pixel distance a user must scroll vertically to trigger grid scrolling.
  931. scrollRedrawThreshold: (has("ie") ? 100 : 50),
  932. // scroll methods
  933. scrollTo: function(inTop){
  934. // summary:
  935. // Vertically scroll the grid to a given pixel position
  936. // inTop: Integer
  937. // vertical position of the grid in pixels
  938. if(!this.fastScroll){
  939. this.setScrollTop(inTop);
  940. return;
  941. }
  942. var delta = Math.abs(this.lastScrollTop - inTop);
  943. this.lastScrollTop = inTop;
  944. if(delta > this.scrollRedrawThreshold || this.delayScroll){
  945. this.delayScroll = true;
  946. this.scrollTop = inTop;
  947. this.views.setScrollTop(inTop);
  948. if(this._pendingScroll){
  949. window.clearTimeout(this._pendingScroll);
  950. }
  951. var _this = this;
  952. this._pendingScroll = window.setTimeout(function(){
  953. delete _this._pendingScroll;
  954. _this.finishScrollJob();
  955. }, 200);
  956. }else{
  957. this.setScrollTop(inTop);
  958. }
  959. },
  960. finishScrollJob: function(){
  961. this.delayScroll = false;
  962. this.setScrollTop(this.scrollTop);
  963. },
  964. setScrollTop: function(inTop){
  965. this.scroller.scroll(this.views.setScrollTop(inTop));
  966. },
  967. scrollToRow: function(inRowIndex){
  968. // summary:
  969. // Scroll the grid to a specific row.
  970. // inRowIndex: Integer
  971. // grid row index
  972. this.setScrollTop(this.scroller.findScrollTop(inRowIndex) + 1);
  973. },
  974. // styling (private, used internally to style individual parts of a row)
  975. styleRowNode: function(inRowIndex, inRowNode){
  976. if(inRowNode){
  977. this.rows.styleRowNode(inRowIndex, inRowNode);
  978. }
  979. },
  980. // called when the mouse leaves the grid so we can deselect all hover rows
  981. _mouseOut: function(e){
  982. this.rows.setOverRow(-2);
  983. },
  984. // cells
  985. getCell: function(inIndex){
  986. // summary:
  987. // Retrieves the cell object for a given grid column.
  988. // inIndex: Integer
  989. // Grid column index of cell to retrieve
  990. // returns:
  991. // a grid cell
  992. return this.layout.cells[inIndex];
  993. },
  994. setCellWidth: function(inIndex, inUnitWidth){
  995. this.getCell(inIndex).unitWidth = inUnitWidth;
  996. },
  997. getCellName: function(inCell){
  998. // summary: Returns the cell name of a passed cell
  999. return "Cell " + inCell.index; // String
  1000. },
  1001. // sorting
  1002. canSort: function(inSortInfo){
  1003. // summary:
  1004. // Determines if the grid can be sorted
  1005. // inSortInfo: Integer
  1006. // Sort information, 1-based index of column on which to sort, positive for an ascending sort
  1007. // and negative for a descending sort
  1008. // returns: Boolean
  1009. // True if grid can be sorted on the given column in the given direction
  1010. },
  1011. sort: function(){
  1012. },
  1013. getSortAsc: function(inSortInfo){
  1014. // summary:
  1015. // Returns true if grid is sorted in an ascending direction.
  1016. inSortInfo = inSortInfo == undefined ? this.sortInfo : inSortInfo;
  1017. return Boolean(inSortInfo > 0); // Boolean
  1018. },
  1019. getSortIndex: function(inSortInfo){
  1020. // summary:
  1021. // Returns the index of the column on which the grid is sorted
  1022. inSortInfo = inSortInfo == undefined ? this.sortInfo : inSortInfo;
  1023. return Math.abs(inSortInfo) - 1; // Integer
  1024. },
  1025. setSortIndex: function(inIndex, inAsc){
  1026. // summary:
  1027. // Sort the grid on a column in a specified direction
  1028. // inIndex: Integer
  1029. // Column index on which to sort.
  1030. // inAsc: Boolean
  1031. // If true, sort the grid in ascending order, otherwise in descending order
  1032. var si = inIndex +1;
  1033. if(inAsc != undefined){
  1034. si *= (inAsc ? 1 : -1);
  1035. } else if(this.getSortIndex() == inIndex){
  1036. si = -this.sortInfo;
  1037. }
  1038. this.setSortInfo(si);
  1039. },
  1040. setSortInfo: function(inSortInfo){
  1041. if(this.canSort(inSortInfo)){
  1042. this.sortInfo = inSortInfo;
  1043. this.sort();
  1044. this.update();
  1045. }
  1046. },
  1047. // DOM event handler
  1048. doKeyEvent: function(e){
  1049. e.dispatch = 'do' + e.type;
  1050. this.onKeyEvent(e);
  1051. },
  1052. // event dispatch
  1053. //: protected
  1054. _dispatch: function(m, e){
  1055. if(m in this){
  1056. return this[m](e);
  1057. }
  1058. return false;
  1059. },
  1060. dispatchKeyEvent: function(e){
  1061. this._dispatch(e.dispatch, e);
  1062. },
  1063. dispatchContentEvent: function(e){
  1064. this.edit.dispatchEvent(e) || e.sourceView.dispatchContentEvent(e) || this._dispatch(e.dispatch, e);
  1065. },
  1066. dispatchHeaderEvent: function(e){
  1067. e.sourceView.dispatchHeaderEvent(e) || this._dispatch('doheader' + e.type, e);
  1068. },
  1069. dokeydown: function(e){
  1070. this.onKeyDown(e);
  1071. },
  1072. doclick: function(e){
  1073. if(e.cellNode){
  1074. this.onCellClick(e);
  1075. }else{
  1076. this.onRowClick(e);
  1077. }
  1078. },
  1079. dodblclick: function(e){
  1080. if(e.cellNode){
  1081. this.onCellDblClick(e);
  1082. }else{
  1083. this.onRowDblClick(e);
  1084. }
  1085. },
  1086. docontextmenu: function(e){
  1087. if(e.cellNode){
  1088. this.onCellContextMenu(e);
  1089. }else{
  1090. this.onRowContextMenu(e);
  1091. }
  1092. },
  1093. doheaderclick: function(e){
  1094. if(e.cellNode){
  1095. this.onHeaderCellClick(e);
  1096. }else{
  1097. this.onHeaderClick(e);
  1098. }
  1099. },
  1100. doheaderdblclick: function(e){
  1101. if(e.cellNode){
  1102. this.onHeaderCellDblClick(e);
  1103. }else{
  1104. this.onHeaderDblClick(e);
  1105. }
  1106. },
  1107. doheadercontextmenu: function(e){
  1108. if(e.cellNode){
  1109. this.onHeaderCellContextMenu(e);
  1110. }else{
  1111. this.onHeaderContextMenu(e);
  1112. }
  1113. },
  1114. // override to modify editing process
  1115. doStartEdit: function(inCell, inRowIndex){
  1116. this.onStartEdit(inCell, inRowIndex);
  1117. },
  1118. doApplyCellEdit: function(inValue, inRowIndex, inFieldIndex){
  1119. this.onApplyCellEdit(inValue, inRowIndex, inFieldIndex);
  1120. },
  1121. doCancelEdit: function(inRowIndex){
  1122. this.onCancelEdit(inRowIndex);
  1123. },
  1124. doApplyEdit: function(inRowIndex){
  1125. this.onApplyEdit(inRowIndex);
  1126. },
  1127. // row editing
  1128. addRow: function(){
  1129. // summary:
  1130. // Add a row to the grid.
  1131. this.updateRowCount(this.get('rowCount')+1);
  1132. },
  1133. removeSelectedRows: function(){
  1134. // summary:
  1135. // Remove the selected rows from the grid.
  1136. if(this.allItemsSelected){
  1137. this.updateRowCount(0);
  1138. }else{
  1139. this.updateRowCount(Math.max(0, this.get('rowCount') - this.selection.getSelected().length));
  1140. }
  1141. this.selection.clear();
  1142. }
  1143. });
  1144. _Grid.markupFactory = function(props, node, ctor, cellFunc){
  1145. var widthFromAttr = function(n){
  1146. var w = html.attr(n, "width")||"auto";
  1147. if((w != "auto")&&(w.slice(-2) != "em")&&(w.slice(-1) != "%")){
  1148. w = parseInt(w, 10)+"px";
  1149. }
  1150. return w;
  1151. };
  1152. // if(!props.store){ console.debug("no store!"); }
  1153. // if a structure isn't referenced, do we have enough
  1154. // data to try to build one automatically?
  1155. if( !props.structure &&
  1156. node.nodeName.toLowerCase() == "table"){
  1157. // try to discover a structure
  1158. props.structure = query("> colgroup", node).map(function(cg){
  1159. var sv = html.attr(cg, "span");
  1160. var v = {
  1161. noscroll: (html.attr(cg, "noscroll") == "true") ? true : false,
  1162. __span: (!!sv ? parseInt(sv, 10) : 1),
  1163. cells: []
  1164. };
  1165. if(html.hasAttr(cg, "width")){
  1166. v.width = widthFromAttr(cg);
  1167. }
  1168. return v; // for vendetta
  1169. });
  1170. if(!props.structure.length){
  1171. props.structure.push({
  1172. __span: Infinity,
  1173. cells: [] // catch-all view
  1174. });
  1175. }
  1176. // check to see if we're gonna have more than one view
  1177. // for each tr in our th, create a row of cells
  1178. query("thead > tr", node).forEach(function(tr, tr_idx){
  1179. var cellCount = 0;
  1180. var viewIdx = 0;
  1181. var lastViewIdx;
  1182. var cView = null;
  1183. query("> th", tr).map(function(th){
  1184. // what view will this cell go into?
  1185. // NOTE:
  1186. // to prevent extraneous iteration, we start counters over
  1187. // for each row, incrementing over the surface area of the
  1188. // structure that colgroup processing generates and
  1189. // creating cell objects for each <th> to place into those
  1190. // cell groups. There's a lot of state-keepking logic
  1191. // here, but it is what it has to be.
  1192. if(!cView){ // current view book keeping
  1193. lastViewIdx = 0;
  1194. cView = props.structure[0];
  1195. }else if(cellCount >= (lastViewIdx+cView.__span)){
  1196. viewIdx++;
  1197. // move to allocating things into the next view
  1198. lastViewIdx += cView.__span;
  1199. var lastView = cView;
  1200. cView = props.structure[viewIdx];
  1201. }
  1202. // actually define the cell from what markup hands us
  1203. var cell = {
  1204. name: lang.trim(html.attr(th, "name")||th.innerHTML),
  1205. colSpan: parseInt(html.attr(th, "colspan")||1, 10),
  1206. type: lang.trim(html.attr(th, "cellType")||""),
  1207. id: lang.trim(html.attr(th,"id")||"")
  1208. };
  1209. cellCount += cell.colSpan;
  1210. var rowSpan = html.attr(th, "rowspan");
  1211. if(rowSpan){
  1212. cell.rowSpan = rowSpan;
  1213. }
  1214. if(html.hasAttr(th, "width")){
  1215. cell.width = widthFromAttr(th);
  1216. }
  1217. if(html.hasAttr(th, "relWidth")){
  1218. cell.relWidth = window.parseInt(html.attr(th, "relWidth"), 10);
  1219. }
  1220. if(html.hasAttr(th, "hidden")){
  1221. cell.hidden = (html.attr(th, "hidden") == "true" || html.attr(th, "hidden") === true/*always boolean true in Chrome*/);
  1222. }
  1223. if(cellFunc){
  1224. cellFunc(th, cell);
  1225. }
  1226. cell.type = cell.type ? lang.getObject(cell.type) : dojox.grid.cells.Cell;
  1227. if(cell.type && cell.type.markupFactory){
  1228. cell.type.markupFactory(th, cell);
  1229. }
  1230. if(!cView.cells[tr_idx]){
  1231. cView.cells[tr_idx] = [];
  1232. }
  1233. cView.cells[tr_idx].push(cell);
  1234. });
  1235. });
  1236. }
  1237. return new ctor(props, node);
  1238. };
  1239. return _Grid;
  1240. });