DnD.js 30 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093
  1. define("dojox/grid/enhanced/plugins/DnD", [
  2. "dojo/_base/kernel",
  3. "dojo/_base/declare",
  4. "dojo/_base/connect",
  5. "dojo/_base/array",
  6. "dojo/_base/lang",
  7. "dojo/_base/html",
  8. "dojo/_base/json",
  9. "dojo/_base/window",
  10. "dojo/query",
  11. "dojo/keys",
  12. "dojo/dnd/Source",
  13. "dojo/dnd/Avatar",
  14. "../_Plugin",
  15. "../../EnhancedGrid",
  16. "./Selector",
  17. "./Rearrange",
  18. "dojo/dnd/Manager"
  19. ], function(dojo, declare, connect, array, lang, html, json, win, query, keys, Source, Avatar, _Plugin, EnhancedGrid){
  20. var _devideToArrays = function(a){
  21. a.sort(function(v1, v2){
  22. return v1 - v2;
  23. });
  24. var arr = [[a[0]]];
  25. for(var i = 1, j = 0; i < a.length; ++i){
  26. if(a[i] == a[i-1] + 1){
  27. arr[j].push(a[i]);
  28. }else{
  29. arr[++j] = [a[i]];
  30. }
  31. }
  32. return arr;
  33. },
  34. _joinToArray = function(arrays){
  35. var a = arrays[0];
  36. for(var i = 1; i < arrays.length; ++i){
  37. a = a.concat(arrays[i]);
  38. }
  39. return a;
  40. };
  41. var GridDnDElement = declare("dojox.grid.enhanced.plugins.GridDnDElement", null, {
  42. constructor: function(dndPlugin){
  43. this.plugin = dndPlugin;
  44. this.node = html.create("div");
  45. this._items = {};
  46. },
  47. destroy: function(){
  48. this.plugin = null;
  49. html.destroy(this.node);
  50. this.node = null;
  51. this._items = null;
  52. },
  53. createDnDNodes: function(dndRegion){
  54. this.destroyDnDNodes();
  55. var acceptType = ["grid/" + dndRegion.type + "s"];
  56. var itemNodeIdBase = this.plugin.grid.id + "_dndItem";
  57. array.forEach(dndRegion.selected, function(range, i){
  58. var id = itemNodeIdBase + i;
  59. this._items[id] = {
  60. "type": acceptType,
  61. "data": range,
  62. "dndPlugin": this.plugin
  63. };
  64. this.node.appendChild(html.create("div", {
  65. "id": id
  66. }));
  67. }, this);
  68. },
  69. getDnDNodes: function(){
  70. return array.map(this.node.childNodes, function(node){
  71. return node;
  72. });
  73. },
  74. destroyDnDNodes: function(){
  75. html.empty(this.node);
  76. this._items = {};
  77. },
  78. getItem: function(nodeId){
  79. return this._items[nodeId];
  80. }
  81. });
  82. var GridDnDSource = declare("dojox.grid.enhanced.plugins.GridDnDSource", Source,{
  83. accept: ["grid/cells", "grid/rows", "grid/cols"],
  84. constructor: function(node, param){
  85. this.grid = param.grid;
  86. this.dndElem = param.dndElem;
  87. this.dndPlugin = param.dnd;
  88. this.sourcePlugin = null;
  89. },
  90. destroy: function(){
  91. this.inherited(arguments);
  92. this.grid = null;
  93. this.dndElem = null;
  94. this.dndPlugin = null;
  95. this.sourcePlugin = null;
  96. },
  97. getItem: function(nodeId){
  98. return this.dndElem.getItem(nodeId);
  99. },
  100. checkAcceptance: function(source, nodes){
  101. if(this != source && nodes[0]){
  102. var item = source.getItem(nodes[0].id);
  103. if(item.dndPlugin){
  104. var type = item.type;
  105. for(var j = 0; j < type.length; ++j){
  106. if(type[j] in this.accept){
  107. if(this.dndPlugin._canAccept(item.dndPlugin)){
  108. this.sourcePlugin = item.dndPlugin;
  109. }else{
  110. return false;
  111. }
  112. break;
  113. }
  114. }
  115. }else if("grid/rows" in this.accept){
  116. var rows = [];
  117. array.forEach(nodes, function(node){
  118. var item = source.getItem(node.id);
  119. if(item.data && array.indexOf(item.type, "grid/rows") >= 0){
  120. var rowData = item.data;
  121. if(typeof item.data == "string"){
  122. rowData = json.fromJson(item.data);
  123. }
  124. if(rowData){
  125. rows.push(rowData);
  126. }
  127. }
  128. });
  129. if(rows.length){
  130. this.sourcePlugin = {
  131. _dndRegion: {
  132. type: "row",
  133. selected: [rows]
  134. }
  135. };
  136. }else{
  137. return false;
  138. }
  139. }
  140. }
  141. return this.inherited(arguments);
  142. },
  143. onDraggingOver: function(){
  144. this.dndPlugin.onDraggingOver(this.sourcePlugin);
  145. },
  146. onDraggingOut: function(){
  147. this.dndPlugin.onDraggingOut(this.sourcePlugin);
  148. },
  149. onDndDrop: function(source, nodes, copy, target){
  150. //this.inherited(arguments);
  151. this.onDndCancel();
  152. if(this != source && this == target){
  153. this.dndPlugin.onDragIn(this.sourcePlugin, copy);
  154. }
  155. }
  156. });
  157. var GridDnDAvatar = declare("dojox.grid.enhanced.plugins.GridDnDAvatar", Avatar, {
  158. construct: function(){
  159. // summary:
  160. // constructor function;
  161. // it is separate so it can be (dynamically) overwritten in case of need
  162. this._itemType = this.manager._dndPlugin._dndRegion.type;
  163. this._itemCount = this._getItemCount();
  164. this.isA11y = html.hasClass(win.body(), "dijit_a11y");
  165. var a = html.create("table", {
  166. "border": "0",
  167. "cellspacing": "0",
  168. "class": "dojoxGridDndAvatar",
  169. "style": {
  170. position: "absolute",
  171. zIndex: "1999",
  172. margin: "0px"
  173. }
  174. }),
  175. source = this.manager.source,
  176. b = html.create("tbody", null, a),
  177. tr = html.create("tr", null, b),
  178. td = html.create("td", {
  179. "class": "dojoxGridDnDIcon"
  180. }, tr);
  181. if(this.isA11y){
  182. html.create("span", {
  183. "id" : "a11yIcon",
  184. "innerHTML" : this.manager.copy ? '+' : "<"
  185. }, td);
  186. }
  187. td = html.create("td", {
  188. "class" : "dojoxGridDnDItemIcon " + this._getGridDnDIconClass()
  189. }, tr);
  190. td = html.create("td", null, tr);
  191. html.create("span", {
  192. "class": "dojoxGridDnDItemCount",
  193. "innerHTML": source.generateText ? this._generateText() : ""
  194. }, td);
  195. // we have to set the opacity on IE only after the node is live
  196. html.style(tr, {
  197. "opacity": 0.9
  198. });
  199. this.node = a;
  200. },
  201. _getItemCount: function(){
  202. var selected = this.manager._dndPlugin._dndRegion.selected,
  203. count = 0;
  204. switch(this._itemType){
  205. case "cell":
  206. selected = selected[0];
  207. var cells = this.manager._dndPlugin.grid.layout.cells,
  208. colCount = selected.max.col - selected.min.col + 1,
  209. rowCount = selected.max.row - selected.min.row + 1;
  210. if(colCount > 1){
  211. for(var i = selected.min.col; i <= selected.max.col; ++i){
  212. if(cells[i].hidden){
  213. --colCount;
  214. }
  215. }
  216. }
  217. count = colCount * rowCount;
  218. break;
  219. case "row":
  220. case "col":
  221. count = _joinToArray(selected).length;
  222. }
  223. return count;
  224. },
  225. _getGridDnDIconClass: function(){
  226. return {
  227. "row": ["dojoxGridDnDIconRowSingle", "dojoxGridDnDIconRowMulti"],
  228. "col": ["dojoxGridDnDIconColSingle", "dojoxGridDnDIconColMulti"],
  229. "cell": ["dojoxGridDnDIconCellSingle", "dojoxGridDnDIconCellMulti"]
  230. }[this._itemType][this._itemCount == 1 ? 0 : 1];
  231. },
  232. _generateText: function(){
  233. // summary:
  234. // generates a proper text to reflect copying or moving of items
  235. return "(" + this._itemCount + ")";
  236. }
  237. });
  238. var DnD = declare("dojox.grid.enhanced.plugins.DnD", _Plugin, {
  239. // summary:
  240. // Provide drag and drop for grid columns/rows/cells within grid and out of grid.
  241. // The store of grid must implement dojo.data.api.Write.
  242. // DnD selected columns:
  243. // Support moving within grid, moving/copying out of grid to a non-grid DnD target.
  244. // DnD selected rows:
  245. // Support moving within grid, moving/copying out of grid to any DnD target.
  246. // DnD selected cells (in rectangle shape only):
  247. // Support moving/copying within grid, moving/copying out of grid to any DnD target.
  248. //
  249. // name: String,
  250. // plugin name;
  251. name: "dnd",
  252. _targetAnchorBorderWidth: 2,
  253. _copyOnly: false,
  254. _config: {
  255. "row":{
  256. "within":true,
  257. "in":true,
  258. "out":true
  259. },
  260. "col":{
  261. "within":true,
  262. "in":true,
  263. "out":true
  264. },
  265. "cell":{
  266. "within":true,
  267. "in":true,
  268. "out":true
  269. }
  270. },
  271. constructor: function(grid, args){
  272. this.grid = grid;
  273. this._config = lang.clone(this._config);
  274. args = lang.isObject(args) ? args : {};
  275. this.setupConfig(args.dndConfig);
  276. this._copyOnly = !!args.copyOnly;
  277. //Get the plugins we are dependent on.
  278. this._mixinGrid();
  279. this.selector = grid.pluginMgr.getPlugin("selector");
  280. this.rearranger = grid.pluginMgr.getPlugin("rearrange");
  281. //TODO: waiting for a better plugin framework to pass args to dependent plugins.
  282. this.rearranger.setArgs(args);
  283. //Initialized the components we need.
  284. this._clear();
  285. this._elem = new GridDnDElement(this);
  286. this._source = new GridDnDSource(this._elem.node, {
  287. "grid": grid,
  288. "dndElem": this._elem,
  289. "dnd": this
  290. });
  291. this._container = query(".dojoxGridMasterView", this.grid.domNode)[0];
  292. this._initEvents();
  293. },
  294. destroy: function(){
  295. this.inherited(arguments);
  296. this._clear();
  297. this._source.destroy();
  298. this._elem.destroy();
  299. this._container = null;
  300. this.grid = null;
  301. this.selector = null;
  302. this.rearranger = null;
  303. this._config = null;
  304. },
  305. _mixinGrid: function(){
  306. // summary:
  307. // Provide APIs for grid.
  308. this.grid.setupDnDConfig = lang.hitch(this, "setupConfig");
  309. this.grid.dndCopyOnly = lang.hitch(this, "copyOnly");
  310. },
  311. setupConfig: function(config){
  312. // summary:
  313. // Configure which DnD functionalities are needed.
  314. // Combination of any item from type set ("row", "col", "cell")
  315. // and any item from mode set("within", "in", "out") is configurable.
  316. //
  317. // "row", "col", "cell" are straitforward, while the other 3 are explained below:
  318. // "within": DnD within grid, that is, column/row reordering and cell moving/copying.
  319. // "in": Whether allowed to accept rows/cells (currently not support columns) from another grid.
  320. // "out": Whether allowed to drag out of grid, to another grid or even to any other DnD target.
  321. //
  322. // If not provided in the config, will use the default.
  323. // When declared together, Mode set has higher priority than type set.
  324. // config: Object
  325. // DnD configuration object.
  326. // See the examples below.
  327. // example:
  328. // The following code disables row DnD within grid,
  329. // but still can drag rows out of grid or drag rows from other gird.
  330. // | setUpConfig({
  331. // | "row": {
  332. // | "within": false
  333. // | }
  334. // | });
  335. //
  336. // The opposite way is also okay:
  337. // | setUpConfig({
  338. // | "within": {
  339. // | "row": false
  340. // | }
  341. // | });
  342. //
  343. // And if you'd like to disable/enable a whole set, here's a shortcut:
  344. // | setUpConfig({
  345. // | "cell", true,
  346. // | "out": false
  347. // | });
  348. //
  349. // Because mode has higher priority than type, the following will disable row dnd within grid:
  350. // | setUpConfig({
  351. // | "within", {
  352. // | "row": false;
  353. // | },
  354. // | "row", {
  355. // | "within": true
  356. // | }
  357. // | });
  358. if(config && lang.isObject(config)){
  359. var firstLevel = ["row", "col", "cell"],
  360. secondLevel = ["within", "in", "out"],
  361. cfg = this._config;
  362. array.forEach(firstLevel, function(type){
  363. if(type in config){
  364. var t = config[type];
  365. if(t && lang.isObject(t)){
  366. array.forEach(secondLevel, function(mode){
  367. if(mode in t){
  368. cfg[type][mode] = !!t[mode];
  369. }
  370. });
  371. }else{
  372. array.forEach(secondLevel, function(mode){
  373. cfg[type][mode] = !!t;
  374. });
  375. }
  376. }
  377. });
  378. array.forEach(secondLevel, function(mode){
  379. if(mode in config){
  380. var m = config[mode];
  381. if(m && lang.isObject(m)){
  382. array.forEach(firstLevel, function(type){
  383. if(type in m){
  384. cfg[type][mode] = !!m[type];
  385. }
  386. });
  387. }else{
  388. array.forEach(firstLevel, function(type){
  389. cfg[type][mode] = !!m;
  390. });
  391. }
  392. }
  393. });
  394. }
  395. },
  396. copyOnly: function(isCopyOnly){
  397. // summary:
  398. // Setter/getter of this._copyOnly.
  399. if(typeof isCopyOnly != "undefined"){
  400. this._copyOnly = !!isCopyOnly;
  401. }
  402. return this._copyOnly;
  403. },
  404. _isOutOfGrid: function(evt){
  405. var gridPos = html.position(this.grid.domNode), x = evt.clientX, y = evt.clientY;
  406. return y < gridPos.y || y > gridPos.y + gridPos.h ||
  407. x < gridPos.x || x > gridPos.x + gridPos.w;
  408. },
  409. _onMouseMove: function(evt){
  410. if(this._dndRegion && !this._dnding && !this._externalDnd){
  411. this._dnding = true;
  412. this._startDnd(evt);
  413. }else{
  414. if(this._isMouseDown && !this._dndRegion){
  415. delete this._isMouseDown;
  416. this._oldCursor = html.style(win.body(), "cursor");
  417. html.style(win.body(), "cursor", "not-allowed");
  418. }
  419. //TODO: should implement as mouseenter/mouseleave
  420. //But we have an avatar under mouse when dnd, and this will cause a lot of mouseenter in FF.
  421. var isOut = this._isOutOfGrid(evt);
  422. if(!this._alreadyOut && isOut){
  423. this._alreadyOut = true;
  424. if(this._dnding){
  425. this._destroyDnDUI(true, false);
  426. }
  427. this._moveEvent = evt;
  428. this._source.onOutEvent();
  429. }else if(this._alreadyOut && !isOut){
  430. this._alreadyOut = false;
  431. if(this._dnding){
  432. this._createDnDUI(evt, true);
  433. }
  434. this._moveEvent = evt;
  435. this._source.onOverEvent();
  436. }
  437. }
  438. },
  439. _onMouseUp: function(){
  440. if(!this._extDnding && !this._isSource){
  441. var isInner = this._dnding && !this._alreadyOut;
  442. if(isInner && this._config[this._dndRegion.type]["within"]){
  443. this._rearrange();
  444. }
  445. this._endDnd(isInner);
  446. }
  447. html.style(win.body(), "cursor", this._oldCursor || "");
  448. delete this._isMouseDown;
  449. },
  450. _initEvents: function(){
  451. var g = this.grid, s = this.selector;
  452. this.connect(win.doc, "onmousemove", "_onMouseMove");
  453. this.connect(win.doc, "onmouseup", "_onMouseUp");
  454. this.connect(g, "onCellMouseOver", function(evt){
  455. if(!this._dnding && !s.isSelecting() && !evt.ctrlKey){
  456. this._dndReady = s.isSelected("cell", evt.rowIndex, evt.cell.index);
  457. s.selectEnabled(!this._dndReady);
  458. }
  459. });
  460. this.connect(g, "onHeaderCellMouseOver", function(evt){
  461. if(this._dndReady){
  462. s.selectEnabled(true);
  463. }
  464. });
  465. this.connect(g, "onRowMouseOver", function(evt){
  466. if(this._dndReady && !evt.cell){
  467. s.selectEnabled(true);
  468. }
  469. });
  470. this.connect(g, "onCellMouseDown", function(evt){
  471. if(!evt.ctrlKey && this._dndReady){
  472. this._dndRegion = this._getDnDRegion(evt.rowIndex, evt.cell.index);
  473. this._isMouseDown = true;
  474. }
  475. });
  476. this.connect(g, "onCellMouseUp", function(evt){
  477. if(!this._dndReady && !s.isSelecting() && evt.cell){
  478. this._dndReady = s.isSelected("cell", evt.rowIndex, evt.cell.index);
  479. s.selectEnabled(!this._dndReady);
  480. }
  481. });
  482. this.connect(g, "onCellClick", function(evt){
  483. if(this._dndReady && !evt.ctrlKey && !evt.shiftKey){
  484. s.select("cell", evt.rowIndex, evt.cell.index);
  485. }
  486. });
  487. this.connect(g, "onEndAutoScroll", function(isVertical, isForward, view, target, evt){
  488. if(this._dnding){
  489. this._markTargetAnchor(evt);
  490. }
  491. });
  492. this.connect(win.doc, "onkeydown", function(evt){
  493. if(evt.keyCode == keys.ESCAPE){
  494. this._endDnd(false);
  495. }else if(evt.keyCode == keys.CTRL){
  496. s.selectEnabled(true);
  497. this._isCopy = true;
  498. }
  499. });
  500. this.connect(win.doc, "onkeyup", function(evt){
  501. if(evt.keyCode == keys.CTRL){
  502. s.selectEnabled(!this._dndReady);
  503. this._isCopy = false;
  504. }
  505. });
  506. },
  507. _clear: function(){
  508. this._dndRegion = null;
  509. this._target = null;
  510. this._moveEvent = null;
  511. this._targetAnchor = {};
  512. this._dnding = false;
  513. this._externalDnd = false;
  514. this._isSource = false;
  515. this._alreadyOut = false;
  516. this._extDnding = false;
  517. },
  518. _getDnDRegion: function(rowIndex, colIndex){
  519. var s = this.selector,
  520. selected = s._selected,
  521. flag = (!!selected.cell.length) | (!!selected.row.length << 1) | (!!selected.col.length << 2),
  522. type;
  523. switch(flag){
  524. case 1:
  525. type = "cell";
  526. if(!this._config[type]["within"] && !this._config[type]["out"]){
  527. return null;
  528. }
  529. var cells = this.grid.layout.cells,
  530. getCount = function(range){
  531. var hiddenColCnt = 0;
  532. for(var i = range.min.col; i <= range.max.col; ++i){
  533. if(cells[i].hidden){
  534. ++hiddenColCnt;
  535. }
  536. }
  537. return (range.max.row - range.min.row + 1) * (range.max.col - range.min.col + 1 - hiddenColCnt);
  538. },
  539. inRange = function(item, range){
  540. return item.row >= range.min.row && item.row <= range.max.row &&
  541. item.col >= range.min.col && item.col <= range.max.col;
  542. },
  543. range = {
  544. max: {
  545. row: -1,
  546. col: -1
  547. },
  548. min: {
  549. row: Infinity,
  550. col: Infinity
  551. }
  552. };
  553. array.forEach(selected[type], function(item){
  554. if(item.row < range.min.row){
  555. range.min.row = item.row;
  556. }
  557. if(item.row > range.max.row){
  558. range.max.row = item.row;
  559. }
  560. if(item.col < range.min.col){
  561. range.min.col = item.col;
  562. }
  563. if(item.col > range.max.col){
  564. range.max.col = item.col;
  565. }
  566. });
  567. if(array.some(selected[type], function(item){
  568. return item.row == rowIndex && item.col == colIndex;
  569. })){
  570. if(getCount(range) == selected[type].length && array.every(selected[type], function(item){
  571. return inRange(item, range);
  572. })){
  573. return {
  574. "type": type,
  575. "selected": [range],
  576. "handle": {
  577. "row": rowIndex,
  578. "col": colIndex
  579. }
  580. };
  581. }
  582. }
  583. return null;
  584. case 2: case 4:
  585. type = flag == 2 ? "row" : "col";
  586. if(!this._config[type]["within"] && !this._config[type]["out"]){
  587. return null;
  588. }
  589. var res = s.getSelected(type);
  590. if(res.length){
  591. return {
  592. "type": type,
  593. "selected": _devideToArrays(res),
  594. "handle": flag == 2 ? rowIndex : colIndex
  595. };
  596. }
  597. return null;
  598. }
  599. return null;
  600. },
  601. _startDnd: function(evt){
  602. this._createDnDUI(evt);
  603. },
  604. _endDnd: function(destroySource){
  605. this._destroyDnDUI(false, destroySource);
  606. this._clear();
  607. },
  608. _createDnDUI: function(evt, isMovingIn){
  609. //By default the master view of grid do not have height, because the children in it are all positioned absolutely.
  610. //But we need it to contain avatars.
  611. var viewPos = html.position(this.grid.views.views[0].domNode);
  612. html.style(this._container, "height", viewPos.h + "px");
  613. try{
  614. //If moving in from out side, dnd source is already created.
  615. if(!isMovingIn){
  616. this._createSource(evt);
  617. }
  618. this._createMoveable(evt);
  619. this._oldCursor = html.style(win.body(), "cursor");
  620. html.style(win.body(), "cursor", "default");
  621. }catch(e){
  622. console.warn("DnD._createDnDUI() error:", e);
  623. }
  624. },
  625. _destroyDnDUI: function(isMovingOut, destroySource){
  626. try{
  627. if(destroySource){
  628. this._destroySource();
  629. }
  630. this._unmarkTargetAnchor();
  631. if(!isMovingOut){
  632. this._destroyMoveable();
  633. }
  634. html.style(win.body(), "cursor", this._oldCursor);
  635. }catch(e){
  636. console.warn("DnD._destroyDnDUI() error:", this.grid.id, e);
  637. }
  638. },
  639. _createSource: function(evt){
  640. this._elem.createDnDNodes(this._dndRegion);
  641. var m = dojo.dnd.manager();
  642. var oldMakeAvatar = m.makeAvatar;
  643. m._dndPlugin = this;
  644. m.makeAvatar = function(){
  645. var avatar = new GridDnDAvatar(m);
  646. delete m._dndPlugin;
  647. return avatar;
  648. };
  649. m.startDrag(this._source, this._elem.getDnDNodes(), evt.ctrlKey);
  650. m.makeAvatar = oldMakeAvatar;
  651. m.onMouseMove(evt);
  652. },
  653. _destroySource: function(){
  654. connect.publish("/dnd/cancel");
  655. },
  656. _createMoveable: function(evt){
  657. if(!this._markTagetAnchorHandler){
  658. this._markTagetAnchorHandler = this.connect(win.doc, "onmousemove", "_markTargetAnchor");
  659. }
  660. },
  661. _destroyMoveable: function(){
  662. this.disconnect(this._markTagetAnchorHandler);
  663. delete this._markTagetAnchorHandler;
  664. },
  665. _calcColTargetAnchorPos: function(evt, containerPos){
  666. // summary:
  667. // Calculate the position of the column DnD avatar
  668. var i, headPos, left, target, ex = evt.clientX,
  669. cells = this.grid.layout.cells,
  670. ltr = html._isBodyLtr(),
  671. headers = this._getVisibleHeaders();
  672. for(i = 0; i < headers.length; ++i){
  673. headPos = html.position(headers[i].node);
  674. if(ltr ? ((i === 0 || ex >= headPos.x) && ex < headPos.x + headPos.w) :
  675. ((i === 0 || ex < headPos.x + headPos.w) && ex >= headPos.x)){
  676. left = headPos.x + (ltr ? 0 : headPos.w);
  677. break;
  678. }else if(ltr ? (i === headers.length - 1 && ex >= headPos.x + headPos.w) :
  679. (i === headers.length - 1 && ex < headPos.x)){
  680. ++i;
  681. left = headPos.x + (ltr ? headPos.w : 0);
  682. break;
  683. }
  684. }
  685. if(i < headers.length){
  686. target = headers[i].cell.index;
  687. if(this.selector.isSelected("col", target) && this.selector.isSelected("col", target - 1)){
  688. var ranges = this._dndRegion.selected;
  689. for(i = 0; i < ranges.length; ++i){
  690. if(array.indexOf(ranges[i], target) >= 0){
  691. target = ranges[i][0];
  692. headPos = html.position(cells[target].getHeaderNode());
  693. left = headPos.x + (ltr ? 0 : headPos.w);
  694. break;
  695. }
  696. }
  697. }
  698. }else{
  699. target = cells.length;
  700. }
  701. this._target = target;
  702. return left - containerPos.x;
  703. },
  704. _calcRowTargetAnchorPos: function(evt, containerPos){
  705. // summary:
  706. // Calculate the position of the row DnD avatar
  707. var g = this.grid, top, i = 0,
  708. cells = g.layout.cells;
  709. while(cells[i].hidden){ ++i; }
  710. var cell = g.layout.cells[i],
  711. rowIndex = g.scroller.firstVisibleRow,
  712. cellNode = cell.getNode(rowIndex);
  713. if(!cellNode){
  714. //if the target grid is empty, set to -1
  715. //which will be processed in Rearrange
  716. this._target = -1;
  717. return 0; //position of the insert bar
  718. }
  719. var nodePos = html.position(cellNode);
  720. while(nodePos.y + nodePos.h < evt.clientY){
  721. if(++rowIndex >= g.rowCount){
  722. break;
  723. }
  724. nodePos = html.position(cell.getNode(rowIndex));
  725. }
  726. if(rowIndex < g.rowCount){
  727. if(this.selector.isSelected("row", rowIndex) && this.selector.isSelected("row", rowIndex - 1)){
  728. var ranges = this._dndRegion.selected;
  729. for(i = 0; i < ranges.length; ++i){
  730. if(array.indexOf(ranges[i], rowIndex) >= 0){
  731. rowIndex = ranges[i][0];
  732. nodePos = html.position(cell.getNode(rowIndex));
  733. break;
  734. }
  735. }
  736. }
  737. top = nodePos.y;
  738. }else{
  739. top = nodePos.y + nodePos.h;
  740. }
  741. this._target = rowIndex;
  742. return top - containerPos.y;
  743. },
  744. _calcCellTargetAnchorPos: function(evt, containerPos, targetAnchor){
  745. // summary:
  746. // Calculate the position of the cell DnD avatar
  747. var s = this._dndRegion.selected[0],
  748. origin = this._dndRegion.handle,
  749. g = this.grid, ltr = html._isBodyLtr(),
  750. cells = g.layout.cells, headPos,
  751. minPos, maxPos, headers,
  752. height, width, left, top,
  753. minCol, maxCol, i,
  754. preSpan = origin.col - s.min.col,
  755. postSpan = s.max.col - origin.col,
  756. leftTopDiv, rightBottomDiv;
  757. if(!targetAnchor.childNodes.length){
  758. leftTopDiv = html.create("div", {
  759. "class": "dojoxGridCellBorderLeftTopDIV"
  760. }, targetAnchor);
  761. rightBottomDiv = html.create("div", {
  762. "class": "dojoxGridCellBorderRightBottomDIV"
  763. }, targetAnchor);
  764. }else{
  765. leftTopDiv = query(".dojoxGridCellBorderLeftTopDIV", targetAnchor)[0];
  766. rightBottomDiv = query(".dojoxGridCellBorderRightBottomDIV", targetAnchor)[0];
  767. }
  768. for(i = s.min.col + 1; i < origin.col; ++i){
  769. if(cells[i].hidden){
  770. --preSpan;
  771. }
  772. }
  773. for(i = origin.col + 1; i < s.max.col; ++i){
  774. if(cells[i].hidden){
  775. --postSpan;
  776. }
  777. }
  778. headers = this._getVisibleHeaders();
  779. //calc width
  780. for(i = preSpan; i < headers.length - postSpan; ++i){
  781. headPos = html.position(headers[i].node);
  782. if((evt.clientX >= headPos.x && evt.clientX < headPos.x + headPos.w) || //within in this column
  783. //prior to this column, but within range
  784. (i == preSpan && (ltr ? evt.clientX < headPos.x : evt.clientX >= headPos.x + headPos.w)) ||
  785. //post to this column, but within range
  786. (i == headers.length - postSpan - 1 && (ltr ? evt.clientX >= headPos.x + headPos.w : evt < headPos.x))){
  787. minCol = headers[i - preSpan];
  788. maxCol = headers[i + postSpan];
  789. minPos = html.position(minCol.node);
  790. maxPos = html.position(maxCol.node);
  791. minCol = minCol.cell.index;
  792. maxCol = maxCol.cell.index;
  793. left = ltr ? minPos.x : maxPos.x;
  794. width = ltr ? (maxPos.x + maxPos.w - minPos.x) : (minPos.x + minPos.w - maxPos.x);
  795. break;
  796. }
  797. }
  798. //calc height
  799. i = 0;
  800. while(cells[i].hidden){ ++i; }
  801. var cell = cells[i],
  802. rowIndex = g.scroller.firstVisibleRow,
  803. nodePos = html.position(cell.getNode(rowIndex));
  804. while(nodePos.y + nodePos.h < evt.clientY){
  805. if(++rowIndex < g.rowCount){
  806. nodePos = html.position(cell.getNode(rowIndex));
  807. }else{
  808. break;
  809. }
  810. }
  811. var minRow = rowIndex >= origin.row - s.min.row ? rowIndex - origin.row + s.min.row : 0;
  812. var maxRow = minRow + s.max.row - s.min.row;
  813. if(maxRow >= g.rowCount){
  814. maxRow = g.rowCount - 1;
  815. minRow = maxRow - s.max.row + s.min.row;
  816. }
  817. minPos = html.position(cell.getNode(minRow));
  818. maxPos = html.position(cell.getNode(maxRow));
  819. top = minPos.y;
  820. height = maxPos.y + maxPos.h - minPos.y;
  821. this._target = {
  822. "min":{
  823. "row": minRow,
  824. "col": minCol
  825. },
  826. "max":{
  827. "row": maxRow,
  828. "col": maxCol
  829. }
  830. };
  831. var anchorBorderSize = (html.marginBox(leftTopDiv).w - html.contentBox(leftTopDiv).w) / 2;
  832. var leftTopCellPos = html.position(cells[minCol].getNode(minRow));
  833. html.style(leftTopDiv, {
  834. "width": (leftTopCellPos.w - anchorBorderSize) + "px",
  835. "height": (leftTopCellPos.h - anchorBorderSize) + "px"
  836. });
  837. var rightBottomCellPos = html.position(cells[maxCol].getNode(maxRow));
  838. html.style(rightBottomDiv, {
  839. "width": (rightBottomCellPos.w - anchorBorderSize) + "px",
  840. "height": (rightBottomCellPos.h - anchorBorderSize) + "px"
  841. });
  842. return {
  843. h: height,
  844. w: width,
  845. l: left - containerPos.x,
  846. t: top - containerPos.y
  847. };
  848. },
  849. _markTargetAnchor: function(evt){
  850. try{
  851. var t = this._dndRegion.type;
  852. if(this._alreadyOut || (this._dnding && !this._config[t]["within"]) || (this._extDnding && !this._config[t]["in"])){
  853. return;
  854. }
  855. var height, width, left, top,
  856. targetAnchor = this._targetAnchor[t],
  857. pos = html.position(this._container);
  858. if(!targetAnchor){
  859. targetAnchor = this._targetAnchor[t] = html.create("div", {
  860. "class": (t == "cell") ? "dojoxGridCellBorderDIV" : "dojoxGridBorderDIV"
  861. });
  862. html.style(targetAnchor, "display", "none");
  863. this._container.appendChild(targetAnchor);
  864. }
  865. switch(t){
  866. case "col":
  867. height = pos.h;
  868. width = this._targetAnchorBorderWidth;
  869. left = this._calcColTargetAnchorPos(evt, pos);
  870. top = 0;
  871. break;
  872. case "row":
  873. height = this._targetAnchorBorderWidth;
  874. width = pos.w;
  875. left = 0;
  876. top = this._calcRowTargetAnchorPos(evt, pos);
  877. break;
  878. case "cell":
  879. var cellPos = this._calcCellTargetAnchorPos(evt, pos, targetAnchor);
  880. height = cellPos.h;
  881. width = cellPos.w;
  882. left = cellPos.l;
  883. top = cellPos.t;
  884. }
  885. if(typeof height == "number" && typeof width == "number" && typeof left == "number" && typeof top == "number"){
  886. html.style(targetAnchor, {
  887. "height": height + "px",
  888. "width": width + "px",
  889. "left": left + "px",
  890. "top": top + "px"
  891. });
  892. html.style(targetAnchor, "display", "");
  893. }else{
  894. this._target = null;
  895. }
  896. }catch(e){
  897. console.warn("DnD._markTargetAnchor() error:",e);
  898. }
  899. },
  900. _unmarkTargetAnchor: function(){
  901. if(this._dndRegion){
  902. var targetAnchor = this._targetAnchor[this._dndRegion.type];
  903. if(targetAnchor){
  904. html.style(this._targetAnchor[this._dndRegion.type], "display", "none");
  905. }
  906. }
  907. },
  908. _getVisibleHeaders: function(){
  909. return array.map(array.filter(this.grid.layout.cells, function(cell){
  910. return !cell.hidden;
  911. }), function(cell){
  912. return {
  913. "node": cell.getHeaderNode(),
  914. "cell": cell
  915. };
  916. });
  917. },
  918. _rearrange: function(){
  919. if(this._target === null){
  920. return;
  921. }
  922. var t = this._dndRegion.type;
  923. var ranges = this._dndRegion.selected;
  924. if(t === "cell"){
  925. this.rearranger[(this._isCopy || this._copyOnly) ? "copyCells" : "moveCells"](ranges[0], this._target === -1 ? null : this._target);
  926. }else{
  927. this.rearranger[t == "col" ? "moveColumns" : "moveRows"](_joinToArray(ranges), this._target === -1 ? null: this._target);
  928. }
  929. this._target = null;
  930. },
  931. onDraggingOver: function(sourcePlugin){
  932. if(!this._dnding && sourcePlugin){
  933. sourcePlugin._isSource = true;
  934. this._extDnding = true;
  935. if(!this._externalDnd){
  936. this._externalDnd = true;
  937. this._dndRegion = this._mapRegion(sourcePlugin.grid, sourcePlugin._dndRegion);
  938. }
  939. this._createDnDUI(this._moveEvent,true);
  940. this.grid.pluginMgr.getPlugin("autoScroll").readyForAutoScroll = true;
  941. }
  942. },
  943. _mapRegion: function(srcGrid, dndRegion){
  944. if(dndRegion.type === "cell"){
  945. var srcRange = dndRegion.selected[0];
  946. var cells = this.grid.layout.cells;
  947. var srcCells = srcGrid.layout.cells;
  948. var c, cnt = 0;
  949. for(c = srcRange.min.col; c <= srcRange.max.col; ++c){
  950. if(!srcCells[c].hidden){
  951. ++cnt;
  952. }
  953. }
  954. for(c = 0; cnt > 0; ++c){
  955. if(!cells[c].hidden){
  956. --cnt;
  957. }
  958. }
  959. var region = lang.clone(dndRegion);
  960. region.selected[0].min.col = 0;
  961. region.selected[0].max.col = c - 1;
  962. for(c = srcRange.min.col; c <= dndRegion.handle.col; ++c){
  963. if(!srcCells[c].hidden){
  964. ++cnt;
  965. }
  966. }
  967. for(c = 0; cnt > 0; ++c){
  968. if(!cells[c].hidden){
  969. --cnt;
  970. }
  971. }
  972. region.handle.col = c;
  973. }
  974. return dndRegion;
  975. },
  976. onDraggingOut: function(sourcePlugin){
  977. if(this._externalDnd){
  978. this._extDnding = false;
  979. this._destroyDnDUI(true, false);
  980. if(sourcePlugin){
  981. sourcePlugin._isSource = false;
  982. }
  983. }
  984. },
  985. onDragIn: function(sourcePlugin, isCopy){
  986. var success = false;
  987. if(this._target !== null){
  988. var type = sourcePlugin._dndRegion.type;
  989. var ranges = sourcePlugin._dndRegion.selected;
  990. switch(type){
  991. case "cell":
  992. this.rearranger.changeCells(sourcePlugin.grid, ranges[0], this._target);
  993. break;
  994. case "row":
  995. var range = _joinToArray(ranges);
  996. this.rearranger.insertRows(sourcePlugin.grid, range, this._target);
  997. break;
  998. }
  999. success = true;
  1000. }
  1001. this._endDnd(true);
  1002. if(sourcePlugin.onDragOut){
  1003. sourcePlugin.onDragOut(success && !isCopy);
  1004. }
  1005. },
  1006. onDragOut: function(isMove){
  1007. if(isMove && !this._copyOnly){
  1008. var type = this._dndRegion.type;
  1009. var ranges = this._dndRegion.selected;
  1010. switch(type){
  1011. case "cell":
  1012. this.rearranger.clearCells(ranges[0]);
  1013. break;
  1014. case "row":
  1015. this.rearranger.removeRows(_joinToArray(ranges));
  1016. break;
  1017. }
  1018. }
  1019. this._endDnd(true);
  1020. },
  1021. _canAccept: function(sourcePlugin){
  1022. if(!sourcePlugin){
  1023. return false;
  1024. }
  1025. var srcRegion = sourcePlugin._dndRegion;
  1026. var type = srcRegion.type;
  1027. if(!this._config[type]["in"] || !sourcePlugin._config[type]["out"]){
  1028. return false;
  1029. }
  1030. var g = this.grid;
  1031. var ranges = srcRegion.selected;
  1032. var colCnt = array.filter(g.layout.cells, function(cell){
  1033. return !cell.hidden;
  1034. }).length;
  1035. var rowCnt = g.rowCount;
  1036. var res = true;
  1037. switch(type){
  1038. case "cell":
  1039. ranges = ranges[0];
  1040. res = g.store.getFeatures()["dojo.data.api.Write"] &&
  1041. (ranges.max.row - ranges.min.row) <= rowCnt &&
  1042. array.filter(sourcePlugin.grid.layout.cells, function(cell){
  1043. return cell.index >= ranges.min.col && cell.index <= ranges.max.col && !cell.hidden;
  1044. }).length <= colCnt;
  1045. //intentional drop through - don't break
  1046. case "row":
  1047. if(sourcePlugin._allDnDItemsLoaded()){
  1048. return res;
  1049. }
  1050. }
  1051. return false;
  1052. },
  1053. _allDnDItemsLoaded: function(){
  1054. if(this._dndRegion){
  1055. var type = this._dndRegion.type,
  1056. ranges = this._dndRegion.selected,
  1057. rows = [];
  1058. switch(type){
  1059. case "cell":
  1060. for(var i = ranges[0].min.row, max = ranges[0].max.row; i <= max; ++i){
  1061. rows.push(i);
  1062. }
  1063. break;
  1064. case "row":
  1065. rows = _joinToArray(ranges);
  1066. break;
  1067. default:
  1068. return false;
  1069. }
  1070. var cache = this.grid._by_idx;
  1071. return array.every(rows, function(rowIndex){
  1072. return !!cache[rowIndex];
  1073. });
  1074. }
  1075. return false;
  1076. }
  1077. });
  1078. EnhancedGrid.registerPlugin(DnD/*name:'dnd'*/, {
  1079. "dependency": ["selector", "rearrange"]
  1080. });
  1081. return DnD;
  1082. });