UNPKG

dojo

Version:

Dojo core is a powerful, lightweight library that makes common tasks quicker and easier. Animate elements, manipulate the DOM, and query with easy CSS syntax, all without sacrificing performance.

507 lines (485 loc) 15.1 kB
define([ "../_base/array", "../_base/declare", "../_base/kernel", "../_base/lang", "../dom-class", "../dom-geometry", "../mouse", "../ready", "../topic", "./common", "./Selector", "./Manager" ], function(array, declare, kernel, lang, domClass, domGeom, mouse, ready, topic, dnd, Selector, Manager){ // module: // dojo/dnd/Source /* Container property: "Horizontal"- if this is the horizontal container Source states: "" - normal state "Moved" - this source is being moved "Copied" - this source is being copied Target states: "" - normal state "Disabled" - the target cannot accept an avatar Target anchor state: "" - item is not selected "Before" - insert point is before the anchor "After" - insert point is after the anchor */ /*===== var __SourceArgs = { // summary: // a dict of parameters for DnD Source configuration. Note that any // property on Source elements may be configured, but this is the // short-list // isSource: Boolean? // can be used as a DnD source. Defaults to true. // accept: Array? // list of accepted types (text strings) for a target; defaults to // ["text"] // autoSync: Boolean // if true refreshes the node list on every operation; false by default // copyOnly: Boolean? // copy items, if true, use a state of Ctrl key otherwise, // see selfCopy and selfAccept for more details // delay: Number // the move delay in pixels before detecting a drag; 0 by default // horizontal: Boolean? // a horizontal container, if true, vertical otherwise or when omitted // selfCopy: Boolean? // copy items by default when dropping on itself, // false by default, works only if copyOnly is true // selfAccept: Boolean? // accept its own items when copyOnly is true, // true by default, works only if copyOnly is true // withHandles: Boolean? // allows dragging only by handles, false by default // generateText: Boolean? // generate text node for drag and drop, true by default }; =====*/ // For back-compat, remove in 2.0. if(!kernel.isAsync){ ready(0, function(){ var requires = ["dojo/dnd/AutoSource", "dojo/dnd/Target"]; require(requires); // use indirection so modules not rolled into a build }); } var Source = declare("dojo.dnd.Source", Selector, { // summary: // a Source object, which can be used as a DnD source, or a DnD target // object attributes (for markup) isSource: true, horizontal: false, copyOnly: false, selfCopy: false, selfAccept: true, skipForm: false, withHandles: false, autoSync: false, delay: 0, // pixels accept: ["text"], generateText: true, constructor: function(/*DOMNode|String*/ node, /*__SourceArgs?*/ params){ // summary: // a constructor of the Source // node: // node or node's id to build the source on // params: // any property of this class may be configured via the params // object which is mixed-in to the `dojo/dnd/Source` instance lang.mixin(this, lang.mixin({}, params)); var type = this.accept; if(type.length){ this.accept = {}; for(var i = 0; i < type.length; ++i){ this.accept[type[i]] = 1; } } // class-specific variables this.isDragging = false; this.mouseDown = false; this.targetAnchor = null; this.targetBox = null; this.before = true; this._lastX = 0; this._lastY = 0; // states this.sourceState = ""; if(this.isSource){ domClass.add(this.node, "dojoDndSource"); } this.targetState = ""; if(this.accept){ domClass.add(this.node, "dojoDndTarget"); } if(this.horizontal){ domClass.add(this.node, "dojoDndHorizontal"); } // set up events this.topics = [ topic.subscribe("/dnd/source/over", lang.hitch(this, "onDndSourceOver")), topic.subscribe("/dnd/start", lang.hitch(this, "onDndStart")), topic.subscribe("/dnd/drop", lang.hitch(this, "onDndDrop")), topic.subscribe("/dnd/cancel", lang.hitch(this, "onDndCancel")) ]; }, // methods checkAcceptance: function(source, nodes){ // summary: // checks if the target can accept nodes from this source // source: Object // the source which provides items // nodes: Array // the list of transferred items if(this == source){ return !this.copyOnly || this.selfAccept; } for(var i = 0; i < nodes.length; ++i){ var type = source.getItem(nodes[i].id).type; // type instanceof Array var flag = false; for(var j = 0; j < type.length; ++j){ if(type[j] in this.accept){ flag = true; break; } } if(!flag){ return false; // Boolean } } return true; // Boolean }, copyState: function(keyPressed, self){ // summary: // Returns true if we need to copy items, false to move. // It is separated to be overwritten dynamically, if needed. // keyPressed: Boolean // the "copy" key was pressed // self: Boolean? // optional flag that means that we are about to drop on itself if(keyPressed){ return true; } if(arguments.length < 2){ self = this == Manager.manager().target; } if(self){ if(this.copyOnly){ return this.selfCopy; } }else{ return this.copyOnly; } return false; // Boolean }, destroy: function(){ // summary: // prepares the object to be garbage-collected Source.superclass.destroy.call(this); array.forEach(this.topics, function(t){t.remove();}); this.targetAnchor = null; }, // mouse event processors onMouseMove: function(e){ // summary: // event processor for onmousemove // e: Event // mouse event if(this.isDragging && this.targetState == "Disabled"){ return; } Source.superclass.onMouseMove.call(this, e); var m = Manager.manager(); if(!this.isDragging){ if(this.mouseDown && this.isSource && (Math.abs(e.pageX - this._lastX) > this.delay || Math.abs(e.pageY - this._lastY) > this.delay)){ var nodes = this.getSelectedNodes(); if(nodes.length){ m.startDrag(this, nodes, this.copyState(dnd.getCopyKeyState(e), true)); } } } if(this.isDragging){ // calculate before/after var before = false; if(this.current){ if(!this.targetBox || this.targetAnchor != this.current){ this.targetBox = domGeom.position(this.current, true); } if(this.horizontal){ // In LTR mode, the left part of the object means "before", but in RTL mode it means "after". before = (e.pageX - this.targetBox.x < this.targetBox.w / 2) == domGeom.isBodyLtr(this.current.ownerDocument); }else{ before = (e.pageY - this.targetBox.y) < (this.targetBox.h / 2); } } if(this.current != this.targetAnchor || before != this.before){ this._markTargetAnchor(before); m.canDrop(!this.current || m.source != this || !(this.current.id in this.selection)); } } }, onMouseDown: function(e){ // summary: // event processor for onmousedown // e: Event // mouse event if(!this.mouseDown && this._legalMouseDown(e) && (!this.skipForm || !dnd.isFormElement(e))){ this.mouseDown = true; this._lastX = e.pageX; this._lastY = e.pageY; Source.superclass.onMouseDown.call(this, e); } }, onMouseUp: function(e){ // summary: // event processor for onmouseup // e: Event // mouse event if(this.mouseDown){ this.mouseDown = false; Source.superclass.onMouseUp.call(this, e); } }, // topic event processors onDndSourceOver: function(source){ // summary: // topic event processor for /dnd/source/over, called when detected a current source // source: Object // the source which has the mouse over it if(this !== source){ this.mouseDown = false; if(this.targetAnchor){ this._unmarkTargetAnchor(); } }else if(this.isDragging){ var m = Manager.manager(); m.canDrop(this.targetState != "Disabled" && (!this.current || m.source != this || !(this.current.id in this.selection))); } }, onDndStart: function(source, nodes, copy){ // summary: // topic event processor for /dnd/start, 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 if(this.autoSync){ this.sync(); } if(this.isSource){ this._changeState("Source", this == source ? (copy ? "Copied" : "Moved") : ""); } var accepted = this.accept && this.checkAcceptance(source, nodes); this._changeState("Target", accepted ? "" : "Disabled"); if(this == source){ Manager.manager().overSource(this); } this.isDragging = true; }, onDndDrop: function(source, nodes, copy, target){ // summary: // topic event processor for /dnd/drop, called to finish 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 // target: Object // the target which accepts items if(this == target){ // this one is for us => move nodes! this.onDrop(source, nodes, copy); } this.onDndCancel(); }, onDndCancel: function(){ // summary: // topic event processor for /dnd/cancel, called to cancel the DnD operation if(this.targetAnchor){ this._unmarkTargetAnchor(); this.targetAnchor = null; } this.before = true; this.isDragging = false; this.mouseDown = false; this._changeState("Source", ""); this._changeState("Target", ""); }, // local events onDrop: function(source, nodes, copy){ // summary: // called only on the current target, when drop is performed // source: Object // the source which provides items // nodes: Array // the list of transferred items // copy: Boolean // copy items, if true, move items otherwise if(this != source){ this.onDropExternal(source, nodes, copy); }else{ this.onDropInternal(nodes, copy); } }, onDropExternal: function(source, nodes, copy){ // summary: // called only on the current target, when drop is performed // from an external source // source: Object // the source which provides items // nodes: Array // the list of transferred items // copy: Boolean // copy items, if true, move items otherwise var oldCreator = this._normalizedCreator; // transferring nodes from the source to the target if(this.creator){ // use defined creator this._normalizedCreator = function(node, hint){ return oldCreator.call(this, source.getItem(node.id).data, hint); }; }else{ // we have no creator defined => move/clone nodes if(copy){ // clone nodes this._normalizedCreator = function(node /*=====, hint =====*/){ var t = source.getItem(node.id); var n = node.cloneNode(true); n.id = dnd.getUniqueId(); return {node: n, data: t.data, type: t.type}; }; }else{ // move nodes this._normalizedCreator = function(node /*=====, hint =====*/){ var t = source.getItem(node.id); source.delItem(node.id); return {node: node, data: t.data, type: t.type}; }; } } this.selectNone(); if(!copy && !this.creator){ source.selectNone(); } this.insertNodes(true, nodes, this.before, this.current); if(!copy && this.creator){ source.deleteSelectedNodes(); } this._normalizedCreator = oldCreator; }, onDropInternal: function(nodes, copy){ // summary: // called only on the current target, when drop is performed // from the same target/source // nodes: Array // the list of transferred items // copy: Boolean // copy items, if true, move items otherwise var oldCreator = this._normalizedCreator; // transferring nodes within the single source if(this.current && this.current.id in this.selection){ // do nothing return; } if(copy){ if(this.creator){ // create new copies of data items this._normalizedCreator = function(node, hint){ return oldCreator.call(this, this.getItem(node.id).data, hint); }; }else{ // clone nodes this._normalizedCreator = function(node/*=====, hint =====*/){ var t = this.getItem(node.id); var n = node.cloneNode(true); n.id = dnd.getUniqueId(); return {node: n, data: t.data, type: t.type}; }; } }else{ // move nodes if(!this.current){ // do nothing return; } this._normalizedCreator = function(node /*=====, hint =====*/){ var t = this.getItem(node.id); return {node: node, data: t.data, type: t.type}; }; } this._removeSelection(); this.insertNodes(true, nodes, this.before, this.current); this._normalizedCreator = oldCreator; }, onDraggingOver: function(){ // summary: // called during the active DnD operation, when items // are dragged over this target, and it is not disabled }, onDraggingOut: function(){ // summary: // called during the active DnD operation, when items // are dragged away from this target, and it is not disabled }, // utilities onOverEvent: function(){ // summary: // this function is called once, when mouse is over our container Source.superclass.onOverEvent.call(this); Manager.manager().overSource(this); if(this.isDragging && this.targetState != "Disabled"){ this.onDraggingOver(); } }, onOutEvent: function(){ // summary: // this function is called once, when mouse is out of our container Source.superclass.onOutEvent.call(this); Manager.manager().outSource(this); if(this.isDragging && this.targetState != "Disabled"){ this.onDraggingOut(); } }, _markTargetAnchor: function(before){ // summary: // assigns a class to the current target anchor based on "before" status // before: Boolean // insert before, if true, after otherwise if(this.current == this.targetAnchor && this.before == before){ return; } if(this.targetAnchor){ this._removeItemClass(this.targetAnchor, this.before ? "Before" : "After"); } this.targetAnchor = this.current; this.targetBox = null; this.before = before; if(this.targetAnchor){ this._addItemClass(this.targetAnchor, this.before ? "Before" : "After"); } }, _unmarkTargetAnchor: function(){ // summary: // removes a class of the current target anchor based on "before" status if(!this.targetAnchor){ return; } this._removeItemClass(this.targetAnchor, this.before ? "Before" : "After"); this.targetAnchor = null; this.targetBox = null; this.before = true; }, _markDndStatus: function(copy){ // summary: // changes source's state based on "copy" status this._changeState("Source", copy ? "Copied" : "Moved"); }, _legalMouseDown: function(e){ // summary: // checks if user clicked on "approved" items // e: Event // mouse event // accept only the left mouse button, or the left finger if(e.type != "touchstart" && !mouse.isLeft(e)){ return false; } if(!this.withHandles){ return true; } // check for handles for(var node = e.target; node && node !== this.node; node = node.parentNode){ if(domClass.contains(node, "dojoDndHandle")){ return true; } if(domClass.contains(node, "dojoDndItem") || domClass.contains(node, "dojoDndIgnore")){ break; } } return false; // Boolean } }); return Source; });