YAHOO.util.DragDrop |
DragDrop.js
Quick Links:
Class Summary |
Source Code
Souce Code [top]
/* Copyright (c) 2006 Yahoo! Inc. All rights reserved. */ /** * 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 */ YAHOO.util.DragDrop = function(id, sGroup) { if (id) { this.init(id, sGroup); } }; 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, /** * An associative array of HTML tags that will be ignored if clicked. * @type {string: string} */ invalidHandleTypes: null, /** * An associative array of ids for elements that will be ignored if clicked * @type {string: 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: 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.debug("onAvailable (base)"); }, /** * Returns a reference to the linked element * * @return {HTMLElement} the html element */ getEl: function() { if (!this._domRef) { this._domRef = this.DDM.getElement(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 this.DDM.getElement(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 * element is supposed to be a target only, set to false. */ init: function(id, sGroup) { this.initTarget(id, sGroup); 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 * element is supposed to be a target only, set to false. */ initTarget: function(id, sGroup) { // create a local reference to the drag and drop manager this.DDM = YAHOO.util.DDM; // create a logger instance this.logger = new ygLogger("DragDrop"); // set the default padding this.padding = [0, 0, 0, 0]; // initialize the groups array this.groups = {}; // set the id this.id = id; // the element is a drag handle by default this.setDragElId(id); // by default, clicked anchors will not start drag operations this.invalidHandleTypes = { A: "A" }; this.invalidHandleIds = {}; this.invalidHandleClasses = []; // 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; // cache the position of the element if we can // if (document && document.body) { // this.setInitPosition(); // } // var self = this; YAHOO.util.Event.onAvailable(id, this.handleOnAvailable, this, true); // add to an interaction group this.addToGroup((sGroup) ? sGroup : "default"); }, /** * 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.debug(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.debug(this.id + " inital position: " + this.initPageX + ", " + this.initPageY); 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); }, /** * 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.debug("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.debug("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 type = node.nodeName; // if (type == "#text") { // // this.logger.debug("text node, getting parent node type"); // type = node.parentNode.nodeName; // } var valid = true; var n = (node.nodeName == "#text") ? node.parentNode : node; valid = valid && !this.invalidHandleTypes[n.nodeName]; valid = valid && !this.invalidHandleIds[n.id]; for (var i=0, len=this.invalidHandleClasses.length; valid && i<len; ++i) { valid = !YAHOO.util.Dom.hasClass(n, this.invalidHandleClasses[i]); } this.logger.debug("Valid handle? ... " + valid); return valid; //return ( !(this.invalidHandleTypes[n.nodeName] || //this.invalidHandleIds[n.id]) ); }, /** * 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.debug("initPageX:" + this.initPageX + " minX:" + this.minX + " maxX:" + this.maxX); }, /** * 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.debug("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.debug("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.debug("resetConstraints"); // Maintain offsets if necessary if (this.initPageX || this.initPageX === 0) { this.logger.debug("init pagexy: " + this.initPageX + ", " + this.initPageY); this.logger.debug("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(val, tickArray) { return ("YAHOO.util.DragDrop {" + this.id + "}"); } };