source: Dev/trunk/src/client/dojox/grid/enhanced/plugins/Selector.js

Last change on this file was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 46.3 KB
Line 
1define([
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/*=====
20declare("__SelectItem", null,{
21        // summary:
22        //              An abstract representation of an item.
23});
24declare("__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});
36declare("__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});
48declare("__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
62var 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        };
119var 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
1473EnhancedGrid.registerPlugin(Selector/*name:'selector'*/, {
1474        "dependency": ["autoScroll"]
1475});
1476
1477return Selector;
1478
1479});
Note: See TracBrowser for help on using the repository browser.