Selector.js 46 KB

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