_Grid.js 42 KB

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