DnD.js 30 KB

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