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