1 | define([ |
---|
2 | "dojo/_base/kernel", |
---|
3 | "dojo/_base/lang", |
---|
4 | "dojo/_base/declare", |
---|
5 | "dojo/_base/array", |
---|
6 | "dojo/_base/event", |
---|
7 | "dojo/keys", |
---|
8 | "dojo/query", |
---|
9 | "dojo/_base/html", |
---|
10 | "dojo/_base/window", |
---|
11 | "dijit/focus", |
---|
12 | "../../_RowSelector", |
---|
13 | "../_Plugin", |
---|
14 | "../../EnhancedGrid", |
---|
15 | "../../cells/_base", |
---|
16 | "./AutoScroll" |
---|
17 | ], function(dojo, lang, declare, array, event, keys, query, html, win, dijitFocus, _RowSelector, _Plugin, EnhancedGrid){ |
---|
18 | |
---|
19 | /*===== |
---|
20 | declare("__SelectItem", null,{ |
---|
21 | // summary: |
---|
22 | // An abstract representation of an item. |
---|
23 | }); |
---|
24 | declare("__SelectCellItem", __SelectItem,{ |
---|
25 | // summary: |
---|
26 | // An abstract representation of a cell. |
---|
27 | |
---|
28 | // row: Integer |
---|
29 | // Row index of this cell |
---|
30 | row: 0, |
---|
31 | |
---|
32 | // col: Integer |
---|
33 | // Column index of this cell |
---|
34 | col: 0 |
---|
35 | }); |
---|
36 | declare("__SelectRowItem", __SelectItem,{ |
---|
37 | // summary: |
---|
38 | // An abstract representation of a row. |
---|
39 | |
---|
40 | // row: Integer |
---|
41 | // Row index of this row |
---|
42 | row: 0, |
---|
43 | |
---|
44 | // except: Integer[] |
---|
45 | // An array of column indexes of all the unselected cells in this row. |
---|
46 | except: [] |
---|
47 | }); |
---|
48 | declare("__SelectColItem", __SelectItem,{ |
---|
49 | // summary: |
---|
50 | // An abstract representation of a column. |
---|
51 | |
---|
52 | // col: Integer |
---|
53 | // Column index of this column |
---|
54 | col: 0, |
---|
55 | |
---|
56 | // except: Integer[] |
---|
57 | // An array of row indexes of all the unselected cells in this column. |
---|
58 | except: [] |
---|
59 | }); |
---|
60 | =====*/ |
---|
61 | |
---|
62 | var DISABLED = 0, SINGLE = 1, MULTI = 2, |
---|
63 | _theOther = { col: "row", row: "col" }, |
---|
64 | _inRange = function(type, value, start, end, halfClose){ |
---|
65 | if(type !== "cell"){ |
---|
66 | value = value[type]; |
---|
67 | start = start[type]; |
---|
68 | end = end[type]; |
---|
69 | if(typeof value !== "number" || typeof start !== "number" || typeof end !== "number"){ |
---|
70 | return false; |
---|
71 | } |
---|
72 | return halfClose ? ((value >= start && value < end) || (value > end && value <= start)) |
---|
73 | : ((value >= start && value <= end) || (value >= end && value <= start)); |
---|
74 | }else{ |
---|
75 | return _inRange("col", value, start, end, halfClose) && _inRange("row", value, start, end, halfClose); |
---|
76 | } |
---|
77 | }, |
---|
78 | _isEqual = function(type, v1, v2){ |
---|
79 | try{ |
---|
80 | if(v1 && v2){ |
---|
81 | switch(type){ |
---|
82 | case "col": case "row": |
---|
83 | return v1[type] == v2[type] && typeof v1[type] == "number" && |
---|
84 | !(_theOther[type] in v1) && !(_theOther[type] in v2); |
---|
85 | case "cell": |
---|
86 | return v1.col == v2.col && v1.row == v2.row && typeof v1.col == "number" && typeof v1.row == "number"; |
---|
87 | } |
---|
88 | } |
---|
89 | }catch(e){} |
---|
90 | return false; |
---|
91 | }, |
---|
92 | _stopEvent = function(evt){ |
---|
93 | try{ |
---|
94 | if(evt && evt.preventDefault){ |
---|
95 | event.stop(evt); |
---|
96 | } |
---|
97 | }catch(e){} |
---|
98 | }, |
---|
99 | _createItem = function(type, rowIndex, colIndex){ |
---|
100 | switch(type){ |
---|
101 | case "col": |
---|
102 | return { |
---|
103 | "col": typeof colIndex == "undefined" ? rowIndex : colIndex, |
---|
104 | "except": [] |
---|
105 | }; |
---|
106 | case "row": |
---|
107 | return { |
---|
108 | "row": rowIndex, |
---|
109 | "except": [] |
---|
110 | }; |
---|
111 | case "cell": |
---|
112 | return { |
---|
113 | "row": rowIndex, |
---|
114 | "col": colIndex |
---|
115 | }; |
---|
116 | } |
---|
117 | return null; |
---|
118 | }; |
---|
119 | var Selector = declare("dojox.grid.enhanced.plugins.Selector", _Plugin, { |
---|
120 | // summary: |
---|
121 | // Provides standard extended selection for grid. |
---|
122 | // Supports mouse/keyboard selection, multi-selection, and de-selection. |
---|
123 | // |
---|
124 | // Acceptable plugin parameters: |
---|
125 | // The whole plugin parameter object is a config object passed to the setupConfig function. |
---|
126 | // |
---|
127 | // Acceptable cell parameters defined in layout: |
---|
128 | // |
---|
129 | // 1. notselectable: Boolean: Whether this column is (and all the cells in it are) selectable. |
---|
130 | |
---|
131 | // name: String |
---|
132 | // plugin name |
---|
133 | name: "selector", |
---|
134 | |
---|
135 | // noClear: Boolean |
---|
136 | // Not to clear rows selected by IndirectSelection. |
---|
137 | /* |
---|
138 | // _config: null, |
---|
139 | // _enabled: true, |
---|
140 | // _selecting: { |
---|
141 | // row: false, |
---|
142 | // col: false, |
---|
143 | // cell: false |
---|
144 | // }, |
---|
145 | // _selected: { |
---|
146 | // row: [], |
---|
147 | // col: [], |
---|
148 | // cell: [] |
---|
149 | // }, |
---|
150 | // _startPoint: {}, |
---|
151 | // _currentPoint: {}, |
---|
152 | // _lastAnchorPoint: {}, |
---|
153 | // _lastEndPoint: {}, |
---|
154 | // _lastSelectedAnchorPoint: {}, |
---|
155 | // _lastSelectedEndPoint: {}, |
---|
156 | // _keyboardSelect: { |
---|
157 | // row: 0, |
---|
158 | // col: 0, |
---|
159 | // cell: 0 |
---|
160 | // }, |
---|
161 | // _curType: null, |
---|
162 | // _lastType: null, |
---|
163 | // _usingKeyboard: false, |
---|
164 | // _toSelect: true, |
---|
165 | */ |
---|
166 | |
---|
167 | constructor: function(grid, args){ |
---|
168 | this.grid = grid; |
---|
169 | this._config = { |
---|
170 | row: MULTI, |
---|
171 | col: MULTI, |
---|
172 | cell: MULTI |
---|
173 | }; |
---|
174 | this.noClear = args && args.noClear; |
---|
175 | this.setupConfig(args); |
---|
176 | if(grid.selectionMode === "single"){ |
---|
177 | this._config.row = SINGLE; |
---|
178 | } |
---|
179 | this._enabled = true; |
---|
180 | this._selecting = {}; |
---|
181 | this._selected = { |
---|
182 | "col": [], |
---|
183 | "row": [], |
---|
184 | "cell": [] |
---|
185 | }; |
---|
186 | this._startPoint = {}; |
---|
187 | this._currentPoint = {}; |
---|
188 | this._lastAnchorPoint = {}; |
---|
189 | this._lastEndPoint = {}; |
---|
190 | this._lastSelectedAnchorPoint = {}; |
---|
191 | this._lastSelectedEndPoint = {}; |
---|
192 | this._keyboardSelect = {}; |
---|
193 | this._lastType = null; |
---|
194 | this._selectedRowModified = {}; |
---|
195 | this._hacks(); |
---|
196 | this._initEvents(); |
---|
197 | this._initAreas(); |
---|
198 | this._mixinGrid(); |
---|
199 | }, |
---|
200 | destroy: function(){ |
---|
201 | this.inherited(arguments); |
---|
202 | }, |
---|
203 | //------------public-------------------- |
---|
204 | setupConfig: function(config){ |
---|
205 | // summary: |
---|
206 | // Set selection mode for row/col/cell. |
---|
207 | // config: Object |
---|
208 | // An object with the following structure (all properties are optional): |
---|
209 | // | { |
---|
210 | // | //Default is "multi", all other values are same as "multi". |
---|
211 | // | row: false|"disabled"|"single", |
---|
212 | // | col: false|"disabled"|"single", |
---|
213 | // | cell: false|"disabled"|"single" |
---|
214 | // | } |
---|
215 | if(!config || !lang.isObject(config)){ |
---|
216 | return; |
---|
217 | } |
---|
218 | var types = ["row", "col", "cell"]; |
---|
219 | for(var type in config){ |
---|
220 | if(array.indexOf(types, type) >= 0){ |
---|
221 | if(!config[type] || config[type] == "disabled"){ |
---|
222 | this._config[type] = DISABLED; |
---|
223 | }else if(config[type] == "single"){ |
---|
224 | this._config[type] = SINGLE; |
---|
225 | }else{ |
---|
226 | this._config[type] = MULTI; |
---|
227 | } |
---|
228 | } |
---|
229 | } |
---|
230 | |
---|
231 | //Have to set mode to default grid selection. |
---|
232 | var mode = ["none","single","extended"][this._config.row]; |
---|
233 | this.grid.selection.setMode(mode); |
---|
234 | }, |
---|
235 | isSelected: function(type, rowIndex, colIndex){ |
---|
236 | // summary: |
---|
237 | // Check whether a location (a cell, a column or a row) is selected. |
---|
238 | // tags: |
---|
239 | // public |
---|
240 | // type: String |
---|
241 | // "row" or "col" or "cell" |
---|
242 | // rowIndex: Integer |
---|
243 | // If type is "row" or "cell", this is the row index. |
---|
244 | // If type if "col", this is the column index. |
---|
245 | // colIndex: Integer? |
---|
246 | // Only valid when type is "cell" |
---|
247 | // returns: Boolean |
---|
248 | // true if selected, false if not. If cell is covered by a selected column, it's selected. |
---|
249 | return this._isSelected(type, _createItem(type, rowIndex, colIndex)); |
---|
250 | }, |
---|
251 | toggleSelect: function(type, rowIndex, colIndex){ |
---|
252 | this._startSelect(type, _createItem(type, rowIndex, colIndex), this._config[type] === MULTI, false, false, !this.isSelected(type, rowIndex, colIndex)); |
---|
253 | this._endSelect(type); |
---|
254 | }, |
---|
255 | select: function(type, rowIndex, colIndex){ |
---|
256 | // summary: |
---|
257 | // Select a location (a cell, a column or a row). |
---|
258 | // tags: |
---|
259 | // public |
---|
260 | // type: String |
---|
261 | // "row" or "col" or "cell" |
---|
262 | // rowIndex: Integer |
---|
263 | // If type is "row" or "cell", this is the row index. |
---|
264 | // If type if "col", this is the column index. |
---|
265 | // colIndex: Integer? |
---|
266 | // Only valid when type is "cell" |
---|
267 | if(!this.isSelected(type, rowIndex, colIndex)){ |
---|
268 | this.toggleSelect(type, rowIndex, colIndex); |
---|
269 | } |
---|
270 | }, |
---|
271 | deselect: function(type, rowIndex, colIndex){ |
---|
272 | if(this.isSelected(type, rowIndex, colIndex)){ |
---|
273 | this.toggleSelect(type, rowIndex, colIndex); |
---|
274 | } |
---|
275 | }, |
---|
276 | selectRange: function(type, start, end, toSelect){ |
---|
277 | // summary: |
---|
278 | // Select a continuous range (a block of cells, a set of continuous columns or rows) |
---|
279 | // tags: |
---|
280 | // public |
---|
281 | // type: String |
---|
282 | // "row" or "col" or "cell" |
---|
283 | // start: Integer|Object |
---|
284 | // If type is "row" or "col", this is the index of the starting row or column. |
---|
285 | // If type if "cell", this is the left-top cell of the range. |
---|
286 | // end: Integer|Object |
---|
287 | // If type is "row" or "col", this is the index of the ending row or column. |
---|
288 | // If type if "cell", this is the right-bottom cell of the range. |
---|
289 | this.grid._selectingRange = true; |
---|
290 | var startPoint = type == "cell" ? _createItem(type, start.row, start.col) : _createItem(type, start), |
---|
291 | endPoint = type == "cell" ? _createItem(type, end.row, end.col) : _createItem(type, end); |
---|
292 | this._startSelect(type, startPoint, false, false, false, toSelect); |
---|
293 | this._highlight(type, endPoint, toSelect === undefined ? true : toSelect); |
---|
294 | this._endSelect(type); |
---|
295 | this.grid._selectingRange = false; |
---|
296 | }, |
---|
297 | clear: function(type){ |
---|
298 | // summary: |
---|
299 | // Clear all selections. |
---|
300 | // tags: |
---|
301 | // public |
---|
302 | // type: String? |
---|
303 | // "row" or "col" or "cell". If omitted, clear all. |
---|
304 | this._clearSelection(type || "all"); |
---|
305 | }, |
---|
306 | isSelecting: function(type){ |
---|
307 | // summary: |
---|
308 | // Check whether the user is currently selecting something. |
---|
309 | // tags: |
---|
310 | // public |
---|
311 | // type: String |
---|
312 | // "row" or "col" or "cell" |
---|
313 | // returns: Boolean |
---|
314 | // true if is selection, false otherwise. |
---|
315 | if(typeof type == "undefined"){ |
---|
316 | return this._selecting.col || this._selecting.row || this._selecting.cell; |
---|
317 | } |
---|
318 | return this._selecting[type]; |
---|
319 | }, |
---|
320 | selectEnabled: function(toEnable){ |
---|
321 | // summary: |
---|
322 | // Turn on/off this selection functionality if *toEnable* is provided. |
---|
323 | // Check whether this selection functionality is enabled if nothing is passed in. |
---|
324 | // tags: |
---|
325 | // public |
---|
326 | // toEnable: Boolean? |
---|
327 | // To enable or not. |
---|
328 | // returns: Boolean|undefined |
---|
329 | // Enabled or not. |
---|
330 | if(typeof toEnable != "undefined" && !this.isSelecting()){ |
---|
331 | this._enabled = !!toEnable; |
---|
332 | } |
---|
333 | return this._enabled; |
---|
334 | }, |
---|
335 | getSelected: function(type, includeExceptions){ |
---|
336 | // summary: |
---|
337 | // Get an array of selected locations. |
---|
338 | // tags: |
---|
339 | // public |
---|
340 | // type: String |
---|
341 | // "row" or "col" or "cell" |
---|
342 | // includeExceptions: Boolean |
---|
343 | // Only meaningful for rows/columns. If true, all selected rows/cols, even they are partly selected, are all returned. |
---|
344 | // returns: __SelectItem[] |
---|
345 | switch(type){ |
---|
346 | case "cell": |
---|
347 | return array.map(this._selected[type], function(item){ return item; }); |
---|
348 | case "col": case "row": |
---|
349 | return array.map(includeExceptions ? this._selected[type] |
---|
350 | : array.filter(this._selected[type], function(item){ |
---|
351 | return item.except.length === 0; |
---|
352 | }), function(item){ |
---|
353 | return includeExceptions ? item : item[type]; |
---|
354 | }); |
---|
355 | } |
---|
356 | return []; |
---|
357 | }, |
---|
358 | getSelectedCount: function(type, includeExceptions){ |
---|
359 | // summary: |
---|
360 | // Get the number of selected items. |
---|
361 | // tags: |
---|
362 | // public |
---|
363 | // type: String |
---|
364 | // "row" or "col" or "cell" |
---|
365 | // includeExceptions: Boolean |
---|
366 | // Only meaningful for rows/columns. If true, all selected rows/cols, even they are partly selected, are all returned. |
---|
367 | // returns: Integer |
---|
368 | // The number of selected items. |
---|
369 | switch(type){ |
---|
370 | case "cell": |
---|
371 | return this._selected[type].length; |
---|
372 | case "col": case "row": |
---|
373 | return (includeExceptions ? this._selected[type] |
---|
374 | : array.filter(this._selected[type], function(item){ |
---|
375 | return item.except.length === 0; |
---|
376 | })).length; |
---|
377 | } |
---|
378 | return 0; |
---|
379 | }, |
---|
380 | getSelectedType: function(){ |
---|
381 | // summary: |
---|
382 | // Get the type of selected items. |
---|
383 | // tags: |
---|
384 | // public |
---|
385 | // returns: String |
---|
386 | // "row" or "col" or "cell", or any mix of these (separator is | ). |
---|
387 | var s = this._selected; |
---|
388 | return ["", "cell", "row", "row|cell", |
---|
389 | "col", "col|cell", "col|row", "col|row|cell" |
---|
390 | ][(!!s.cell.length) | (!!s.row.length << 1) | (!!s.col.length << 2)]; |
---|
391 | }, |
---|
392 | getLastSelectedRange: function(type){ |
---|
393 | // summary: |
---|
394 | // Get last selected range of the given type. |
---|
395 | // tags: |
---|
396 | // public |
---|
397 | // returns: Object |
---|
398 | // {start: __SelectItem, end: __SelectItem} |
---|
399 | // return null if nothing is selected. |
---|
400 | return this._lastAnchorPoint[type] ? { |
---|
401 | "start": this._lastAnchorPoint[type], |
---|
402 | "end": this._lastEndPoint[type] |
---|
403 | } : null; |
---|
404 | }, |
---|
405 | |
---|
406 | //--------------------------private---------------------------- |
---|
407 | _hacks: function(){ |
---|
408 | // summary: |
---|
409 | // Complete the event system of grid, hack some grid functions to prevent default behavior. |
---|
410 | var g = this.grid; |
---|
411 | var doContentMouseUp = function(e){ |
---|
412 | if(e.cellNode){ |
---|
413 | g.onMouseUp(e); |
---|
414 | } |
---|
415 | g.onMouseUpRow(e); |
---|
416 | }; |
---|
417 | var mouseUp = lang.hitch(g, "onMouseUp"); |
---|
418 | var mouseDown = lang.hitch(g, "onMouseDown"); |
---|
419 | var doRowSelectorFocus = function(e){ |
---|
420 | e.cellNode.style.border = "solid 1px"; |
---|
421 | }; |
---|
422 | array.forEach(g.views.views, function(view){ |
---|
423 | view.content.domouseup = doContentMouseUp; |
---|
424 | view.header.domouseup = mouseUp; |
---|
425 | if(view.declaredClass == "dojox.grid._RowSelector"){ |
---|
426 | view.domousedown = mouseDown; |
---|
427 | view.domouseup = mouseUp; |
---|
428 | view.dofocus = doRowSelectorFocus; |
---|
429 | } |
---|
430 | }); |
---|
431 | //Disable default selection. |
---|
432 | g.selection.clickSelect = function(){}; |
---|
433 | |
---|
434 | this._oldDeselectAll = g.selection.deselectAll; |
---|
435 | var _this = this; |
---|
436 | g.selection.selectRange = function(from, to){ |
---|
437 | _this.selectRange("row", from, to, true); |
---|
438 | if(g.selection.preserver){ |
---|
439 | g.selection.preserver._updateMapping(true, true, false, from, to); |
---|
440 | } |
---|
441 | g.selection.onChanged(); |
---|
442 | }; |
---|
443 | g.selection.deselectRange = function(from, to){ |
---|
444 | _this.selectRange("row", from, to, false); |
---|
445 | if(g.selection.preserver){ |
---|
446 | g.selection.preserver._updateMapping(true, false, false, from, to); |
---|
447 | } |
---|
448 | g.selection.onChanged(); |
---|
449 | }; |
---|
450 | g.selection.deselectAll = function(){ |
---|
451 | g._selectingRange = true; |
---|
452 | _this._oldDeselectAll.apply(g.selection, arguments); |
---|
453 | _this._clearSelection("all"); |
---|
454 | g._selectingRange = false; |
---|
455 | if(g.selection.preserver){ |
---|
456 | g.selection.preserver._updateMapping(true, false, true); |
---|
457 | } |
---|
458 | g.selection.onChanged(); |
---|
459 | }; |
---|
460 | |
---|
461 | var rowSelector = g.views.views[0]; |
---|
462 | //The default function re-write the whole className, so can not insert any other classes. |
---|
463 | if(rowSelector instanceof _RowSelector){ |
---|
464 | rowSelector.doStyleRowNode = function(inRowIndex, inRowNode){ |
---|
465 | html.removeClass(inRowNode, "dojoxGridRow"); |
---|
466 | html.addClass(inRowNode, "dojoxGridRowbar"); |
---|
467 | html.addClass(inRowNode, "dojoxGridNonNormalizedCell"); |
---|
468 | html.toggleClass(inRowNode, "dojoxGridRowbarOver", g.rows.isOver(inRowIndex)); |
---|
469 | html.toggleClass(inRowNode, "dojoxGridRowbarSelected", !!g.selection.isSelected(inRowIndex)); |
---|
470 | }; |
---|
471 | } |
---|
472 | this.connect(g, "updateRow", function(rowIndex){ |
---|
473 | array.forEach(g.layout.cells, function(cell){ |
---|
474 | if(this.isSelected("cell", rowIndex, cell.index)){ |
---|
475 | this._highlightNode(cell.getNode(rowIndex), true); |
---|
476 | } |
---|
477 | }, this); |
---|
478 | }); |
---|
479 | }, |
---|
480 | _mixinGrid: function(){ |
---|
481 | // summary: |
---|
482 | // Expose events to grid. |
---|
483 | var g = this.grid; |
---|
484 | g.setupSelectorConfig = lang.hitch(this, this.setupConfig); |
---|
485 | g.onStartSelect = function(){}; |
---|
486 | g.onEndSelect = function(){}; |
---|
487 | g.onStartDeselect = function(){}; |
---|
488 | g.onEndDeselect = function(){}; |
---|
489 | g.onSelectCleared = function(){}; |
---|
490 | }, |
---|
491 | _initEvents: function(){ |
---|
492 | // summary: |
---|
493 | // Connect events, create event handlers. |
---|
494 | var g = this.grid, |
---|
495 | _this = this, |
---|
496 | dp = lang.partial, |
---|
497 | starter = function(type, e){ |
---|
498 | if(type === "row"){ |
---|
499 | _this._isUsingRowSelector = true; |
---|
500 | } |
---|
501 | //only left mouse button can select. |
---|
502 | if(_this.selectEnabled() && _this._config[type] && e.button != 2){ |
---|
503 | if(_this._keyboardSelect.col || _this._keyboardSelect.row || _this._keyboardSelect.cell){ |
---|
504 | _this._endSelect("all"); |
---|
505 | _this._keyboardSelect.col = _this._keyboardSelect.row = _this._keyboardSelect.cell = 0; |
---|
506 | } |
---|
507 | if(_this._usingKeyboard){ |
---|
508 | _this._usingKeyboard = false; |
---|
509 | } |
---|
510 | var target = _createItem(type, e.rowIndex, e.cell && e.cell.index); |
---|
511 | _this._startSelect(type, target, e.ctrlKey, e.shiftKey); |
---|
512 | } |
---|
513 | }, |
---|
514 | ender = lang.hitch(this, "_endSelect"); |
---|
515 | this.connect(g, "onHeaderCellMouseDown", dp(starter, "col")); |
---|
516 | this.connect(g, "onHeaderCellMouseUp", dp(ender, "col")); |
---|
517 | |
---|
518 | this.connect(g, "onRowSelectorMouseDown", dp(starter, "row")); |
---|
519 | this.connect(g, "onRowSelectorMouseUp", dp(ender, "row")); |
---|
520 | |
---|
521 | this.connect(g, "onCellMouseDown", function(e){ |
---|
522 | if(e.cell && e.cell.isRowSelector){ return; } |
---|
523 | if(g.singleClickEdit){ |
---|
524 | _this._singleClickEdit = true; |
---|
525 | g.singleClickEdit = false; |
---|
526 | } |
---|
527 | starter(_this._config["cell"] == DISABLED ? "row" : "cell", e); |
---|
528 | }); |
---|
529 | this.connect(g, "onCellMouseUp", function(e){ |
---|
530 | if(_this._singleClickEdit){ |
---|
531 | delete _this._singleClickEdit; |
---|
532 | g.singleClickEdit = true; |
---|
533 | } |
---|
534 | ender("all", e); |
---|
535 | }); |
---|
536 | |
---|
537 | this.connect(g, "onCellMouseOver", function(e){ |
---|
538 | if(_this._curType != "row" && _this._selecting[_this._curType] && _this._config[_this._curType] == MULTI){ |
---|
539 | _this._highlight("col", _createItem("col", e.cell.index), _this._toSelect); |
---|
540 | if(!_this._keyboardSelect.cell){ |
---|
541 | _this._highlight("cell", _createItem("cell", e.rowIndex, e.cell.index), _this._toSelect); |
---|
542 | } |
---|
543 | } |
---|
544 | }); |
---|
545 | this.connect(g, "onHeaderCellMouseOver", function(e){ |
---|
546 | if(_this._selecting.col && _this._config.col == MULTI){ |
---|
547 | _this._highlight("col", _createItem("col", e.cell.index), _this._toSelect); |
---|
548 | } |
---|
549 | }); |
---|
550 | this.connect(g, "onRowMouseOver", function(e){ |
---|
551 | if(_this._selecting.row && _this._config.row == MULTI){ |
---|
552 | _this._highlight("row", _createItem("row", e.rowIndex), _this._toSelect); |
---|
553 | } |
---|
554 | }); |
---|
555 | |
---|
556 | //When row order has changed in a unpredictable way (sorted or filtered), map the new rowindex. |
---|
557 | this.connect(g, "onSelectedById", "_onSelectedById"); |
---|
558 | |
---|
559 | //When the grid refreshes, all those selected should still appear selected. |
---|
560 | this.connect(g, "_onFetchComplete", function(){ |
---|
561 | //console.debug("refresh after buildPage:", g._notRefreshSelection); |
---|
562 | if(!g._notRefreshSelection){ |
---|
563 | this._refreshSelected(true); |
---|
564 | } |
---|
565 | }); |
---|
566 | |
---|
567 | //Small scroll might not refresh the grid. |
---|
568 | this.connect(g.scroller, "buildPage", function(){ |
---|
569 | //console.debug("refresh after buildPage:", g._notRefreshSelection); |
---|
570 | if(!g._notRefreshSelection){ |
---|
571 | this._refreshSelected(true); |
---|
572 | } |
---|
573 | }); |
---|
574 | |
---|
575 | //Whenever the mouse is up, end selecting. |
---|
576 | this.connect(win.doc, "onmouseup", dp(ender, "all")); |
---|
577 | |
---|
578 | //If autoscroll is enabled, connect to it. |
---|
579 | this.connect(g, "onEndAutoScroll", function(isVertical, isForward, view, target){ |
---|
580 | var selectCell = _this._selecting.cell, |
---|
581 | type, current, dir = isForward ? 1 : -1; |
---|
582 | if(isVertical && (selectCell || _this._selecting.row)){ |
---|
583 | type = selectCell ? "cell" : "row"; |
---|
584 | current = _this._currentPoint[type]; |
---|
585 | _this._highlight(type, _createItem(type, current.row + dir, current.col), _this._toSelect); |
---|
586 | }else if(!isVertical && (selectCell || _this._selecting.col)){ |
---|
587 | type = selectCell ? "cell" : "col"; |
---|
588 | current = _this._currentPoint[type]; |
---|
589 | _this._highlight(type, _createItem(type, current.row, target), _this._toSelect); |
---|
590 | } |
---|
591 | }); |
---|
592 | //If the grid is changed, selection should be consistent. |
---|
593 | this.subscribe("dojox/grid/rearrange/move/" + g.id, "_onInternalRearrange"); |
---|
594 | this.subscribe("dojox/grid/rearrange/copy/" + g.id, "_onInternalRearrange"); |
---|
595 | this.subscribe("dojox/grid/rearrange/change/" + g.id, "_onExternalChange"); |
---|
596 | this.subscribe("dojox/grid/rearrange/insert/" + g.id, "_onExternalChange"); |
---|
597 | this.subscribe("dojox/grid/rearrange/remove/" + g.id, "clear"); |
---|
598 | |
---|
599 | //have to also select when the grid's default select is used. |
---|
600 | this.connect(g, "onSelected", function(rowIndex){ |
---|
601 | if(this._selectedRowModified && this._isUsingRowSelector){ |
---|
602 | delete this._selectedRowModified; |
---|
603 | }else if(!this.grid._selectingRange){ |
---|
604 | this.select("row", rowIndex); |
---|
605 | } |
---|
606 | }); |
---|
607 | this.connect(g, "onDeselected", function(rowIndex){ |
---|
608 | if(this._selectedRowModified && this._isUsingRowSelector){ |
---|
609 | delete this._selectedRowModified; |
---|
610 | }else if(!this.grid._selectingRange){ |
---|
611 | this.deselect("row", rowIndex); |
---|
612 | } |
---|
613 | }); |
---|
614 | }, |
---|
615 | _onSelectedById: function(id, newIndex, isSelected){ |
---|
616 | if(this.grid._noInternalMapping){ |
---|
617 | return; |
---|
618 | } |
---|
619 | var pointSet = [this._lastAnchorPoint.row, this._lastEndPoint.row, |
---|
620 | this._lastSelectedAnchorPoint.row, this._lastSelectedEndPoint.row]; |
---|
621 | pointSet = pointSet.concat(this._selected.row); |
---|
622 | var found = false; |
---|
623 | array.forEach(pointSet, function(item){ |
---|
624 | if(item){ |
---|
625 | if(item.id === id){ |
---|
626 | found = true; |
---|
627 | item.row = newIndex; |
---|
628 | }else if(item.row === newIndex && item.id){ |
---|
629 | item.row = -1; |
---|
630 | } |
---|
631 | } |
---|
632 | }); |
---|
633 | if(!found && isSelected){ |
---|
634 | array.some(this._selected.row, function(item){ |
---|
635 | if(item && !item.id && !item.except.length){ |
---|
636 | item.id = id; |
---|
637 | item.row = newIndex; |
---|
638 | return true; |
---|
639 | } |
---|
640 | return false; |
---|
641 | }); |
---|
642 | } |
---|
643 | found = false; |
---|
644 | pointSet = [this._lastAnchorPoint.cell, this._lastEndPoint.cell, |
---|
645 | this._lastSelectedAnchorPoint.cell, this._lastSelectedEndPoint.cell]; |
---|
646 | pointSet = pointSet.concat(this._selected.cell); |
---|
647 | array.forEach(pointSet, function(item){ |
---|
648 | if(item){ |
---|
649 | if(item.id === id){ |
---|
650 | found = true; |
---|
651 | item.row = newIndex; |
---|
652 | }else if(item.row === newIndex && item.id){ |
---|
653 | item.row = -1; |
---|
654 | } |
---|
655 | } |
---|
656 | }); |
---|
657 | }, |
---|
658 | onSetStore: function(){ |
---|
659 | this._clearSelection("all"); |
---|
660 | }, |
---|
661 | _onInternalRearrange: function(type, mapping){ |
---|
662 | try{ |
---|
663 | //The column can not refresh it self! |
---|
664 | this._refresh("col", false); |
---|
665 | |
---|
666 | array.forEach(this._selected.row, function(item){ |
---|
667 | array.forEach(this.grid.layout.cells, function(cell){ |
---|
668 | this._highlightNode(cell.getNode(item.row), false); |
---|
669 | }, this); |
---|
670 | }, this); |
---|
671 | //The rowbar must be cleaned manually |
---|
672 | query(".dojoxGridRowSelectorSelected").forEach(function(node){ |
---|
673 | html.removeClass(node, "dojoxGridRowSelectorSelected"); |
---|
674 | html.removeClass(node, "dojoxGridRowSelectorSelectedUp"); |
---|
675 | html.removeClass(node, "dojoxGridRowSelectorSelectedDown"); |
---|
676 | }); |
---|
677 | |
---|
678 | var cleanUp = function(item){ |
---|
679 | if(item){ |
---|
680 | delete item.converted; |
---|
681 | } |
---|
682 | }, |
---|
683 | pointSet = [this._lastAnchorPoint[type], this._lastEndPoint[type], |
---|
684 | this._lastSelectedAnchorPoint[type], this._lastSelectedEndPoint[type]]; |
---|
685 | |
---|
686 | if(type === "cell"){ |
---|
687 | this.selectRange("cell", mapping.to.min, mapping.to.max); |
---|
688 | var cells = this.grid.layout.cells; |
---|
689 | array.forEach(pointSet, function(item){ |
---|
690 | if(item.converted){ return; } |
---|
691 | for(var r = mapping.from.min.row, tr = mapping.to.min.row; r <= mapping.from.max.row; ++r, ++tr){ |
---|
692 | for(var c = mapping.from.min.col, tc = mapping.to.min.col; c <= mapping.from.max.col; ++c, ++tc){ |
---|
693 | while(cells[c].hidden){ ++c; } |
---|
694 | while(cells[tc].hidden){ ++tc; } |
---|
695 | if(item.row == r && item.col == c){ |
---|
696 | //console.log('mapping found: (', item.row, ",",item.col,") to (", tr, ",", tc,")"); |
---|
697 | item.row = tr; |
---|
698 | item.col = tc; |
---|
699 | item.converted = true; |
---|
700 | return; |
---|
701 | } |
---|
702 | } |
---|
703 | } |
---|
704 | }); |
---|
705 | }else{ |
---|
706 | pointSet = this._selected.cell.concat(this._selected[type]).concat(pointSet).concat( |
---|
707 | [this._lastAnchorPoint.cell, this._lastEndPoint.cell, |
---|
708 | this._lastSelectedAnchorPoint.cell, this._lastSelectedEndPoint.cell]); |
---|
709 | array.forEach(pointSet, function(item){ |
---|
710 | if(item && !item.converted){ |
---|
711 | var from = item[type]; |
---|
712 | if(from in mapping){ |
---|
713 | item[type] = mapping[from]; |
---|
714 | } |
---|
715 | item.converted = true; |
---|
716 | } |
---|
717 | }); |
---|
718 | array.forEach(this._selected[_theOther[type]], function(item){ |
---|
719 | for(var i = 0, len = item.except.length; i < len; ++i){ |
---|
720 | var from = item.except[i]; |
---|
721 | if(from in mapping){ |
---|
722 | item.except[i] = mapping[from]; |
---|
723 | } |
---|
724 | } |
---|
725 | }); |
---|
726 | } |
---|
727 | |
---|
728 | array.forEach(pointSet, cleanUp); |
---|
729 | |
---|
730 | this._refreshSelected(true); |
---|
731 | this._focusPoint(type, this._lastEndPoint); |
---|
732 | }catch(e){ |
---|
733 | console.warn("Selector._onInternalRearrange() error",e); |
---|
734 | } |
---|
735 | }, |
---|
736 | _onExternalChange: function(type, target){ |
---|
737 | var start = type == "cell" ? target.min : target[0], |
---|
738 | end = type == "cell" ? target.max : target[target.length - 1]; |
---|
739 | this.selectRange(type, start, end); |
---|
740 | }, |
---|
741 | _refresh: function(type, toHighlight){ |
---|
742 | if(!this._keyboardSelect[type]){ |
---|
743 | array.forEach(this._selected[type], function(item){ |
---|
744 | this._highlightSingle(type, toHighlight, item, undefined, true); |
---|
745 | }, this); |
---|
746 | } |
---|
747 | }, |
---|
748 | _refreshSelected: function(){ |
---|
749 | this._refresh("col", true); |
---|
750 | this._refresh("row", true); |
---|
751 | this._refresh("cell", true); |
---|
752 | }, |
---|
753 | _initAreas: function(){ |
---|
754 | var g = this.grid, f = g.focus, _this = this, |
---|
755 | keyboardSelectReady = 1, duringKeyboardSelect = 2, |
---|
756 | onmove = function(type, createNewEnd, rowStep, colStep, evt){ |
---|
757 | //Keyboard swipe selection is SHIFT + Direction Keys. |
---|
758 | var ks = _this._keyboardSelect; |
---|
759 | //Tricky, rely on valid status not being 0. |
---|
760 | if(evt.shiftKey && ks[type]){ |
---|
761 | if(ks[type] === keyboardSelectReady){ |
---|
762 | if(type === "cell"){ |
---|
763 | var item = _this._lastEndPoint[type]; |
---|
764 | if(f.cell != g.layout.cells[item.col + colStep] || f.rowIndex != item.row + rowStep){ |
---|
765 | ks[type] = 0; |
---|
766 | return; |
---|
767 | } |
---|
768 | } |
---|
769 | //If selecting is not started, start it |
---|
770 | _this._startSelect(type, _this._lastAnchorPoint[type], true, false, true); |
---|
771 | _this._highlight(type, _this._lastEndPoint[type], _this._toSelect); |
---|
772 | ks[type] = duringKeyboardSelect; |
---|
773 | } |
---|
774 | //Highlight to the new end point. |
---|
775 | var newEnd = createNewEnd(type, rowStep, colStep, evt); |
---|
776 | if(_this._isValid(type, newEnd, g)){ |
---|
777 | _this._highlight(type, newEnd, _this._toSelect); |
---|
778 | } |
---|
779 | _stopEvent(evt); |
---|
780 | } |
---|
781 | }, |
---|
782 | onkeydown = function(type, getTarget, evt, isBubble){ |
---|
783 | if(isBubble && _this.selectEnabled() && _this._config[type] != DISABLED){ |
---|
784 | switch(evt.keyCode){ |
---|
785 | case keys.SPACE: |
---|
786 | //Keyboard single point selection is SPACE. |
---|
787 | _this._startSelect(type, getTarget(), evt.ctrlKey, evt.shiftKey); |
---|
788 | _this._endSelect(type); |
---|
789 | break; |
---|
790 | case keys.SHIFT: |
---|
791 | //Keyboard swipe selection starts with SHIFT. |
---|
792 | if(_this._config[type] == MULTI && _this._isValid(type, _this._lastAnchorPoint[type], g)){ |
---|
793 | //End last selection if any. |
---|
794 | _this._endSelect(type); |
---|
795 | _this._keyboardSelect[type] = keyboardSelectReady; |
---|
796 | _this._usingKeyboard = true; |
---|
797 | } |
---|
798 | } |
---|
799 | } |
---|
800 | }, |
---|
801 | onkeyup = function(type, evt, isBubble){ |
---|
802 | if(isBubble && evt.keyCode == keys.SHIFT && _this._keyboardSelect[type]){ |
---|
803 | _this._endSelect(type); |
---|
804 | _this._keyboardSelect[type] = 0; |
---|
805 | } |
---|
806 | }; |
---|
807 | //TODO: this area "rowHeader" should be put outside, same level as header/content. |
---|
808 | if(g.views.views[0] instanceof _RowSelector){ |
---|
809 | this._lastFocusedRowBarIdx = 0; |
---|
810 | f.addArea({ |
---|
811 | name:"rowHeader", |
---|
812 | onFocus: function(evt, step){ |
---|
813 | var view = g.views.views[0]; |
---|
814 | if(view instanceof _RowSelector){ |
---|
815 | var rowBarNode = view.getCellNode(_this._lastFocusedRowBarIdx, 0); |
---|
816 | if(rowBarNode){ |
---|
817 | html.toggleClass(rowBarNode, f.focusClass, false); |
---|
818 | } |
---|
819 | //evt might not be real event, it may be a mock object instead. |
---|
820 | if(evt && "rowIndex" in evt){ |
---|
821 | if(evt.rowIndex >= 0){ |
---|
822 | _this._lastFocusedRowBarIdx = evt.rowIndex; |
---|
823 | }else if(!_this._lastFocusedRowBarIdx){ |
---|
824 | _this._lastFocusedRowBarIdx = 0; |
---|
825 | } |
---|
826 | } |
---|
827 | rowBarNode = view.getCellNode(_this._lastFocusedRowBarIdx, 0); |
---|
828 | if(rowBarNode){ |
---|
829 | dijitFocus.focus(rowBarNode); |
---|
830 | html.toggleClass(rowBarNode, f.focusClass, true); |
---|
831 | } |
---|
832 | f.rowIndex = _this._lastFocusedRowBarIdx; |
---|
833 | _stopEvent(evt); |
---|
834 | return true; |
---|
835 | } |
---|
836 | return false; |
---|
837 | }, |
---|
838 | onBlur: function(evt, step){ |
---|
839 | var view = g.views.views[0]; |
---|
840 | if(view instanceof _RowSelector){ |
---|
841 | var rowBarNode = view.getCellNode(_this._lastFocusedRowBarIdx, 0); |
---|
842 | if(rowBarNode){ |
---|
843 | html.toggleClass(rowBarNode, f.focusClass, false); |
---|
844 | } |
---|
845 | _stopEvent(evt); |
---|
846 | } |
---|
847 | return true; |
---|
848 | }, |
---|
849 | onMove: function(rowStep, colStep, evt){ |
---|
850 | var view = g.views.views[0]; |
---|
851 | if(rowStep && view instanceof _RowSelector){ |
---|
852 | var next = _this._lastFocusedRowBarIdx + rowStep; |
---|
853 | if(next >= 0 && next < g.rowCount){ |
---|
854 | //TODO: these logic require a better Scroller. |
---|
855 | _stopEvent(evt); |
---|
856 | var rowBarNode = view.getCellNode(_this._lastFocusedRowBarIdx, 0); |
---|
857 | html.toggleClass(rowBarNode, f.focusClass, false); |
---|
858 | //If the row is not fetched, fetch it. |
---|
859 | var sc = g.scroller; |
---|
860 | var lastPageRow = sc.getLastPageRow(sc.page); |
---|
861 | var rc = g.rowCount - 1, row = Math.min(rc, next); |
---|
862 | if(next > lastPageRow){ |
---|
863 | g.setScrollTop(g.scrollTop + sc.findScrollTop(row) - sc.findScrollTop(_this._lastFocusedRowBarIdx)); |
---|
864 | } |
---|
865 | //Now we have fetched the row. |
---|
866 | rowBarNode = view.getCellNode(next, 0); |
---|
867 | dijitFocus.focus(rowBarNode); |
---|
868 | html.toggleClass(rowBarNode, f.focusClass, true); |
---|
869 | _this._lastFocusedRowBarIdx = next; |
---|
870 | //If the row is out of view, scroll to it. |
---|
871 | f.cell = rowBarNode; |
---|
872 | f.cell.view = view; |
---|
873 | f.cell.getNode = function(index){ |
---|
874 | return f.cell; |
---|
875 | }; |
---|
876 | f.rowIndex = _this._lastFocusedRowBarIdx; |
---|
877 | f.scrollIntoView(); |
---|
878 | f.cell = null; |
---|
879 | } |
---|
880 | } |
---|
881 | } |
---|
882 | }); |
---|
883 | f.placeArea("rowHeader","before","content"); |
---|
884 | } |
---|
885 | //Support keyboard selection. |
---|
886 | f.addArea({ |
---|
887 | name:"cellselect", |
---|
888 | onMove: lang.partial(onmove, "cell", function(type, rowStep, colStep, evt){ |
---|
889 | var current = _this._currentPoint[type]; |
---|
890 | return _createItem("cell", current.row + rowStep, current.col + colStep); |
---|
891 | }), |
---|
892 | onKeyDown: lang.partial(onkeydown, "cell", function(){ |
---|
893 | return _createItem("cell", f.rowIndex, f.cell.index); |
---|
894 | }), |
---|
895 | onKeyUp: lang.partial(onkeyup, "cell") |
---|
896 | }); |
---|
897 | f.placeArea("cellselect","below","content"); |
---|
898 | f.addArea({ |
---|
899 | name:"colselect", |
---|
900 | onMove: lang.partial(onmove, "col", function(type, rowStep, colStep, evt){ |
---|
901 | var current = _this._currentPoint[type]; |
---|
902 | return _createItem("col", current.col + colStep); |
---|
903 | }), |
---|
904 | onKeyDown: lang.partial(onkeydown, "col", function(){ |
---|
905 | return _createItem("col", f.getHeaderIndex()); |
---|
906 | }), |
---|
907 | onKeyUp: lang.partial(onkeyup, "col") |
---|
908 | }); |
---|
909 | f.placeArea("colselect","below","header"); |
---|
910 | f.addArea({ |
---|
911 | name:"rowselect", |
---|
912 | onMove: lang.partial(onmove, "row", function(type, rowStep, colStep, evt){ |
---|
913 | return _createItem("row", f.rowIndex); |
---|
914 | }), |
---|
915 | onKeyDown: lang.partial(onkeydown, "row", function(){ |
---|
916 | return _createItem("row", f.rowIndex); |
---|
917 | }), |
---|
918 | onKeyUp: lang.partial(onkeyup, "row") |
---|
919 | }); |
---|
920 | f.placeArea("rowselect","below","rowHeader"); |
---|
921 | }, |
---|
922 | _clearSelection: function(type, reservedItem){ |
---|
923 | // summary: |
---|
924 | // Clear selection for given type and fire events, but retain the highlight for *reservedItem*, |
---|
925 | // thus avoid "flashing". |
---|
926 | // tags: |
---|
927 | // private |
---|
928 | // type: String |
---|
929 | // "row", "col", or "cell |
---|
930 | // reservedItem: __SelectItem |
---|
931 | // The item to retain highlight. |
---|
932 | if(type == "all"){ |
---|
933 | this._clearSelection("cell", reservedItem); |
---|
934 | this._clearSelection("col", reservedItem); |
---|
935 | this._clearSelection("row", reservedItem); |
---|
936 | return; |
---|
937 | } |
---|
938 | this._isUsingRowSelector = true; |
---|
939 | array.forEach(this._selected[type], function(item){ |
---|
940 | if(!_isEqual(type, reservedItem, item)){ |
---|
941 | this._highlightSingle(type, false, item); |
---|
942 | } |
---|
943 | }, this); |
---|
944 | this._blurPoint(type, this._currentPoint); |
---|
945 | this._selecting[type] = false; |
---|
946 | this._startPoint[type] = this._currentPoint[type] = null; |
---|
947 | this._selected[type] = []; |
---|
948 | |
---|
949 | //Have to also deselect default grid selection. |
---|
950 | if(type == "row" && !this.grid._selectingRange){ |
---|
951 | this._oldDeselectAll.call(this.grid.selection); |
---|
952 | this.grid.selection._selectedById = {}; |
---|
953 | } |
---|
954 | |
---|
955 | //Fire events. |
---|
956 | this.grid.onEndDeselect(type, null, null, this._selected); |
---|
957 | this.grid.onSelectCleared(type); |
---|
958 | }, |
---|
959 | _startSelect: function(type, start, extending, isRange, mandatarySelect, toSelect){ |
---|
960 | // summary: |
---|
961 | // Start selection, setup start point and current point, fire events. |
---|
962 | // tags: |
---|
963 | // private |
---|
964 | // type: String |
---|
965 | // "row", "col", or "cell" |
---|
966 | // extending: Boolean |
---|
967 | // Whether this is a multi selection |
---|
968 | // isRange: Boolean |
---|
969 | // Whether this is a range selection (i.e. select from the last end point to this point) |
---|
970 | // start: __SelectItem |
---|
971 | // The start point |
---|
972 | // mandatarySelect: Boolean |
---|
973 | // If true, toSelect will be same as the original selection status. |
---|
974 | if(!this._isValid(type, start)){ |
---|
975 | return; |
---|
976 | } |
---|
977 | var lastIsSelected = this._isSelected(type, this._lastEndPoint[type]), |
---|
978 | isSelected = this._isSelected(type, start); |
---|
979 | |
---|
980 | if(this.noClear && !extending){ |
---|
981 | this._toSelect = toSelect === undefined ? true : toSelect; |
---|
982 | }else{ |
---|
983 | //If we are modifying the selection using keyboard, retain the old status. |
---|
984 | this._toSelect = mandatarySelect ? isSelected : !isSelected; |
---|
985 | } |
---|
986 | |
---|
987 | //If CTRL is not pressed or it's SINGLE mode, this is a brand new selection. |
---|
988 | if(!extending || (!isSelected && this._config[type] == SINGLE)){ |
---|
989 | this._clearSelection("col", start); |
---|
990 | this._clearSelection("cell", start); |
---|
991 | if(!this.noClear || (type === 'row' && this._config[type] == SINGLE)){ |
---|
992 | this._clearSelection('row', start); |
---|
993 | } |
---|
994 | this._toSelect = toSelect === undefined ? true : toSelect; |
---|
995 | } |
---|
996 | |
---|
997 | this._selecting[type] = true; |
---|
998 | this._currentPoint[type] = null; |
---|
999 | |
---|
1000 | //We're holding SHIFT while clicking, it's a Click-Range selection. |
---|
1001 | if(isRange && this._lastType == type && lastIsSelected == this._toSelect && this._config[type] == MULTI){ |
---|
1002 | if(type === "row"){ |
---|
1003 | this._isUsingRowSelector = true; |
---|
1004 | } |
---|
1005 | this._startPoint[type] = this._lastAnchorPoint[type]; |
---|
1006 | this._highlight(type, this._startPoint[type]); |
---|
1007 | this._isUsingRowSelector = false; |
---|
1008 | }else{ |
---|
1009 | this._startPoint[type] = start; |
---|
1010 | } |
---|
1011 | //Now start selection |
---|
1012 | this._curType = type; |
---|
1013 | this._fireEvent("start", type); |
---|
1014 | this._isStartFocus = true; |
---|
1015 | this._isUsingRowSelector = true; |
---|
1016 | this._highlight(type, start, this._toSelect); |
---|
1017 | this._isStartFocus = false; |
---|
1018 | }, |
---|
1019 | _endSelect: function(type){ |
---|
1020 | // summary: |
---|
1021 | // End selection. Keep records, fire events and cleanup status. |
---|
1022 | // tags: |
---|
1023 | // private |
---|
1024 | // type: String |
---|
1025 | // "row", "col", or "cell" |
---|
1026 | if(type === "row"){ |
---|
1027 | delete this._isUsingRowSelector; |
---|
1028 | } |
---|
1029 | if(type == "all"){ |
---|
1030 | this._endSelect("col"); |
---|
1031 | this._endSelect("row"); |
---|
1032 | this._endSelect("cell"); |
---|
1033 | }else if(this._selecting[type]){ |
---|
1034 | this._addToSelected(type); |
---|
1035 | this._lastAnchorPoint[type] = this._startPoint[type]; |
---|
1036 | this._lastEndPoint[type] = this._currentPoint[type]; |
---|
1037 | if(this._toSelect){ |
---|
1038 | this._lastSelectedAnchorPoint[type] = this._lastAnchorPoint[type]; |
---|
1039 | this._lastSelectedEndPoint[type] = this._lastEndPoint[type]; |
---|
1040 | } |
---|
1041 | this._startPoint[type] = this._currentPoint[type] = null; |
---|
1042 | this._selecting[type] = false; |
---|
1043 | this._lastType = type; |
---|
1044 | this._fireEvent("end", type); |
---|
1045 | } |
---|
1046 | }, |
---|
1047 | _fireEvent: function(evtName, type){ |
---|
1048 | switch(evtName){ |
---|
1049 | case "start": |
---|
1050 | this.grid[this._toSelect ? "onStartSelect" : "onStartDeselect"](type, this._startPoint[type], this._selected); |
---|
1051 | break; |
---|
1052 | case "end": |
---|
1053 | this.grid[this._toSelect ? "onEndSelect" : "onEndDeselect"](type, this._lastAnchorPoint[type], this._lastEndPoint[type], this._selected); |
---|
1054 | break; |
---|
1055 | } |
---|
1056 | }, |
---|
1057 | _calcToHighlight: function(type, target, toHighlight, toSelect){ |
---|
1058 | // summary: |
---|
1059 | // Calculate what status should *target* have. |
---|
1060 | // If *toSelect* is not provided, this is a no op. |
---|
1061 | |
---|
1062 | // This function is time-critical!! |
---|
1063 | if(toSelect !== undefined){ |
---|
1064 | var sltd; |
---|
1065 | if(this._usingKeyboard && !toHighlight){ |
---|
1066 | var last = this._isInLastRange(this._lastType, target); |
---|
1067 | if(last){ |
---|
1068 | sltd = this._isSelected(type, target); |
---|
1069 | //This 2 cases makes the keyboard swipe selection valid! |
---|
1070 | if(toSelect && sltd){ |
---|
1071 | return false; |
---|
1072 | } |
---|
1073 | if(!toSelect && !sltd && this._isInLastRange(this._lastType, target, true)){ |
---|
1074 | return true; |
---|
1075 | } |
---|
1076 | } |
---|
1077 | } |
---|
1078 | return toHighlight ? toSelect : (sltd || this._isSelected(type, target)); |
---|
1079 | } |
---|
1080 | return toHighlight; |
---|
1081 | }, |
---|
1082 | _highlightNode: function(node, toHighlight){ |
---|
1083 | // summary: |
---|
1084 | // Do the actual highlight work. |
---|
1085 | if(node){ |
---|
1086 | var selectCSSClass = "dojoxGridRowSelected"; |
---|
1087 | var selectCellClass = "dojoxGridCellSelected"; |
---|
1088 | html.toggleClass(node, selectCSSClass, toHighlight); |
---|
1089 | html.toggleClass(node, selectCellClass, toHighlight); |
---|
1090 | } |
---|
1091 | }, |
---|
1092 | _highlightHeader: function(colIdx, toHighlight){ |
---|
1093 | var cells = this.grid.layout.cells; |
---|
1094 | var node = cells[colIdx].getHeaderNode(); |
---|
1095 | var selectedClass = "dojoxGridHeaderSelected"; |
---|
1096 | html.toggleClass(node, selectedClass, toHighlight); |
---|
1097 | }, |
---|
1098 | _highlightRowSelector: function(rowIdx, toHighlight){ |
---|
1099 | //var t1 = (new Date()).getTime(); |
---|
1100 | var rowSelector = this.grid.views.views[0]; |
---|
1101 | if(rowSelector instanceof _RowSelector){ |
---|
1102 | var node = rowSelector.getRowNode(rowIdx); |
---|
1103 | if(node){ |
---|
1104 | var selectedClass = "dojoxGridRowSelectorSelected"; |
---|
1105 | html.toggleClass(node, selectedClass, toHighlight); |
---|
1106 | } |
---|
1107 | } |
---|
1108 | //console.log((new Date()).getTime() - t1); |
---|
1109 | }, |
---|
1110 | _highlightSingle: function(type, toHighlight, target, toSelect, isRefresh){ |
---|
1111 | // summary: |
---|
1112 | // Highlight a single item. |
---|
1113 | |
---|
1114 | // This function is time critical!! |
---|
1115 | var _this = this, toHL, g = _this.grid, cells = g.layout.cells; |
---|
1116 | switch(type){ |
---|
1117 | case "cell": |
---|
1118 | toHL = this._calcToHighlight(type, target, toHighlight, toSelect); |
---|
1119 | var c = cells[target.col]; |
---|
1120 | if(!c.hidden && !c.notselectable){ |
---|
1121 | this._highlightNode(target.node || c.getNode(target.row), toHL); |
---|
1122 | } |
---|
1123 | break; |
---|
1124 | case "col": |
---|
1125 | toHL = this._calcToHighlight(type, target, toHighlight, toSelect); |
---|
1126 | this._highlightHeader(target.col, toHL); |
---|
1127 | query("td[idx='" + target.col + "']", g.domNode).forEach(function(cellNode){ |
---|
1128 | var rowNode = cells[target.col].view.content.findRowTarget(cellNode); |
---|
1129 | if(rowNode){ |
---|
1130 | var rowIndex = rowNode[dojox.grid.util.rowIndexTag]; |
---|
1131 | _this._highlightSingle("cell", toHL, { |
---|
1132 | "row": rowIndex, |
---|
1133 | "col": target.col, |
---|
1134 | "node": cellNode |
---|
1135 | }); |
---|
1136 | } |
---|
1137 | }); |
---|
1138 | break; |
---|
1139 | case "row": |
---|
1140 | toHL = this._calcToHighlight(type, target, toHighlight, toSelect); |
---|
1141 | this._highlightRowSelector(target.row, toHL); |
---|
1142 | if(this._config.cell){ |
---|
1143 | array.forEach(cells, function(cell){ |
---|
1144 | _this._highlightSingle("cell", toHL, { |
---|
1145 | "row": target.row, |
---|
1146 | "col": cell.index, |
---|
1147 | "node": cell.getNode(target.row) |
---|
1148 | }); |
---|
1149 | }); |
---|
1150 | } |
---|
1151 | //To avoid dead lock |
---|
1152 | this._selectedRowModified = true; |
---|
1153 | if(!isRefresh){ |
---|
1154 | g.selection.setSelected(target.row, toHL); |
---|
1155 | } |
---|
1156 | } |
---|
1157 | }, |
---|
1158 | _highlight: function(type, target, toSelect){ |
---|
1159 | // summary: |
---|
1160 | // Highlight from start point to target. |
---|
1161 | // toSelect: Boolean |
---|
1162 | // Whether we are selecting or deselecting. |
---|
1163 | |
---|
1164 | // This function is time critical!! |
---|
1165 | if(this._selecting[type] && target !== null){ |
---|
1166 | var start = this._startPoint[type], |
---|
1167 | current = this._currentPoint[type], |
---|
1168 | _this = this, |
---|
1169 | highlight = function(from, to, toHL){ |
---|
1170 | _this._forEach(type, from, to, function(item){ |
---|
1171 | _this._highlightSingle(type, toHL, item, toSelect); |
---|
1172 | }, true); |
---|
1173 | }; |
---|
1174 | switch(type){ |
---|
1175 | case "col": case "row": |
---|
1176 | if(current !== null){ |
---|
1177 | if(_inRange(type, target, start, current, true)){ |
---|
1178 | //target is between start and current, some selected should be deselected. |
---|
1179 | highlight(current, target, false); |
---|
1180 | }else{ |
---|
1181 | if(_inRange(type, start, target, current, true)){ |
---|
1182 | //selection has jumped to different direction, all should be deselected. |
---|
1183 | highlight(current, start, false); |
---|
1184 | current = start; |
---|
1185 | } |
---|
1186 | highlight(target, current, true); |
---|
1187 | } |
---|
1188 | }else{ |
---|
1189 | //First time select. |
---|
1190 | this._highlightSingle(type, true, target, toSelect); |
---|
1191 | } |
---|
1192 | break; |
---|
1193 | case "cell": |
---|
1194 | if(current !== null){ |
---|
1195 | if(_inRange("row", target, start, current, true) || |
---|
1196 | _inRange("col", target, start, current, true) || |
---|
1197 | _inRange("row", start, target, current, true) || |
---|
1198 | _inRange("col", start, target, current, true)){ |
---|
1199 | highlight(start, current, false); |
---|
1200 | } |
---|
1201 | } |
---|
1202 | highlight(start, target, true); |
---|
1203 | } |
---|
1204 | this._currentPoint[type] = target; |
---|
1205 | this._focusPoint(type, this._currentPoint); |
---|
1206 | } |
---|
1207 | }, |
---|
1208 | _focusPoint: function(type, point){ |
---|
1209 | // summary: |
---|
1210 | // Focus the current point, so when you move mouse, the focus indicator follows you. |
---|
1211 | if(!this._isStartFocus){ |
---|
1212 | var current = point[type], |
---|
1213 | f = this.grid.focus; |
---|
1214 | if(type == "col"){ |
---|
1215 | f._colHeadFocusIdx = current.col; |
---|
1216 | f.focusArea("header"); |
---|
1217 | }else if(type == "row"){ |
---|
1218 | f.focusArea("rowHeader", { |
---|
1219 | "rowIndex": current.row |
---|
1220 | }); |
---|
1221 | }else if(type == "cell"){ |
---|
1222 | f.setFocusIndex(current.row, current.col); |
---|
1223 | } |
---|
1224 | } |
---|
1225 | }, |
---|
1226 | _blurPoint: function(type, point){ |
---|
1227 | // summary: |
---|
1228 | // Blur the current point. |
---|
1229 | var f = this.grid.focus; |
---|
1230 | if(type == "cell"){ |
---|
1231 | f._blurContent(); |
---|
1232 | } |
---|
1233 | }, |
---|
1234 | _addToSelected: function(type){ |
---|
1235 | // summary: |
---|
1236 | // Record the selected items. |
---|
1237 | var toSelect = this._toSelect, _this = this, |
---|
1238 | toAdd = [], toRemove = [], |
---|
1239 | start = this._startPoint[type], |
---|
1240 | end = this._currentPoint[type]; |
---|
1241 | if(this._usingKeyboard){ |
---|
1242 | //If using keyboard, selection will be ended after every move. But we have to remember the original selection status, |
---|
1243 | //so as to return to correct status when we shrink the selection region. |
---|
1244 | this._forEach(type, this._lastAnchorPoint[type], this._lastEndPoint[type], function(item){ |
---|
1245 | //If the original selected item is not in current range, change its status. |
---|
1246 | if(!_inRange(type, item, start, end)){ |
---|
1247 | (toSelect ? toRemove : toAdd).push(item); |
---|
1248 | } |
---|
1249 | }); |
---|
1250 | } |
---|
1251 | this._forEach(type, start, end, function(item){ |
---|
1252 | var isSelected = _this._isSelected(type, item); |
---|
1253 | if(toSelect && !isSelected){ |
---|
1254 | //Add new selected items |
---|
1255 | toAdd.push(item); |
---|
1256 | }else if(!toSelect){ |
---|
1257 | //Remove deselected items. |
---|
1258 | toRemove.push(item); |
---|
1259 | } |
---|
1260 | }); |
---|
1261 | this._add(type, toAdd); |
---|
1262 | this._remove(type, toRemove); |
---|
1263 | |
---|
1264 | // have to keep record in original grid selection |
---|
1265 | array.forEach(this._selected.row, function(item){ |
---|
1266 | if(item.except.length > 0){ |
---|
1267 | //to avoid dead lock |
---|
1268 | this._selectedRowModified = true; |
---|
1269 | this.grid.selection.setSelected(item.row, false); |
---|
1270 | } |
---|
1271 | }, this); |
---|
1272 | }, |
---|
1273 | _forEach: function(type, start, end, func, halfClose){ |
---|
1274 | // summary: |
---|
1275 | // Go through items from *start* point to *end* point. |
---|
1276 | |
---|
1277 | // This function is time critical!! |
---|
1278 | if(!this._isValid(type, start, true) || !this._isValid(type, end, true)){ |
---|
1279 | return; |
---|
1280 | } |
---|
1281 | switch(type){ |
---|
1282 | case "col": case "row": |
---|
1283 | start = start[type]; |
---|
1284 | end = end[type]; |
---|
1285 | var dir = end > start ? 1 : -1; |
---|
1286 | if(!halfClose){ |
---|
1287 | end += dir; |
---|
1288 | } |
---|
1289 | for(; start != end; start += dir){ |
---|
1290 | func(_createItem(type, start)); |
---|
1291 | } |
---|
1292 | break; |
---|
1293 | case "cell": |
---|
1294 | var colDir = end.col > start.col ? 1 : -1, |
---|
1295 | rowDir = end.row > start.row ? 1 : -1; |
---|
1296 | for(var i = start.row, p = end.row + rowDir; i != p; i += rowDir){ |
---|
1297 | for(var j = start.col, q = end.col + colDir; j != q; j += colDir){ |
---|
1298 | func(_createItem(type, i, j)); |
---|
1299 | } |
---|
1300 | } |
---|
1301 | } |
---|
1302 | }, |
---|
1303 | _makeupForExceptions: function(type, newCellItems){ |
---|
1304 | // summary: |
---|
1305 | // When new cells is selected, maybe they will fill in the "holes" in selected rows and columns. |
---|
1306 | var makedUps = []; |
---|
1307 | array.forEach(this._selected[type], function(v1){ |
---|
1308 | array.forEach(newCellItems, function(v2){ |
---|
1309 | if(v1[type] == v2[type]){ |
---|
1310 | var pos = array.indexOf(v1.except, v2[_theOther[type]]); |
---|
1311 | if(pos >= 0){ |
---|
1312 | v1.except.splice(pos, 1); |
---|
1313 | } |
---|
1314 | makedUps.push(v2); |
---|
1315 | } |
---|
1316 | }); |
---|
1317 | }); |
---|
1318 | return makedUps; |
---|
1319 | }, |
---|
1320 | _makeupForCells: function(type, newItems){ |
---|
1321 | // summary: |
---|
1322 | // When some rows/cols are selected, maybe they can cover some of the selected cells, |
---|
1323 | // and fill some of the "holes" in the selected cols/rows. |
---|
1324 | var toRemove = []; |
---|
1325 | array.forEach(this._selected.cell, function(v1){ |
---|
1326 | array.some(newItems, function(v2){ |
---|
1327 | if(v1[type] == v2[type]){ |
---|
1328 | toRemove.push(v1); |
---|
1329 | return true; |
---|
1330 | } |
---|
1331 | return false; |
---|
1332 | }); |
---|
1333 | }); |
---|
1334 | this._remove("cell", toRemove); |
---|
1335 | array.forEach(this._selected[_theOther[type]], function(v1){ |
---|
1336 | array.forEach(newItems, function(v2){ |
---|
1337 | var pos = array.indexOf(v1.except, v2[type]); |
---|
1338 | if(pos >= 0){ |
---|
1339 | v1.except.splice(pos, 1); |
---|
1340 | } |
---|
1341 | }); |
---|
1342 | }); |
---|
1343 | }, |
---|
1344 | _addException: function(type, items){ |
---|
1345 | // summary: |
---|
1346 | // If some rows/cols are deselected, maybe they have created "holes" in selected cols/rows. |
---|
1347 | array.forEach(this._selected[type], function(v1){ |
---|
1348 | array.forEach(items, function(v2){ |
---|
1349 | v1.except.push(v2[_theOther[type]]); |
---|
1350 | }); |
---|
1351 | }); |
---|
1352 | }, |
---|
1353 | _addCellException: function(type, items){ |
---|
1354 | // summary: |
---|
1355 | // If some cells are deselected, maybe they have created "holes" in selected rows/cols. |
---|
1356 | array.forEach(this._selected[type], function(v1){ |
---|
1357 | array.forEach(items, function(v2){ |
---|
1358 | if(v1[type] == v2[type]){ |
---|
1359 | v1.except.push(v2[_theOther[type]]); |
---|
1360 | } |
---|
1361 | }); |
---|
1362 | }); |
---|
1363 | }, |
---|
1364 | _add: function(type, items){ |
---|
1365 | // summary: |
---|
1366 | // Add to the selection record. |
---|
1367 | var cells = this.grid.layout.cells; |
---|
1368 | if(type == "cell"){ |
---|
1369 | var colMakedup = this._makeupForExceptions("col", items); |
---|
1370 | var rowMakedup = this._makeupForExceptions("row", items); |
---|
1371 | //Step over hidden columns. |
---|
1372 | items = array.filter(items, function(item){ |
---|
1373 | return array.indexOf(colMakedup, item) < 0 && array.indexOf(rowMakedup, item) < 0 && |
---|
1374 | !cells[item.col].hidden && !cells[item.col].notselectable; |
---|
1375 | }); |
---|
1376 | }else{ |
---|
1377 | if(type == "col"){ |
---|
1378 | //Step over hidden columns. |
---|
1379 | items = array.filter(items, function(item){ |
---|
1380 | return !cells[item.col].hidden && !cells[item.col].notselectable; |
---|
1381 | }); |
---|
1382 | } |
---|
1383 | this._makeupForCells(type, items); |
---|
1384 | this._selected[type] = array.filter(this._selected[type], function(v){ |
---|
1385 | return array.every(items, function(item){ |
---|
1386 | return v[type] !== item[type]; |
---|
1387 | }); |
---|
1388 | }); |
---|
1389 | } |
---|
1390 | if(type != "col" && this.grid._hasIdentity){ |
---|
1391 | array.forEach(items, function(item){ |
---|
1392 | var record = this.grid._by_idx[item.row]; |
---|
1393 | if(record){ |
---|
1394 | item.id = record.idty; |
---|
1395 | } |
---|
1396 | }, this); |
---|
1397 | } |
---|
1398 | this._selected[type] = this._selected[type].concat(items); |
---|
1399 | }, |
---|
1400 | _remove: function(type, items){ |
---|
1401 | // summary: |
---|
1402 | // Remove from the selection record. |
---|
1403 | var comp = lang.partial(_isEqual, type); |
---|
1404 | this._selected[type] = array.filter(this._selected[type], function(v1){ |
---|
1405 | return !array.some(items, function(v2){ |
---|
1406 | return comp(v1, v2); |
---|
1407 | }); |
---|
1408 | }); |
---|
1409 | if(type == "cell"){ |
---|
1410 | this._addCellException("col", items); |
---|
1411 | this._addCellException("row", items); |
---|
1412 | }else if(this._config.cell){ |
---|
1413 | this._addException(_theOther[type], items); |
---|
1414 | } |
---|
1415 | }, |
---|
1416 | _isCellNotInExcept: function(type, item){ |
---|
1417 | // summary: |
---|
1418 | // Return true only when a cell is covered by selected row/col, and its not a "hole". |
---|
1419 | var attr = item[type], corres = item[_theOther[type]]; |
---|
1420 | return array.some(this._selected[type], function(v){ |
---|
1421 | return v[type] == attr && array.indexOf(v.except, corres) < 0; |
---|
1422 | }); |
---|
1423 | }, |
---|
1424 | _isSelected: function(type, item){ |
---|
1425 | // summary: |
---|
1426 | // Return true when the item is selected. (or logically selected, i.e, covered by a row/col). |
---|
1427 | if(!item){ return false; } |
---|
1428 | var res = array.some(this._selected[type], function(v){ |
---|
1429 | var ret = _isEqual(type, item, v); |
---|
1430 | if(ret && type !== "cell"){ |
---|
1431 | return v.except.length === 0; |
---|
1432 | } |
---|
1433 | return ret; |
---|
1434 | }); |
---|
1435 | if(!res && type === "cell"){ |
---|
1436 | res = (this._isCellNotInExcept("col", item) || this._isCellNotInExcept("row", item)); |
---|
1437 | if(type === "cell"){ |
---|
1438 | res = res && !this.grid.layout.cells[item.col].notselectable; |
---|
1439 | } |
---|
1440 | } |
---|
1441 | return res; |
---|
1442 | }, |
---|
1443 | _isInLastRange: function(type, item, isSelected){ |
---|
1444 | // summary: |
---|
1445 | // Return true only when the item is in the last seletion/deseletion range. |
---|
1446 | var start = this[isSelected ? "_lastSelectedAnchorPoint" : "_lastAnchorPoint"][type], |
---|
1447 | end = this[isSelected ? "_lastSelectedEndPoint" : "_lastEndPoint"][type]; |
---|
1448 | if(!item || !start || !end){ return false; } |
---|
1449 | return _inRange(type, item, start, end); |
---|
1450 | }, |
---|
1451 | _isValid: function(type, item, allowNotSelectable){ |
---|
1452 | // summary: |
---|
1453 | // Check whether the item is a valid __SelectItem for the given type. |
---|
1454 | if(!item){ return false; } |
---|
1455 | try{ |
---|
1456 | var g = this.grid, index = item[type]; |
---|
1457 | switch(type){ |
---|
1458 | case "col": |
---|
1459 | return index >= 0 && index < g.layout.cells.length && lang.isArray(item.except) && |
---|
1460 | (allowNotSelectable || !g.layout.cells[index].notselectable); |
---|
1461 | case "row": |
---|
1462 | return index >= 0 && index < g.rowCount && lang.isArray(item.except); |
---|
1463 | case "cell": |
---|
1464 | return item.col >= 0 && item.col < g.layout.cells.length && |
---|
1465 | item.row >= 0 && item.row < g.rowCount && |
---|
1466 | (allowNotSelectable || !g.layout.cells[item.col].notselectable); |
---|
1467 | } |
---|
1468 | }catch(e){} |
---|
1469 | return false; |
---|
1470 | } |
---|
1471 | }); |
---|
1472 | |
---|
1473 | EnhancedGrid.registerPlugin(Selector/*name:'selector'*/, { |
---|
1474 | "dependency": ["autoScroll"] |
---|
1475 | }); |
---|
1476 | |
---|
1477 | return Selector; |
---|
1478 | |
---|
1479 | }); |
---|