Source.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  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["dojo.dnd.Source"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dojo.dnd.Source"] = true;
  8. dojo.provide("dojo.dnd.Source");
  9. dojo.require("dojo.dnd.Selector");
  10. dojo.require("dojo.dnd.Manager");
  11. /*
  12. Container property:
  13. "Horizontal"- if this is the horizontal container
  14. Source states:
  15. "" - normal state
  16. "Moved" - this source is being moved
  17. "Copied" - this source is being copied
  18. Target states:
  19. "" - normal state
  20. "Disabled" - the target cannot accept an avatar
  21. Target anchor state:
  22. "" - item is not selected
  23. "Before" - insert point is before the anchor
  24. "After" - insert point is after the anchor
  25. */
  26. /*=====
  27. dojo.dnd.__SourceArgs = function(){
  28. // summary:
  29. // a dict of parameters for DnD Source configuration. Note that any
  30. // property on Source elements may be configured, but this is the
  31. // short-list
  32. // isSource: Boolean?
  33. // can be used as a DnD source. Defaults to true.
  34. // accept: Array?
  35. // list of accepted types (text strings) for a target; defaults to
  36. // ["text"]
  37. // autoSync: Boolean
  38. // if true refreshes the node list on every operation; false by default
  39. // copyOnly: Boolean?
  40. // copy items, if true, use a state of Ctrl key otherwise,
  41. // see selfCopy and selfAccept for more details
  42. // delay: Number
  43. // the move delay in pixels before detecting a drag; 0 by default
  44. // horizontal: Boolean?
  45. // a horizontal container, if true, vertical otherwise or when omitted
  46. // selfCopy: Boolean?
  47. // copy items by default when dropping on itself,
  48. // false by default, works only if copyOnly is true
  49. // selfAccept: Boolean?
  50. // accept its own items when copyOnly is true,
  51. // true by default, works only if copyOnly is true
  52. // withHandles: Boolean?
  53. // allows dragging only by handles, false by default
  54. // generateText: Boolean?
  55. // generate text node for drag and drop, true by default
  56. this.isSource = isSource;
  57. this.accept = accept;
  58. this.autoSync = autoSync;
  59. this.copyOnly = copyOnly;
  60. this.delay = delay;
  61. this.horizontal = horizontal;
  62. this.selfCopy = selfCopy;
  63. this.selfAccept = selfAccept;
  64. this.withHandles = withHandles;
  65. this.generateText = true;
  66. }
  67. =====*/
  68. dojo.declare("dojo.dnd.Source", dojo.dnd.Selector, {
  69. // summary:
  70. // a Source object, which can be used as a DnD source, or a DnD target
  71. // object attributes (for markup)
  72. isSource: true,
  73. horizontal: false,
  74. copyOnly: false,
  75. selfCopy: false,
  76. selfAccept: true,
  77. skipForm: false,
  78. withHandles: false,
  79. autoSync: false,
  80. delay: 0, // pixels
  81. accept: ["text"],
  82. generateText: true,
  83. constructor: function(/*DOMNode|String*/node, /*dojo.dnd.__SourceArgs?*/params){
  84. // summary:
  85. // a constructor of the Source
  86. // node:
  87. // node or node's id to build the source on
  88. // params:
  89. // any property of this class may be configured via the params
  90. // object which is mixed-in to the `dojo.dnd.Source` instance
  91. dojo.mixin(this, dojo.mixin({}, params));
  92. var type = this.accept;
  93. if(type.length){
  94. this.accept = {};
  95. for(var i = 0; i < type.length; ++i){
  96. this.accept[type[i]] = 1;
  97. }
  98. }
  99. // class-specific variables
  100. this.isDragging = false;
  101. this.mouseDown = false;
  102. this.targetAnchor = null;
  103. this.targetBox = null;
  104. this.before = true;
  105. this._lastX = 0;
  106. this._lastY = 0;
  107. // states
  108. this.sourceState = "";
  109. if(this.isSource){
  110. dojo.addClass(this.node, "dojoDndSource");
  111. }
  112. this.targetState = "";
  113. if(this.accept){
  114. dojo.addClass(this.node, "dojoDndTarget");
  115. }
  116. if(this.horizontal){
  117. dojo.addClass(this.node, "dojoDndHorizontal");
  118. }
  119. // set up events
  120. this.topics = [
  121. dojo.subscribe("/dnd/source/over", this, "onDndSourceOver"),
  122. dojo.subscribe("/dnd/start", this, "onDndStart"),
  123. dojo.subscribe("/dnd/drop", this, "onDndDrop"),
  124. dojo.subscribe("/dnd/cancel", this, "onDndCancel")
  125. ];
  126. },
  127. // methods
  128. checkAcceptance: function(source, nodes){
  129. // summary:
  130. // checks if the target can accept nodes from this source
  131. // source: Object
  132. // the source which provides items
  133. // nodes: Array
  134. // the list of transferred items
  135. if(this == source){
  136. return !this.copyOnly || this.selfAccept;
  137. }
  138. for(var i = 0; i < nodes.length; ++i){
  139. var type = source.getItem(nodes[i].id).type;
  140. // type instanceof Array
  141. var flag = false;
  142. for(var j = 0; j < type.length; ++j){
  143. if(type[j] in this.accept){
  144. flag = true;
  145. break;
  146. }
  147. }
  148. if(!flag){
  149. return false; // Boolean
  150. }
  151. }
  152. return true; // Boolean
  153. },
  154. copyState: function(keyPressed, self){
  155. // summary:
  156. // Returns true if we need to copy items, false to move.
  157. // It is separated to be overwritten dynamically, if needed.
  158. // keyPressed: Boolean
  159. // the "copy" key was pressed
  160. // self: Boolean?
  161. // optional flag that means that we are about to drop on itself
  162. if(keyPressed){ return true; }
  163. if(arguments.length < 2){
  164. self = this == dojo.dnd.manager().target;
  165. }
  166. if(self){
  167. if(this.copyOnly){
  168. return this.selfCopy;
  169. }
  170. }else{
  171. return this.copyOnly;
  172. }
  173. return false; // Boolean
  174. },
  175. destroy: function(){
  176. // summary:
  177. // prepares the object to be garbage-collected
  178. dojo.dnd.Source.superclass.destroy.call(this);
  179. dojo.forEach(this.topics, dojo.unsubscribe);
  180. this.targetAnchor = null;
  181. },
  182. // markup methods
  183. markupFactory: function(params, node){
  184. params._skipStartup = true;
  185. return new dojo.dnd.Source(node, params);
  186. },
  187. // mouse event processors
  188. onMouseMove: function(e){
  189. // summary:
  190. // event processor for onmousemove
  191. // e: Event
  192. // mouse event
  193. if(this.isDragging && this.targetState == "Disabled"){ return; }
  194. dojo.dnd.Source.superclass.onMouseMove.call(this, e);
  195. var m = dojo.dnd.manager();
  196. if(!this.isDragging){
  197. if(this.mouseDown && this.isSource &&
  198. (Math.abs(e.pageX - this._lastX) > this.delay || Math.abs(e.pageY - this._lastY) > this.delay)){
  199. var nodes = this.getSelectedNodes();
  200. if(nodes.length){
  201. m.startDrag(this, nodes, this.copyState(dojo.isCopyKey(e), true));
  202. }
  203. }
  204. }
  205. if(this.isDragging){
  206. // calculate before/after
  207. var before = false;
  208. if(this.current){
  209. if(!this.targetBox || this.targetAnchor != this.current){
  210. this.targetBox = dojo.position(this.current, true);
  211. }
  212. if(this.horizontal){
  213. before = (e.pageX - this.targetBox.x) < (this.targetBox.w / 2);
  214. }else{
  215. before = (e.pageY - this.targetBox.y) < (this.targetBox.h / 2);
  216. }
  217. }
  218. if(this.current != this.targetAnchor || before != this.before){
  219. this._markTargetAnchor(before);
  220. m.canDrop(!this.current || m.source != this || !(this.current.id in this.selection));
  221. }
  222. }
  223. },
  224. onMouseDown: function(e){
  225. // summary:
  226. // event processor for onmousedown
  227. // e: Event
  228. // mouse event
  229. if(!this.mouseDown && this._legalMouseDown(e) && (!this.skipForm || !dojo.dnd.isFormElement(e))){
  230. this.mouseDown = true;
  231. this._lastX = e.pageX;
  232. this._lastY = e.pageY;
  233. dojo.dnd.Source.superclass.onMouseDown.call(this, e);
  234. }
  235. },
  236. onMouseUp: function(e){
  237. // summary:
  238. // event processor for onmouseup
  239. // e: Event
  240. // mouse event
  241. if(this.mouseDown){
  242. this.mouseDown = false;
  243. dojo.dnd.Source.superclass.onMouseUp.call(this, e);
  244. }
  245. },
  246. // topic event processors
  247. onDndSourceOver: function(source){
  248. // summary:
  249. // topic event processor for /dnd/source/over, called when detected a current source
  250. // source: Object
  251. // the source which has the mouse over it
  252. if(this != source){
  253. this.mouseDown = false;
  254. if(this.targetAnchor){
  255. this._unmarkTargetAnchor();
  256. }
  257. }else if(this.isDragging){
  258. var m = dojo.dnd.manager();
  259. m.canDrop(this.targetState != "Disabled" && (!this.current || m.source != this || !(this.current.id in this.selection)));
  260. }
  261. },
  262. onDndStart: function(source, nodes, copy){
  263. // summary:
  264. // topic event processor for /dnd/start, called to initiate the DnD operation
  265. // source: Object
  266. // the source which provides items
  267. // nodes: Array
  268. // the list of transferred items
  269. // copy: Boolean
  270. // copy items, if true, move items otherwise
  271. if(this.autoSync){ this.sync(); }
  272. if(this.isSource){
  273. this._changeState("Source", this == source ? (copy ? "Copied" : "Moved") : "");
  274. }
  275. var accepted = this.accept && this.checkAcceptance(source, nodes);
  276. this._changeState("Target", accepted ? "" : "Disabled");
  277. if(this == source){
  278. dojo.dnd.manager().overSource(this);
  279. }
  280. this.isDragging = true;
  281. },
  282. onDndDrop: function(source, nodes, copy, target){
  283. // summary:
  284. // topic event processor for /dnd/drop, called to finish the DnD operation
  285. // source: Object
  286. // the source which provides items
  287. // nodes: Array
  288. // the list of transferred items
  289. // copy: Boolean
  290. // copy items, if true, move items otherwise
  291. // target: Object
  292. // the target which accepts items
  293. if(this == target){
  294. // this one is for us => move nodes!
  295. this.onDrop(source, nodes, copy);
  296. }
  297. this.onDndCancel();
  298. },
  299. onDndCancel: function(){
  300. // summary:
  301. // topic event processor for /dnd/cancel, called to cancel the DnD operation
  302. if(this.targetAnchor){
  303. this._unmarkTargetAnchor();
  304. this.targetAnchor = null;
  305. }
  306. this.before = true;
  307. this.isDragging = false;
  308. this.mouseDown = false;
  309. this._changeState("Source", "");
  310. this._changeState("Target", "");
  311. },
  312. // local events
  313. onDrop: function(source, nodes, copy){
  314. // summary:
  315. // called only on the current target, when drop is performed
  316. // source: Object
  317. // the source which provides items
  318. // nodes: Array
  319. // the list of transferred items
  320. // copy: Boolean
  321. // copy items, if true, move items otherwise
  322. if(this != source){
  323. this.onDropExternal(source, nodes, copy);
  324. }else{
  325. this.onDropInternal(nodes, copy);
  326. }
  327. },
  328. onDropExternal: function(source, nodes, copy){
  329. // summary:
  330. // called only on the current target, when drop is performed
  331. // from an external source
  332. // source: Object
  333. // the source which provides items
  334. // nodes: Array
  335. // the list of transferred items
  336. // copy: Boolean
  337. // copy items, if true, move items otherwise
  338. var oldCreator = this._normalizedCreator;
  339. // transferring nodes from the source to the target
  340. if(this.creator){
  341. // use defined creator
  342. this._normalizedCreator = function(node, hint){
  343. return oldCreator.call(this, source.getItem(node.id).data, hint);
  344. };
  345. }else{
  346. // we have no creator defined => move/clone nodes
  347. if(copy){
  348. // clone nodes
  349. this._normalizedCreator = function(node, hint){
  350. var t = source.getItem(node.id);
  351. var n = node.cloneNode(true);
  352. n.id = dojo.dnd.getUniqueId();
  353. return {node: n, data: t.data, type: t.type};
  354. };
  355. }else{
  356. // move nodes
  357. this._normalizedCreator = function(node, hint){
  358. var t = source.getItem(node.id);
  359. source.delItem(node.id);
  360. return {node: node, data: t.data, type: t.type};
  361. };
  362. }
  363. }
  364. this.selectNone();
  365. if(!copy && !this.creator){
  366. source.selectNone();
  367. }
  368. this.insertNodes(true, nodes, this.before, this.current);
  369. if(!copy && this.creator){
  370. source.deleteSelectedNodes();
  371. }
  372. this._normalizedCreator = oldCreator;
  373. },
  374. onDropInternal: function(nodes, copy){
  375. // summary:
  376. // called only on the current target, when drop is performed
  377. // from the same target/source
  378. // nodes: Array
  379. // the list of transferred items
  380. // copy: Boolean
  381. // copy items, if true, move items otherwise
  382. var oldCreator = this._normalizedCreator;
  383. // transferring nodes within the single source
  384. if(this.current && this.current.id in this.selection){
  385. // do nothing
  386. return;
  387. }
  388. if(copy){
  389. if(this.creator){
  390. // create new copies of data items
  391. this._normalizedCreator = function(node, hint){
  392. return oldCreator.call(this, this.getItem(node.id).data, hint);
  393. };
  394. }else{
  395. // clone nodes
  396. this._normalizedCreator = function(node, hint){
  397. var t = this.getItem(node.id);
  398. var n = node.cloneNode(true);
  399. n.id = dojo.dnd.getUniqueId();
  400. return {node: n, data: t.data, type: t.type};
  401. };
  402. }
  403. }else{
  404. // move nodes
  405. if(!this.current){
  406. // do nothing
  407. return;
  408. }
  409. this._normalizedCreator = function(node, hint){
  410. var t = this.getItem(node.id);
  411. return {node: node, data: t.data, type: t.type};
  412. };
  413. }
  414. this._removeSelection();
  415. this.insertNodes(true, nodes, this.before, this.current);
  416. this._normalizedCreator = oldCreator;
  417. },
  418. onDraggingOver: function(){
  419. // summary:
  420. // called during the active DnD operation, when items
  421. // are dragged over this target, and it is not disabled
  422. },
  423. onDraggingOut: function(){
  424. // summary:
  425. // called during the active DnD operation, when items
  426. // are dragged away from this target, and it is not disabled
  427. },
  428. // utilities
  429. onOverEvent: function(){
  430. // summary:
  431. // this function is called once, when mouse is over our container
  432. dojo.dnd.Source.superclass.onOverEvent.call(this);
  433. dojo.dnd.manager().overSource(this);
  434. if(this.isDragging && this.targetState != "Disabled"){
  435. this.onDraggingOver();
  436. }
  437. },
  438. onOutEvent: function(){
  439. // summary:
  440. // this function is called once, when mouse is out of our container
  441. dojo.dnd.Source.superclass.onOutEvent.call(this);
  442. dojo.dnd.manager().outSource(this);
  443. if(this.isDragging && this.targetState != "Disabled"){
  444. this.onDraggingOut();
  445. }
  446. },
  447. _markTargetAnchor: function(before){
  448. // summary:
  449. // assigns a class to the current target anchor based on "before" status
  450. // before: Boolean
  451. // insert before, if true, after otherwise
  452. if(this.current == this.targetAnchor && this.before == before){ return; }
  453. if(this.targetAnchor){
  454. this._removeItemClass(this.targetAnchor, this.before ? "Before" : "After");
  455. }
  456. this.targetAnchor = this.current;
  457. this.targetBox = null;
  458. this.before = before;
  459. if(this.targetAnchor){
  460. this._addItemClass(this.targetAnchor, this.before ? "Before" : "After");
  461. }
  462. },
  463. _unmarkTargetAnchor: function(){
  464. // summary:
  465. // removes a class of the current target anchor based on "before" status
  466. if(!this.targetAnchor){ return; }
  467. this._removeItemClass(this.targetAnchor, this.before ? "Before" : "After");
  468. this.targetAnchor = null;
  469. this.targetBox = null;
  470. this.before = true;
  471. },
  472. _markDndStatus: function(copy){
  473. // summary:
  474. // changes source's state based on "copy" status
  475. this._changeState("Source", copy ? "Copied" : "Moved");
  476. },
  477. _legalMouseDown: function(e){
  478. // summary:
  479. // checks if user clicked on "approved" items
  480. // e: Event
  481. // mouse event
  482. // accept only the left mouse button
  483. if(!dojo.mouseButtons.isLeft(e)){ return false; }
  484. if(!this.withHandles){ return true; }
  485. // check for handles
  486. for(var node = e.target; node && node !== this.node; node = node.parentNode){
  487. if(dojo.hasClass(node, "dojoDndHandle")){ return true; }
  488. if(dojo.hasClass(node, "dojoDndItem") || dojo.hasClass(node, "dojoDndIgnore")){ break; }
  489. }
  490. return false; // Boolean
  491. }
  492. });
  493. dojo.declare("dojo.dnd.Target", dojo.dnd.Source, {
  494. // summary: a Target object, which can be used as a DnD target
  495. constructor: function(node, params){
  496. // summary:
  497. // a constructor of the Target --- see the `dojo.dnd.Source.constructor` for details
  498. this.isSource = false;
  499. dojo.removeClass(this.node, "dojoDndSource");
  500. },
  501. // markup methods
  502. markupFactory: function(params, node){
  503. params._skipStartup = true;
  504. return new dojo.dnd.Target(node, params);
  505. }
  506. });
  507. dojo.declare("dojo.dnd.AutoSource", dojo.dnd.Source, {
  508. // summary:
  509. // a source that syncs its DnD nodes by default
  510. constructor: function(node, params){
  511. // summary:
  512. // constructor of the AutoSource --- see the Source constructor for details
  513. this.autoSync = true;
  514. },
  515. // markup methods
  516. markupFactory: function(params, node){
  517. params._skipStartup = true;
  518. return new dojo.dnd.AutoSource(node, params);
  519. }
  520. });
  521. }