define("dojo/dnd/Manager", [ "../_base/array", "../_base/declare", "../_base/event", "../_base/lang", "../_base/window", "../dom-class", "../Evented", "../has", "../keys", "../on", "../topic", "../touch", "./common", "./autoscroll", "./Avatar" ], function(array, declare, event, lang, win, domClass, Evented, has, keys, on, topic, touch, dnd, autoscroll, Avatar){ // module: // dojo/dnd/Manager var Manager = declare("dojo.dnd.Manager", [Evented], { // summary: // the manager of DnD operations (usually a singleton) constructor: function(){ this.avatar = null; this.source = null; this.nodes = []; this.copy = true; this.target = null; this.canDropFlag = false; this.events = []; }, // avatar's offset from the mouse OFFSET_X: has("touch") ? 0 : 16, OFFSET_Y: has("touch") ? -64 : 16, // methods overSource: function(source){ // summary: // called when a source detected a mouse-over condition // source: Object // the reporter if(this.avatar){ this.target = (source && source.targetState != "Disabled") ? source : null; this.canDropFlag = Boolean(this.target); this.avatar.update(); } topic.publish("/dnd/source/over", source); }, outSource: function(source){ // summary: // called when a source detected a mouse-out condition // source: Object // the reporter if(this.avatar){ if(this.target == source){ this.target = null; this.canDropFlag = false; this.avatar.update(); topic.publish("/dnd/source/over", null); } }else{ topic.publish("/dnd/source/over", null); } }, startDrag: function(source, nodes, copy){ // summary: // called to initiate the DnD operation // source: Object // the source which provides items // nodes: Array // the list of transferred items // copy: Boolean // copy items, if true, move items otherwise // Tell autoscroll that a drag is starting autoscroll.autoScrollStart(win.doc); this.source = source; this.nodes = nodes; this.copy = Boolean(copy); // normalizing to true boolean this.avatar = this.makeAvatar(); win.body().appendChild(this.avatar.node); topic.publish("/dnd/start", source, nodes, this.copy); this.events = [ on(win.doc, touch.move, lang.hitch(this, "onMouseMove")), on(win.doc, touch.release, lang.hitch(this, "onMouseUp")), on(win.doc, "keydown", lang.hitch(this, "onKeyDown")), on(win.doc, "keyup", lang.hitch(this, "onKeyUp")), // cancel text selection and text dragging on(win.doc, "dragstart", event.stop), on(win.body(), "selectstart", event.stop) ]; var c = "dojoDnd" + (copy ? "Copy" : "Move"); domClass.add(win.body(), c); }, canDrop: function(flag){ // summary: // called to notify if the current target can accept items var canDropFlag = Boolean(this.target && flag); if(this.canDropFlag != canDropFlag){ this.canDropFlag = canDropFlag; this.avatar.update(); } }, stopDrag: function(){ // summary: // stop the DnD in progress domClass.remove(win.body(), ["dojoDndCopy", "dojoDndMove"]); array.forEach(this.events, function(handle){ handle.remove(); }); this.events = []; this.avatar.destroy(); this.avatar = null; this.source = this.target = null; this.nodes = []; }, makeAvatar: function(){ // summary: // makes the avatar; it is separate to be overwritten dynamically, if needed return new Avatar(this); }, updateAvatar: function(){ // summary: // updates the avatar; it is separate to be overwritten dynamically, if needed this.avatar.update(); }, // mouse event processors onMouseMove: function(e){ // summary: // event processor for onmousemove // e: Event // mouse event var a = this.avatar; if(a){ autoscroll.autoScrollNodes(e); //autoscroll.autoScroll(e); var s = a.node.style; s.left = (e.pageX + this.OFFSET_X) + "px"; s.top = (e.pageY + this.OFFSET_Y) + "px"; var copy = Boolean(this.source.copyState(dnd.getCopyKeyState(e))); if(this.copy != copy){ this._setCopyStatus(copy); } } if(has("touch")){ // Prevent page from scrolling so that user can drag instead. e.preventDefault(); } }, onMouseUp: function(e){ // summary: // event processor for onmouseup // e: Event // mouse event if(this.avatar){ if(this.target && this.canDropFlag){ var copy = Boolean(this.source.copyState(dnd.getCopyKeyState(e))); topic.publish("/dnd/drop/before", this.source, this.nodes, copy, this.target, e); topic.publish("/dnd/drop", this.source, this.nodes, copy, this.target, e); }else{ topic.publish("/dnd/cancel"); } this.stopDrag(); } }, // keyboard event processors onKeyDown: function(e){ // summary: // event processor for onkeydown: // watching for CTRL for copy/move status, watching for ESCAPE to cancel the drag // e: Event // keyboard event if(this.avatar){ switch(e.keyCode){ case keys.CTRL: var copy = Boolean(this.source.copyState(true)); if(this.copy != copy){ this._setCopyStatus(copy); } break; case keys.ESCAPE: topic.publish("/dnd/cancel"); this.stopDrag(); break; } } }, onKeyUp: function(e){ // summary: // event processor for onkeyup, watching for CTRL for copy/move status // e: Event // keyboard event if(this.avatar && e.keyCode == keys.CTRL){ var copy = Boolean(this.source.copyState(false)); if(this.copy != copy){ this._setCopyStatus(copy); } } }, // utilities _setCopyStatus: function(copy){ // summary: // changes the copy status // copy: Boolean // the copy status this.copy = copy; this.source._markDndStatus(this.copy); this.updateAvatar(); domClass.replace(win.body(), "dojoDnd" + (this.copy ? "Copy" : "Move"), "dojoDnd" + (this.copy ? "Move" : "Copy")); } }); // dnd._manager: // The manager singleton variable. Can be overwritten if needed. dnd._manager = null; Manager.manager = dnd.manager = function(){ // summary: // Returns the current DnD manager. Creates one if it is not created yet. if(!dnd._manager){ dnd._manager = new Manager(); } return dnd._manager; // Object }; return Manager; });