PlottedDnd.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  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.layout.dnd.PlottedDnd"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dojox.layout.dnd.PlottedDnd"] = true;
  8. dojo.provide("dojox.layout.dnd.PlottedDnd");
  9. dojo.require("dojo.dnd.Source");
  10. dojo.require("dojo.dnd.Manager");
  11. dojo.require("dojox.layout.dnd.Avatar");
  12. dojo.declare("dojox.layout.dnd.PlottedDnd", [dojo.dnd.Source], {
  13. // summary:
  14. // dnd source handling plotted zone to show the dropping area
  15. GC_OFFSET_X: dojo.dnd.manager().OFFSET_X,
  16. GC_OFFSET_Y: dojo.dnd.manager().OFFSET_Y,
  17. constructor: function(/*Node*/node, /*Object*/params){
  18. this.childBoxes = null;
  19. this.dropIndicator = new dojox.layout.dnd.DropIndicator("dndDropIndicator", "div");
  20. this.withHandles = params.withHandles;
  21. this.handleClasses = params.handleClasses;
  22. this.opacity = params.opacity;
  23. this.allowAutoScroll = params.allowAutoScroll;//MODIF MYS
  24. this.dom = params.dom;
  25. this.singular = true;
  26. this.skipForm = true;
  27. this._over = false;
  28. this.defaultHandleClass = "GcDndHandle";
  29. this.isDropped = false;
  30. this._timer = null;
  31. //Initialize the params to calculate offset
  32. this.isOffset = (params.isOffset)?true:false;
  33. this.offsetDrag = (params.offsetDrag) ? params.offsetDrag : {x:0,y:0};
  34. this.hideSource = params.hideSource ? params.hideSource : true;
  35. this._drop = this.dropIndicator.create();
  36. },
  37. _calculateCoords : function(/*Boolean*/height){
  38. // summary: Calculate each position of children
  39. dojo.forEach(this.node.childNodes, function(child){
  40. var c = dojo.coords(child, true);
  41. child.coords = {
  42. xy: c,
  43. w: child.offsetWidth / 2,
  44. h: child.offsetHeight / 2,
  45. mw: c.w
  46. };
  47. if(height){
  48. child.coords.mh = c.h;
  49. }
  50. }, this);
  51. },
  52. _legalMouseDown: function(/*Event*/e){
  53. // summary: Checks if user clicked on "approved" items.
  54. if(!this.withHandles){ return true; }
  55. for(var node = (e.target); node && node != this.node; node = node.parentNode){
  56. if(dojo.hasClass(node, this.defaultHandleClass)){
  57. return true;
  58. }
  59. }
  60. return false; // Boolean
  61. },
  62. setDndItemSelectable: function(/*Node*/node, /*Boolean*/isSelectable) {
  63. // summary: set an item as selectable
  64. for(var _node = node; _node && node != this.node; _node = _node.parentNode) {
  65. if (dojo.hasClass(_node,"dojoDndItem")) {
  66. dojo.setSelectable(_node, isSelectable);
  67. return;
  68. }
  69. }
  70. },
  71. getDraggedWidget: function(/*Node*/node) {
  72. // summary: Return one or more widget selected during the drag.
  73. var _node = node;
  74. while (_node && _node.nodeName.toLowerCase()!="body" && !dojo.hasClass(_node,"dojoDndItem")) {
  75. _node = _node.parentNode;
  76. }
  77. return (_node) ? dijit.byNode(_node) : null;
  78. },
  79. isAccepted: function(/*Node*/ node) {
  80. // summary: test if this node can be accepted
  81. var _dndType = (node) ? node.getAttribute("dndtype") : null;
  82. return (_dndType && _dndType in this.accept);
  83. },
  84. onDndStart:function(/*Object*/source, /*Array*/nodes, /*Object*/copy){
  85. // summary: Called to initiate the DnD operation.
  86. this.firstIndicator = (source == this);
  87. this._calculateCoords(true);
  88. //this.isDropped = true;
  89. var m = dojo.dnd.manager();
  90. if(nodes[0].coords){
  91. this._drop.style.height = nodes[0].coords.mh + "px";
  92. dojo.style(m.avatar.node, "width", nodes[0].coords.mw + "px");
  93. }else{
  94. this._drop.style.height = m.avatar.node.clientHeight+"px";
  95. }
  96. this.dndNodes = nodes;
  97. dojox.layout.dnd.PlottedDnd.superclass.onDndStart.call(this,source, nodes, copy);
  98. if(source == this && this.hideSource){
  99. dojo.forEach(nodes, function(n){
  100. dojo.style(n, "display","none");
  101. });
  102. }
  103. },
  104. onDndCancel:function(){
  105. // summary: Called to cancel the DnD operation.
  106. var m = dojo.dnd.manager();
  107. if(m.source == this && this.hideSource){
  108. var nodes = this.getSelectedNodes();
  109. dojo.forEach(nodes, function(n){
  110. dojo.style(n, "display","");
  111. });
  112. }
  113. dojox.layout.dnd.PlottedDnd.superclass.onDndCancel.call(this);
  114. this.deleteDashedZone();
  115. },
  116. onDndDrop: function(source,nodes,copy,target) {
  117. // summary: Called to finish the DnD operation
  118. try{
  119. if(!this.isAccepted(nodes[0])){
  120. this.onDndCancel();
  121. }else{
  122. if(source == this && this._over && this.dropObject){
  123. this.current = this.dropObject.c;
  124. }
  125. dojox.layout.dnd.PlottedDnd.superclass.onDndDrop.call(this, source, nodes, copy, target);
  126. this._calculateCoords(true);
  127. }
  128. }catch(e){
  129. console.warn(e);
  130. }
  131. },
  132. onMouseDown: function(/*Event*/e) {
  133. // summary: Event processor for onmousedown.
  134. if(this.current == null){
  135. this.selection = {};
  136. }else{
  137. if(this.current == this.anchor){
  138. this.anchor = null;
  139. }
  140. }
  141. if(this.current !== null){
  142. var c = dojo.coords(this.current, true);
  143. this.current.coords = {
  144. xy: c,
  145. w: this.current.offsetWidth / 2,
  146. h: this.current.offsetHeight / 2,
  147. mh: c.h,
  148. mw: c.w
  149. };
  150. this._drop.style.height = this.current.coords.mh + "px";
  151. if(this.isOffset){
  152. if(this.offsetDrag.x == 0 && this.offsetDrag.y == 0){
  153. var NoOffsetDrag = true;
  154. var coords = dojo.coords(this._getChildByEvent(e));
  155. this.offsetDrag.x = coords.x - e.pageX;
  156. this.offsetDrag.y = coords.y - e.clientY;
  157. }
  158. if(this.offsetDrag.y < 16 && this.current != null){
  159. this.offsetDrag.y = this.GC_OFFSET_Y;
  160. }
  161. var m = dojo.dnd.manager();
  162. m.OFFSET_X = this.offsetDrag.x;
  163. m.OFFSET_Y = this.offsetDrag.y;
  164. if (NoOffsetDrag) {
  165. this.offsetDrag.x = 0;
  166. this.offsetDrag.y = 0;
  167. }
  168. }
  169. }
  170. if(dojo.dnd.isFormElement(e)){
  171. this.setDndItemSelectable(e.target,true);
  172. }else{
  173. this.containerSource = true;
  174. var _draggedWidget = this.getDraggedWidget(e.target);
  175. if(_draggedWidget && _draggedWidget.dragRestriction){
  176. // FIXME: not clear what this was supposed to mean ... this code needs more cleanups.
  177. // dragRestriction = true;
  178. }else{
  179. dojox.layout.dnd.PlottedDnd.superclass.onMouseDown.call(this,e);
  180. }
  181. }
  182. },
  183. onMouseUp: function(/*Event*/e) {
  184. // summary: Event processor for onmouseup.
  185. dojox.layout.dnd.PlottedDnd.superclass.onMouseUp.call(this,e);
  186. this.containerSource = false;
  187. if (!dojo.isIE && this.mouseDown){
  188. this.setDndItemSelectable(e.target,true);
  189. }
  190. var m = dojo.dnd.manager();
  191. m.OFFSET_X = this.GC_OFFSET_X;
  192. m.OFFSET_Y = this.GC_OFFSET_Y;
  193. },
  194. onMouseMove: function(e) {
  195. // summary: Event processor for onmousemove
  196. var m = dojo.dnd.manager();
  197. if(this.isDragging) {
  198. var before = false;
  199. if(this.current != null || (this.current == null && !this.dropObject)){
  200. if(this.isAccepted(m.nodes[0]) || this.containerSource){
  201. before = this.setIndicatorPosition(e);
  202. }
  203. }
  204. if(this.current != this.targetAnchor || before != this.before){
  205. this._markTargetAnchor(before);
  206. m.canDrop(!this.current || m.source != this || !(this.current.id in this.selection));
  207. }
  208. if(this.allowAutoScroll){
  209. this._checkAutoScroll(e);
  210. }
  211. }else{
  212. if(this.mouseDown && this.isSource){
  213. var nodes = this.getSelectedNodes();
  214. if(nodes.length){
  215. m.startDrag(this, nodes, this.copyState(dojo.isCopyKey(e)));
  216. }
  217. }
  218. if(this.allowAutoScroll){
  219. this._stopAutoScroll();
  220. }
  221. }
  222. },
  223. _markTargetAnchor: function(/*Boolean*/before){
  224. // summary: Assigns a class to the current target anchor based on "before" status
  225. if(this.current == this.targetAnchor && this.before == before){ return; }
  226. this.targetAnchor = this.current;
  227. this.targetBox = null;
  228. this.before = before;
  229. },
  230. _unmarkTargetAnchor: function(){
  231. // summary: Removes a class of the current target anchor based on "before" status.
  232. if(!this.targetAnchor){ return; }
  233. this.targetAnchor = null;
  234. this.targetBox = null;
  235. this.before = true;
  236. },
  237. setIndicatorPosition: function(/*Event*/e) {
  238. // summary: set the position of the drop indicator
  239. var before = false;
  240. if(this.current){
  241. if (!this.current.coords || this.allowAutoScroll) {
  242. this.current.coords = {
  243. xy: dojo.coords(this.current, true),
  244. w: this.current.offsetWidth / 2,
  245. h: this.current.offsetHeight / 2
  246. };
  247. }
  248. before = this.horizontal ?
  249. (e.pageX - this.current.coords.xy.x) < this.current.coords.w :
  250. (e.pageY - this.current.coords.xy.y) < this.current.coords.h
  251. this.insertDashedZone(before);
  252. }else{
  253. if(!this.dropObject /*|| dojo.isIE*/){ this.insertDashedZone(false); }
  254. }
  255. return before;
  256. },
  257. onOverEvent:function(){
  258. this._over = true;
  259. dojox.layout.dnd.PlottedDnd.superclass.onOverEvent.call(this);
  260. if (this.isDragging) {
  261. var m = dojo.dnd.manager();
  262. if (!this.current && !this.dropObject && this.getSelectedNodes()[0] && this.isAccepted(m.nodes[0]))
  263. this.insertDashedZone(false);
  264. }
  265. },
  266. onOutEvent: function() {
  267. this._over = false;
  268. this.containerSource = false;
  269. dojox.layout.dnd.PlottedDnd.superclass.onOutEvent.call(this);
  270. if (this.dropObject) this.deleteDashedZone();
  271. },
  272. deleteDashedZone: function() {
  273. // summary: hide the dashed zone
  274. this._drop.style.display = "none";
  275. var next = this._drop.nextSibling;
  276. while (next != null) {
  277. next.coords.xy.y -= parseInt(this._drop.style.height);
  278. next = next.nextSibling;
  279. }
  280. delete this.dropObject;
  281. },
  282. insertDashedZone: function(/*Boolean*/before) {
  283. // summary: Insert the dashed zone at the right place
  284. if(this.dropObject){
  285. if( before == this.dropObject.b &&
  286. ((this.current && this.dropObject.c == this.current.id) ||
  287. (!this.current && !this.dropObject.c))
  288. ){
  289. return;
  290. }else{
  291. this.deleteDashedZone();
  292. }
  293. }
  294. this.dropObject = { n: this._drop, c: this.current ? this.current.id : null, b: before};
  295. if(this.current){
  296. dojo.place(this._drop, this.current, before ? "before" : "after");
  297. if(!this.firstIndicator){
  298. var next = this._drop.nextSibling;
  299. while(next != null){
  300. next.coords.xy.y += parseInt(this._drop.style.height);
  301. next = next.nextSibling;
  302. }
  303. }else{
  304. this.firstIndicator = false;
  305. }
  306. }else{
  307. this.node.appendChild(this._drop);
  308. }
  309. this._drop.style.display = "";
  310. },
  311. insertNodes: function(/*Boolean*/addSelected, /*Array*/data, /*Boolean*/before, /*Node*/anchor){
  312. // summary: Inserts new data items (see Dojo Container's insertNodes method for details).
  313. if(this.dropObject){
  314. dojo.style(this.dropObject.n,"display","none");
  315. dojox.layout.dnd.PlottedDnd.superclass.insertNodes.call(this,true,data,true,this.dropObject.n);
  316. this.deleteDashedZone();
  317. }else{
  318. return dojox.layout.dnd.PlottedDnd.superclass.insertNodes.call(this,addSelected,data,before,anchor);
  319. }
  320. var _widget = dijit.byId(data[0].getAttribute("widgetId"));
  321. if (_widget) {
  322. dojox.layout.dnd._setGcDndHandle(_widget, this.withHandles, this.handleClasses);
  323. if(this.hideSource)
  324. dojo.style(_widget.domNode, "display", "");
  325. }
  326. },
  327. _checkAutoScroll: function(e){
  328. if(this._timer){
  329. clearTimeout(this._timer);
  330. }
  331. this._stopAutoScroll();
  332. var node = this.dom,
  333. y = this._sumAncestorProperties(node,"offsetTop")
  334. ;
  335. //Down
  336. if( (e.pageY - node.offsetTop +30 ) > node.clientHeight ){
  337. this.autoScrollActive = true;
  338. this._autoScrollDown(node);
  339. } else if ( (node.scrollTop > 0) && (e.pageY - y) < 30){
  340. //Up
  341. this.autoScrollActive = true;
  342. this._autoScrollUp(node);
  343. }
  344. },
  345. _autoScrollUp: function(node){
  346. if( this.autoScrollActive && node.scrollTop > 0) {
  347. node.scrollTop -= 30;
  348. this._timer = setTimeout(dojo.hitch(this,"_autoScrollUp",node), 100);
  349. }
  350. },
  351. _autoScrollDown: function(node){
  352. if( this.autoScrollActive && (node.scrollTop < (node.scrollHeight-node.clientHeight))){
  353. node.scrollTop += 30;
  354. this._timer = setTimeout(dojo.hitch(this, "_autoScrollDown",node), 100);
  355. }
  356. },
  357. _stopAutoScroll: function(){
  358. this.autoScrollActive = false;
  359. },
  360. _sumAncestorProperties: function(node, prop){
  361. // summary
  362. // Returns the sum of the passed property on all ancestors of node.
  363. node = dojo.byId(node);
  364. if(!node){ return 0; }
  365. var retVal = 0;
  366. while(node){
  367. var val = node[prop];
  368. if(val){
  369. retVal += val - 0;
  370. if(node == dojo.body()){ break; }// opera and khtml #body & #html has the same values, we only need one value
  371. }
  372. node = node.parentNode;
  373. }
  374. return retVal; // integer
  375. }
  376. });
  377. dojox.layout.dnd._setGcDndHandle = function(service,withHandles,handleClasses, first) {
  378. var cls = "GcDndHandle";
  379. if(!first){
  380. dojo.query(".GcDndHandle", service.domNode).removeClass(cls);
  381. }
  382. if(!withHandles){
  383. dojo.addClass(service.domNode, cls);
  384. }else{
  385. var _hasHandle = false;
  386. for(var i = handleClasses.length - 1; i >= 0; i--){
  387. var _node = dojo.query("." + handleClasses[i], service.domNode)[0];
  388. if(_node){
  389. _hasHandle = true;
  390. if(handleClasses[i] != cls){
  391. var _gripNode = dojo.query("." + cls, service.domNode);
  392. if(_gripNode.length == 0){
  393. dojo.removeClass(service.domNode, cls);
  394. }else{
  395. _gripNode.removeClass(cls);
  396. }
  397. dojo.addClass(_node, cls);
  398. }
  399. }
  400. }
  401. if(!_hasHandle){
  402. dojo.addClass(service.domNode, cls);
  403. }
  404. }
  405. };
  406. dojo.declare("dojox.layout.dnd.DropIndicator", null, {
  407. // summary: An empty widget to show at the user the drop zone of the widget.
  408. constructor: function(/*String*/cn, /*String*/tag) {
  409. this.tag = tag || "div";
  410. this.style = cn || null;
  411. },
  412. isInserted : function(){
  413. return (this.node.parentNode && this.node.parentNode.nodeType==1);
  414. },
  415. create : function(/*Node*//*nodeRef*/){
  416. if(this.node && this.isInserted()){ return this.node; }
  417. var h = "90px",
  418. el = dojo.doc.createElement(this.tag);
  419. if(this.style){
  420. el.className = this.style;
  421. el.style.height = h;
  422. }else{
  423. // FIXME: allow this to be done mostly in CSS?
  424. dojo.style(el, {
  425. position:"relative",
  426. border:"1px dashed #F60",
  427. margin:"2px",
  428. height: h
  429. })
  430. }
  431. this.node = el;
  432. return el;
  433. },
  434. destroy : function(){
  435. if(!this.node || !this.isInserted()){ return; }
  436. this.node.parentNode.removeChild(this.node);
  437. this.node = null;
  438. }
  439. });
  440. dojo.extend(dojo.dnd.Manager, {
  441. canDrop: function(flag){
  442. var canDropFlag = this.target && flag;
  443. if(this.canDropFlag != canDropFlag){
  444. this.canDropFlag = canDropFlag;
  445. if(this.avatar){ this.avatar.update(); }
  446. }
  447. },
  448. makeAvatar: function(){
  449. //summary: Makes the avatar, it is separate to be overwritten dynamically, if needed.
  450. return (this.source.declaredClass == "dojox.layout.dnd.PlottedDnd") ?
  451. new dojox.layout.dnd.Avatar(this, this.source.opacity) :
  452. new dojo.dnd.Avatar(this)
  453. ;
  454. }
  455. });
  456. if(dojo.isIE){
  457. dojox.layout.dnd.handdleIE = [
  458. dojo.subscribe("/dnd/start", null, function(){
  459. IEonselectstart = document.body.onselectstart;
  460. document.body.onselectstart = function(){ return false; };
  461. }),
  462. dojo.subscribe("/dnd/cancel", null, function(){
  463. document.body.onselectstart = IEonselectstart;
  464. }),
  465. dojo.subscribe("/dnd/drop", null, function(){
  466. document.body.onselectstart = IEonselectstart;
  467. })
  468. ];
  469. dojo.addOnWindowUnload(function(){
  470. dojo.forEach(dojox.layout.dnd.handdleIE, dojo.unsubscribe);
  471. });
  472. }
  473. }