YAHOO.util.DragDrop |
DragDrop.js
Quick Links:
Class Summary |
Source Code
Souce Code [top]
/** * Defines the interface and base operation of items that that can be * dragged or can be drop targets. It was designed to be extended, overriding * the event handlers for startDrag, onDrag, onDragOver, onDragOut. * Up to three html elements can be associated with a DragDrop instance: * <ul> * <li>linked element: the element that is passed into the constructor. * This is the element which defines the boundaries for interaction with * other DragDrop objects.</li> * <li>handle element(s): The drag operation only occurs if the element that * was clicked matches a handle element. By default this is the linked * element, but there are times that you will want only a portion of the * linked element to initiate the drag operation, and the setHandleElId() * method provides a way to define this.</li> * <li>drag element: this represents an the element that would be moved along * with the cursor during a drag operation. By default, this is the linked * element itself as in <a href="YAHOO.util.DD.html#">YAHOO.util.DD</a>. setDragElId() lets you define * a separate element that would be moved, as in <a href="YAHOO.util.DDProxy.html#">YAHOO.util.DDProxy</a> * </li> * </ul> * This class should not be instantiated until the onload event to ensure that * the associated elements are available. * The following would define a DragDrop obj that would interact with any * other * DragDrop obj in the "group1" group: * <pre> * dd = new YAHOO.util.DragDrop("div1", "group1"); * </pre> * Since none of the event handlers have been implemented, nothing would * actually happen if you were to run the code above. Normally you would * override this class or one of the default implementations, but you can * also override the methods you want on an instance of the class... * <pre> * dd.onDragDrop = function(e, id) { * alert("dd was dropped on " + id); * } * </pre> * @constructor * @param {String} id of the element that is linked to this instance * @param {String} sGroup the group of related DragDrop objects * @param {object} config an object containing configurable attributes * Valid properties for DragDrop: * padding, isTarget, maintainOffset, primaryButtonOnly */ YAHOO.util.DragDrop = function(id, sGroup, config) { if (id) { this.init(id, sGroup, config); } }; YAHOO.util.DragDrop.prototype = { /** * The id of the element associated with this object. This is what we * refer to as the "linked element" because the size and position of * this element is used to determine when the drag and drop objects have * interacted. * * @type String */ id: null, /** * Configuration attributes passed into the constructor * @type object */ config: null, /** * An associative array of HTML tags that will be ignored if clicked. * @type string[] */ invalidHandleTypes: null, /** * An associative array of ids for elements that will be ignored if clicked * @type string[] */ invalidHandleIds: null, /** * An indexted array of css class names for elements that will be ignored * if clicked. * @type string[] */ invalidHandleClasses: null, /** * The group defines a logical collection of DragDrop objects that are * related. Instances only get events when interacting with other * DragDrop object in the same group. This lets us define multiple * groups using a single DragDrop subclass if we want. * @type string[] */ groups: null, /** * Lock this instance */ lock: function() { this.locked = true; }, /** * Unlock this instace */ unlock: function() { this.locked = false; }, /** * By default, all insances can be a drop target. This can be disabled by * setting isTarget to false. * * @type boolean */ isTarget: true, /** * The padding configured for this drag and drop object for calculating * the drop zone intersection with this object. * @type int[] */ padding: null, /** * Maintain offsets when we resetconstraints. Used to maintain the * slider thumb value, and this needs to be fixed. * @type boolean */ maintainOffset: false, /** * Array of pixel locations the element will snap to if we specified a * horizontal graduation/interval. This array is generated automatically * when you define a tick interval. * @type int[] */ xTicks: null, /** * Array of pixel locations the element will snap to if we specified a * vertical graduation/interval. This array is generated automatically * when you define a tick interval. * @type int[] */ yTicks: null, /** * By default the drag and drop instance will only respond to the primary * button click (left button for a right-handed mouse). Set to true to * allow drag and drop to start with any mouse click that is propogated * by the browser * @type boolean */ primaryButtonOnly: true, /** * The availabe property is false until the linked dom element is accessible. * @type boolean */ available: false, /** * Abstract method called after a drag/drop object is clicked * and the drag or mousedown time thresholds have beeen met. * * @param {int} X click location * @param {int} Y click location */ startDrag: function(x, y) { /* override this */ }, /** * Abstract method called during the onMouseMove event while dragging an * object. * * @param {Event} e */ onDrag: function(e) { /* override this */ }, /** * Abstract method called when this element fist begins hovering over * another DragDrop obj * * @param {Event} e * @param {String || YAHOO.util.DragDrop[]} id In POINT mode, the element * id this is hovering over. In INTERSECT mode, an array of one or more * dragdrop items being hovered over. */ onDragEnter: function(e, id) { /* override this */ }, /** * Abstract method called when this element is hovering over another * DragDrop obj * * @param {Event} e * @param {String || YAHOO.util.DragDrop[]} id In POINT mode, the element * id this is hovering over. In INTERSECT mode, an array of dd items * being hovered over. */ onDragOver: function(e, id) { /* override this */ }, /** * Abstract method called when we are no longer hovering over an element * * @param {Event} e * @param {String || YAHOO.util.DragDrop[]} id In POINT mode, the element * id this was hovering over. In INTERSECT mode, an array of dd items * that the mouse is no longer over. */ onDragOut: function(e, id) { /* override this */ }, /** * Abstract method called when this item is dropped on another DragDrop * obj * * @param {Event} e * @param {String || YAHOO.util.DragDrop[]} id In POINT mode, the element * id this was dropped on. In INTERSECT mode, an array of dd items this * was dropped on. */ onDragDrop: function(e, id) { /* override this */ }, /** * Fired when we are done dragging the object * * @param {Event} e */ endDrag: function(e) { /* override this */ }, /** * Event handler that fires when a drag/drop obj gets a mousedown * @param {Event} e */ onMouseDown: function(e) { /* override this */ }, /** * Event handler that fires when a drag/drop obj gets a mouseup * @param {Event} e */ onMouseUp: function(e) { /* override this */ }, /** * Override the onAvailable method to do what is needed after the initial * position was determined. */ onAvailable: function () { this.logger.log("onAvailable (base)"); }, /** * Returns a reference to the linked element * * @return {HTMLElement} the html element */ getEl: function() { if (!this._domRef) { this._domRef = YAHOO.util.Dom.get(this.id); } return this._domRef; }, /** * Returns a reference to the actual element to drag. By default this is * the same as the html element, but it can be assigned to another * element. An example of this can be found in YAHOO.util.DDProxy * * @return {HTMLElement} the html element */ getDragEl: function() { return YAHOO.util.Dom.get(this.dragElId); }, /** * Sets up the DragDrop object. Must be called in the constructor of any * YAHOO.util.DragDrop subclass * * @param id the id of the linked element * @param {String} sGroup the group of related items * @param {object} config configuration attributes */ init: function(id, sGroup, config) { this.initTarget(id, sGroup, config); YAHOO.util.Event.addListener(this.id, "mousedown", this.handleMouseDown, this, true); }, /** * Initializes Targeting functionality only... the object does not * get a mousedown handler. * * @param id the id of the linked element * @param {String} sGroup the group of related items * @param {object} config configuration attributes */ initTarget: function(id, sGroup, config) { // configuration attributes this.config = config || {}; // create a local reference to the drag and drop manager this.DDM = YAHOO.util.DDM; // initialize the groups array this.groups = {}; // set the id this.id = id; // add to an interaction group this.addToGroup((sGroup) ? sGroup : "default"); // We don't want to register this as the handle with the manager // so we just set the id rather than calling the setter. this.handleElId = id; YAHOO.util.Event.onAvailable(id, this.handleOnAvailable, this, true); // create a logger instance this.logger = new YAHOO.widget.LogWriter(this.toString()); // the linked element is the element that gets dragged by default this.setDragElId(id); // by default, clicked anchors will not start drag operations. // @TODO what else should be here? Probably form fields. this.invalidHandleTypes = { A: "A" }; this.invalidHandleIds = {}; this.invalidHandleClasses = []; this.applyConfig(); }, /** * Applies the configuration parameters that were passed into the constructor. * This is supposed to happen at each level through the inheritance chain. So * a DDProxy implentation will execute apply config on DDProxy, DD, and * DragDrop in order to get all of the parameters that are available in * each object. */ applyConfig: function() { // configurable properties: // padding, isTarget, maintainOffset, primaryButtonOnly this.padding = this.config.padding || [0, 0, 0, 0]; this.isTarget = (this.config.isTarget !== false); this.maintainOffset = (this.config.maintainOffset); this.primaryButtonOnly = (this.config.primaryButtonOnly !== false); }, /** * Configures the padding for the target zone in px. Effectively expands * (or reduces) the virtual object size for targeting calculations. * Supports css-style shorthand; if only one parameter is passed, all sides * will have that padding, and if only two are passed, the top and bottom * will have the first param, the left and right the second. * @param {int} iTop Top pad * @param {int} iRight Right pad * @param {int} iBot Bot pad * @param {int} iLeft Left pad */ setPadding: function(iTop, iRight, iBot, iLeft) { // this.padding = [iLeft, iRight, iTop, iBot]; if (!iRight && 0 !== iRight) { this.padding = [iTop, iTop, iTop, iTop]; } else if (!iBot && 0 !== iBot) { this.padding = [iTop, iRight, iTop, iRight]; } else { this.padding = [iTop, iRight, iBot, iLeft]; } }, /** * Stores the initial placement of the dd element */ setInitPosition: function(diffX, diffY) { var el = this.getEl(); if (!this.DDM.verifyEl(el)) { this.logger.log(this.id + " element is broken"); return; } var dx = diffX || 0; var dy = diffY || 0; var p = YAHOO.util.Dom.getXY( el ); this.initPageX = p[0] - dx; this.initPageY = p[1] - dy; this.lastPageX = p[0]; this.lastPageY = p[1]; this.logger.log(this.id + " inital position: " + this.initPageX + ", " + this.initPageY); this.deltaSetXY = null; this.setStartPosition(p); }, /** * Add this instance to a group of related drag/drop objects. All * instances belong to at least one group, and can belong to as many * groups as needed. * * @param sGroup {string} the name of the group */ addToGroup: function(sGroup) { this.groups[sGroup] = true; this.DDM.regDragDrop(this, sGroup); }, /** * Remove's this instance from the supplied interaction group * @param {string} sGroup The group to drop */ removeFromGroup: function(sGroup) { this.logger.log("Removing from group: " + sGroup); if (this.groups[sGroup]) { delete this.groups[sGroup]; } this.DDM.removeDDFromGroup(this, sGroup); }, /** * Allows you to specify that an element other than the linked element * will be moved with the cursor during a drag * * @param id the id of the element that will be used to initiate the drag */ setDragElId: function(id) { this.dragElId = id; }, /** * Allows you to specify a child of the linked element that should be * used to initiate the drag operation. An example of this would be if * you have a content div with text and links. Clicking anywhere in the * content area would normally start the drag operation. Use this method * to specify that an element inside of the content div is the element * that starts the drag operation. * * @param id the id of the element that will be used to initiate the drag */ setHandleElId: function(id) { this.handleElId = id; this.DDM.regHandle(this.id, id); }, /** * Allows you to set an element outside of the linked element as a drag * handle */ setOuterHandleElId: function(id) { this.logger.log("Adding outer handle event: " + id); YAHOO.util.Event.addListener(id, "mousedown", this.handleMouseDown, this, true); this.setHandleElId(id); }, /** * Remove all drag and drop hooks for this element */ unreg: function() { this.logger.log("DragDrop obj cleanup " + this.id); YAHOO.util.Event.removeListener(this.id, "mousedown", this.handleMouseDown); this._domRef = null; this.DDM._remove(this); }, /** * Returns true if this instance is locked, or the drag drop mgr is locked * (meaning that all drag/drop is disabled on the page.) * * @return {boolean} true if this obj or all drag/drop is locked, else * false */ isLocked: function() { return (this.DDM.isLocked() || this.locked); }, /** * Allows you to specify a tag name that should not start a drag operation * when clicked. This is designed to facilitate embedding links within a * drag handle that do something other than start the drag. * * @param {string} tagName the type of element to exclude */ addInvalidHandleType: function(tagName) { var type = tagName.toUpperCase(); this.invalidHandleTypes[type] = type; }, /** * Lets you to specify an element id for a child of a drag handle * that should not initiate a drag * @param {string} id the element id of the element you wish to ignore */ addInvalidHandleId: function(id) { this.invalidHandleIds[id] = id; }, /** * Lets you specify a css class of elements that will not initiate a drag * @param {string} cssClass the class of the elements you wish to ignore */ addInvalidHandleClass: function(cssClass) { this.invalidHandleClasses.push(cssClass); }, /** * Unsets an excluded tag name set by addInvalidHandleType * * @param {string} tagName the type of element to unexclude */ removeInvalidHandleType: function(tagName) { var type = tagName.toUpperCase(); // this.invalidHandleTypes[type] = null; delete this.invalidHandleTypes[type]; }, /** * Unsets an invalid handle id * @param {string} the id of the element to re-enable */ removeInvalidHandleId: function(id) { delete this.invalidHandleIds[id]; }, /** * Unsets an invalid css class * @param {string} the class of the element(s) you wish to re-enable */ removeInvalidHandleClass: function(cssClass) { for (var i=0, len=this.invalidHandleClasses.length; i<len; ++i) { if (this.invalidHandleClasses[i] == cssClass) { delete this.invalidHandleClasses[i]; } } }, /** * Checks the tag exclusion list to see if this click should be ignored * * @param {ygNode} node * @return {boolean} true if this is a valid tag type, false if not */ isValidHandleChild: function(node) { var valid = true; // var n = (node.nodeName == "#text") ? node.parentNode : node; var nodeName; try { nodeName = node.nodeName.toUpperCase(); } catch(e) { nodeName = node.nodeName; } valid = valid && !this.invalidHandleTypes[nodeName]; valid = valid && !this.invalidHandleIds[node.id]; for (var i=0, len=this.invalidHandleClasses.length; valid && i<len; ++i) { valid = !YAHOO.util.Dom.hasClass(node, this.invalidHandleClasses[i]); } this.logger.log("Valid handle? ... " + valid); return valid; }, /** * By default, the element can be dragged any place on the screen. Use * this method to limit the horizontal travel of the element. Pass in * 0,0 for the parameters if you want to lock the drag to the y axis. * * @param {int} iLeft the number of pixels the element can move to the left * @param {int} iRight the number of pixels the element can move to the * right * @param {int} iTickSize optional parameter for specifying that the * element * should move iTickSize pixels at a time. */ setXConstraint: function(iLeft, iRight, iTickSize) { this.leftConstraint = iLeft; this.rightConstraint = iRight; this.minX = this.initPageX - iLeft; this.maxX = this.initPageX + iRight; if (iTickSize) { this.setXTicks(this.initPageX, iTickSize); } this.constrainX = true; this.logger.log("initPageX:" + this.initPageX + " minX:" + this.minX + " maxX:" + this.maxX); }, /** * Clears any constraints applied to this instance. Also clears ticks * since they can't exist independent of a constraint at this time. */ clearConstraints: function() { this.logger.log("Clearing constraints"); this.constrainX = false; this.constrainY = false; this.clearTicks(); }, /** * Clears any tick interval defined for this instance */ clearTicks: function() { this.logger.log("Clearing ticks"); this.xTicks = null; this.yTicks = null; this.xTickSize = 0; this.yTickSize = 0; }, /** * By default, the element can be dragged any place on the screen. Set * this to limit the vertical travel of the element. Pass in 0,0 for the * parameters if you want to lock the drag to the x axis. * * @param {int} iUp the number of pixels the element can move up * @param {int} iDown the number of pixels the element can move down * @param {int} iTickSize optional parameter for specifying that the * element should move iTickSize pixels at a time. */ setYConstraint: function(iUp, iDown, iTickSize) { this.logger.log("setYConstraint: " + iUp + "," + iDown + "," + iTickSize); this.topConstraint = iUp; this.bottomConstraint = iDown; this.minY = this.initPageY - iUp; this.maxY = this.initPageY + iDown; if (iTickSize) { this.setYTicks(this.initPageY, iTickSize); } this.constrainY = true; this.logger.log("initPageY:" + this.initPageY + " minY:" + this.minY + " maxY:" + this.maxY); }, /** * resetConstraints must be called if you manually reposition a dd element. * @param {boolean} maintainOffset */ resetConstraints: function() { this.logger.log("resetConstraints"); // Maintain offsets if necessary if (this.initPageX || this.initPageX === 0) { this.logger.log("init pagexy: " + this.initPageX + ", " + this.initPageY); this.logger.log("last pagexy: " + this.lastPageX + ", " + this.lastPageY); // figure out how much this thing has moved var dx = (this.maintainOffset) ? this.lastPageX - this.initPageX : 0; var dy = (this.maintainOffset) ? this.lastPageY - this.initPageY : 0; this.setInitPosition(dx, dy); // This is the first time we have detected the element's position } else { this.setInitPosition(); } if (this.constrainX) { this.setXConstraint( this.leftConstraint, this.rightConstraint, this.xTickSize ); } if (this.constrainY) { this.setYConstraint( this.topConstraint, this.bottomConstraint, this.yTickSize ); } }, /** * toString method * @return {string} string representation of the dd obj */ toString: function() { return ("DragDrop " + this.id); } };