Source.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. define("dojo/dnd/Source", ["../main", "./Selector", "./Manager"], function(dojo, Selector, Manager) {
  2. // module:
  3. // dojo/dnd/Source
  4. // summary:
  5. // TODOC
  6. /*=====
  7. Selector = dojo.dnd.Selector;
  8. =====*/
  9. /*
  10. Container property:
  11. "Horizontal"- if this is the horizontal container
  12. Source states:
  13. "" - normal state
  14. "Moved" - this source is being moved
  15. "Copied" - this source is being copied
  16. Target states:
  17. "" - normal state
  18. "Disabled" - the target cannot accept an avatar
  19. Target anchor state:
  20. "" - item is not selected
  21. "Before" - insert point is before the anchor
  22. "After" - insert point is after the anchor
  23. */
  24. /*=====
  25. dojo.dnd.__SourceArgs = function(){
  26. // summary:
  27. // a dict of parameters for DnD Source configuration. Note that any
  28. // property on Source elements may be configured, but this is the
  29. // short-list
  30. // isSource: Boolean?
  31. // can be used as a DnD source. Defaults to true.
  32. // accept: Array?
  33. // list of accepted types (text strings) for a target; defaults to
  34. // ["text"]
  35. // autoSync: Boolean
  36. // if true refreshes the node list on every operation; false by default
  37. // copyOnly: Boolean?
  38. // copy items, if true, use a state of Ctrl key otherwise,
  39. // see selfCopy and selfAccept for more details
  40. // delay: Number
  41. // the move delay in pixels before detecting a drag; 0 by default
  42. // horizontal: Boolean?
  43. // a horizontal container, if true, vertical otherwise or when omitted
  44. // selfCopy: Boolean?
  45. // copy items by default when dropping on itself,
  46. // false by default, works only if copyOnly is true
  47. // selfAccept: Boolean?
  48. // accept its own items when copyOnly is true,
  49. // true by default, works only if copyOnly is true
  50. // withHandles: Boolean?
  51. // allows dragging only by handles, false by default
  52. // generateText: Boolean?
  53. // generate text node for drag and drop, true by default
  54. this.isSource = isSource;
  55. this.accept = accept;
  56. this.autoSync = autoSync;
  57. this.copyOnly = copyOnly;
  58. this.delay = delay;
  59. this.horizontal = horizontal;
  60. this.selfCopy = selfCopy;
  61. this.selfAccept = selfAccept;
  62. this.withHandles = withHandles;
  63. this.generateText = true;
  64. }
  65. =====*/
  66. // For back-compat, remove in 2.0.
  67. if(!dojo.isAsync){
  68. dojo.ready(0, function(){
  69. var requires = ["dojo/dnd/AutoSource", "dojo/dnd/Target"];
  70. require(requires); // use indirection so modules not rolled into a build
  71. })
  72. }
  73. return dojo.declare("dojo.dnd.Source", Selector, {
  74. // summary:
  75. // a Source object, which can be used as a DnD source, or a DnD target
  76. // object attributes (for markup)
  77. isSource: true,
  78. horizontal: false,
  79. copyOnly: false,
  80. selfCopy: false,
  81. selfAccept: true,
  82. skipForm: false,
  83. withHandles: false,
  84. autoSync: false,
  85. delay: 0, // pixels
  86. accept: ["text"],
  87. generateText: true,
  88. constructor: function(/*DOMNode|String*/node, /*dojo.dnd.__SourceArgs?*/params){
  89. // summary:
  90. // a constructor of the Source
  91. // node:
  92. // node or node's id to build the source on
  93. // params:
  94. // any property of this class may be configured via the params
  95. // object which is mixed-in to the `dojo.dnd.Source` instance
  96. dojo.mixin(this, dojo.mixin({}, params));
  97. var type = this.accept;
  98. if(type.length){
  99. this.accept = {};
  100. for(var i = 0; i < type.length; ++i){
  101. this.accept[type[i]] = 1;
  102. }
  103. }
  104. // class-specific variables
  105. this.isDragging = false;
  106. this.mouseDown = false;
  107. this.targetAnchor = null;
  108. this.targetBox = null;
  109. this.before = true;
  110. this._lastX = 0;
  111. this._lastY = 0;
  112. // states
  113. this.sourceState = "";
  114. if(this.isSource){
  115. dojo.addClass(this.node, "dojoDndSource");
  116. }
  117. this.targetState = "";
  118. if(this.accept){
  119. dojo.addClass(this.node, "dojoDndTarget");
  120. }
  121. if(this.horizontal){
  122. dojo.addClass(this.node, "dojoDndHorizontal");
  123. }
  124. // set up events
  125. this.topics = [
  126. dojo.subscribe("/dnd/source/over", this, "onDndSourceOver"),
  127. dojo.subscribe("/dnd/start", this, "onDndStart"),
  128. dojo.subscribe("/dnd/drop", this, "onDndDrop"),
  129. dojo.subscribe("/dnd/cancel", this, "onDndCancel")
  130. ];
  131. },
  132. // methods
  133. checkAcceptance: function(source, nodes){
  134. // summary:
  135. // checks if the target can accept nodes from this source
  136. // source: Object
  137. // the source which provides items
  138. // nodes: Array
  139. // the list of transferred items
  140. if(this == source){
  141. return !this.copyOnly || this.selfAccept;
  142. }
  143. for(var i = 0; i < nodes.length; ++i){
  144. var type = source.getItem(nodes[i].id).type;
  145. // type instanceof Array
  146. var flag = false;
  147. for(var j = 0; j < type.length; ++j){
  148. if(type[j] in this.accept){
  149. flag = true;
  150. break;
  151. }
  152. }
  153. if(!flag){
  154. return false; // Boolean
  155. }
  156. }
  157. return true; // Boolean
  158. },
  159. copyState: function(keyPressed, self){
  160. // summary:
  161. // Returns true if we need to copy items, false to move.
  162. // It is separated to be overwritten dynamically, if needed.
  163. // keyPressed: Boolean
  164. // the "copy" key was pressed
  165. // self: Boolean?
  166. // optional flag that means that we are about to drop on itself
  167. if(keyPressed){ return true; }
  168. if(arguments.length < 2){
  169. self = this == Manager.manager().target;
  170. }
  171. if(self){
  172. if(this.copyOnly){
  173. return this.selfCopy;
  174. }
  175. }else{
  176. return this.copyOnly;
  177. }
  178. return false; // Boolean
  179. },
  180. destroy: function(){
  181. // summary:
  182. // prepares the object to be garbage-collected
  183. dojo.dnd.Source.superclass.destroy.call(this);
  184. dojo.forEach(this.topics, dojo.unsubscribe);
  185. this.targetAnchor = null;
  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 = Manager.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 = Manager.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. Manager.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. Manager.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. Manager.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. });