/* Copyright (c) 2010, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.com/yui/license.html version: 3.3.0 build: 3167 */ YUI.add('dd-drop', function(Y) { /** * Provides the ability to create a Drop Target. * @module dd * @submodule dd-drop */ /** * Provides the ability to create a Drop Target. * @class Drop * @extends Base * @constructor * @namespace DD */ var NODE = 'node', DDM = Y.DD.DDM, OFFSET_HEIGHT = 'offsetHeight', OFFSET_WIDTH = 'offsetWidth', /** * @event drop:over * @description Fires when a drag element is over this target. * @param {Event.Facade} event An Event Facade object with the following specific property added: *
*
drop
The drop object at the time of the event.
*
drag
The drag object at the time of the event.
*
* @bubbles DDM * @type {Event.Custom} */ EV_DROP_OVER = 'drop:over', /** * @event drop:enter * @description Fires when a drag element enters this target. * @param {Event.Facade} event An Event Facade object with the following specific property added: *
*
drop
The drop object at the time of the event.
*
drag
The drag object at the time of the event.
*
* @bubbles DDM * @type {Event.Custom} */ EV_DROP_ENTER = 'drop:enter', /** * @event drop:exit * @description Fires when a drag element exits this target. * @param {Event.Facade} event An Event Facade object * @bubbles DDM * @type {Event.Custom} */ EV_DROP_EXIT = 'drop:exit', /** * @event drop:hit * @description Fires when a draggable node is dropped on this Drop Target. (Fired from dd-ddm-drop) * @param {Event.Facade} event An Event Facade object with the following specific property added: *
*
drop
The best guess on what was dropped on.
*
drag
The drag object at the time of the event.
*
others
An array of all the other drop targets that was dropped on.
*
* @bubbles DDM * @type {Event.Custom} */ Drop = function() { this._lazyAddAttrs = false; Drop.superclass.constructor.apply(this, arguments); //DD init speed up. Y.on('domready', Y.bind(function() { Y.later(100, this, this._createShim); }, this)); DDM._regTarget(this); /* TODO if (Dom.getStyle(this.el, 'position') == 'fixed') { Event.on(window, 'scroll', function() { this.activateShim(); }, this, true); } */ }; Drop.NAME = 'drop'; Drop.ATTRS = { /** * @attribute node * @description Y.Node instanace to use as the element to make a Drop Target * @type Node */ node: { setter: function(node) { var n = Y.one(node); if (!n) { Y.error('DD.Drop: Invalid Node Given: ' + node); } return n; } }, /** * @attribute groups * @description Array of groups to add this drop into. * @type Array */ groups: { value: ['default'], setter: function(g) { this._groups = {}; Y.each(g, function(v, k) { this._groups[v] = true; }, this); return g; } }, /** * @attribute padding * @description CSS style padding to make the Drop Target bigger than the node. * @type String */ padding: { value: '0', setter: function(p) { return DDM.cssSizestoObject(p); } }, /** * @attribute lock * @description Set to lock this drop element. * @type Boolean */ lock: { value: false, setter: function(lock) { if (lock) { this.get(NODE).addClass(DDM.CSS_PREFIX + '-drop-locked'); } else { this.get(NODE).removeClass(DDM.CSS_PREFIX + '-drop-locked'); } return lock; } }, /** * @deprecated * @attribute bubbles * @description Controls the default bubble parent for this Drop instance. Default: Y.DD.DDM. Set to false to disable bubbling. Use bubbleTargets in config. * @type Object */ bubbles: { setter: function(t) { this.addTarget(t); return t; } }, /** * @deprecated * @attribute useShim * @description Use the Drop shim. Default: true * @type Boolean */ useShim: { value: true, setter: function(v) { Y.DD.DDM._noShim = !v; return v; } } }; Y.extend(Drop, Y.Base, { /** * @private * @property _bubbleTargets * @description The default bubbleTarget for this object. Default: Y.DD.DDM */ _bubbleTargets: Y.DD.DDM, /** * @method addToGroup * @description Add this Drop instance to a group, this should be used for on-the-fly group additions. * @param {String} g The group to add this Drop Instance to. * @return {Self} * @chainable */ addToGroup: function(g) { this._groups[g] = true; return this; }, /** * @method removeFromGroup * @description Remove this Drop instance from a group, this should be used for on-the-fly group removals. * @param {String} g The group to remove this Drop Instance from. * @return {Self} * @chainable */ removeFromGroup: function(g) { delete this._groups[g]; return this; }, /** * @private * @method _createEvents * @description This method creates all the events for this Event Target and publishes them so we get Event Bubbling. */ _createEvents: function() { var ev = [ EV_DROP_OVER, EV_DROP_ENTER, EV_DROP_EXIT, 'drop:hit' ]; Y.each(ev, function(v, k) { this.publish(v, { type: v, emitFacade: true, preventable: false, bubbles: true, queuable: false, prefix: 'drop' }); }, this); }, /** * @private * @property _valid * @description Flag for determining if the target is valid in this operation. * @type Boolean */ _valid: null, /** * @private * @property _groups * @description The groups this target belongs to. * @type Array */ _groups: null, /** * @property shim * @description Node reference to the targets shim * @type {Object} */ shim: null, /** * @property region * @description A region object associated with this target, used for checking regions while dragging. * @type Object */ region: null, /** * @property overTarget * @description This flag is tripped when a drag element is over this target. * @type Boolean */ overTarget: null, /** * @method inGroup * @description Check if this target is in one of the supplied groups. * @param {Array} groups The groups to check against * @return Boolean */ inGroup: function(groups) { this._valid = false; var ret = false; Y.each(groups, function(v, k) { if (this._groups[v]) { ret = true; this._valid = true; } }, this); return ret; }, /** * @private * @method initializer * @description Private lifecycle method */ initializer: function(cfg) { Y.later(100, this, this._createEvents); var node = this.get(NODE), id; if (!node.get('id')) { id = Y.stamp(node); node.set('id', id); } node.addClass(DDM.CSS_PREFIX + '-drop'); //Shouldn't have to do this.. this.set('groups', this.get('groups')); }, /** * @private * @method destructor * @description Lifecycle destructor, unreg the drag from the DDM and remove listeners */ destructor: function() { DDM._unregTarget(this); if (this.shim && (this.shim !== this.get(NODE))) { this.shim.detachAll(); this.shim.remove(); this.shim = null; } this.get(NODE).removeClass(DDM.CSS_PREFIX + '-drop'); this.detachAll(); }, /** * @private * @method _deactivateShim * @description Removes classes from the target, resets some flags and sets the shims deactive position [-999, -999] */ _deactivateShim: function() { if (!this.shim) { return false; } this.get(NODE).removeClass(DDM.CSS_PREFIX + '-drop-active-valid'); this.get(NODE).removeClass(DDM.CSS_PREFIX + '-drop-active-invalid'); this.get(NODE).removeClass(DDM.CSS_PREFIX + '-drop-over'); if (this.get('useShim')) { this.shim.setStyles({ top: '-999px', left: '-999px', zIndex: '1' }); } this.overTarget = false; }, /** * @private * @method _activateShim * @description Activates the shim and adds some interaction CSS classes */ _activateShim: function() { if (!DDM.activeDrag) { return false; //Nothing is dragging, no reason to activate. } if (this.get(NODE) === DDM.activeDrag.get(NODE)) { return false; } if (this.get('lock')) { return false; } var node = this.get(NODE); //TODO Visibility Check.. //if (this.inGroup(DDM.activeDrag.get('groups')) && this.get(NODE).isVisible()) { if (this.inGroup(DDM.activeDrag.get('groups'))) { node.removeClass(DDM.CSS_PREFIX + '-drop-active-invalid'); node.addClass(DDM.CSS_PREFIX + '-drop-active-valid'); DDM._addValid(this); this.overTarget = false; if (!this.get('useShim')) { this.shim = this.get(NODE); } this.sizeShim(); } else { DDM._removeValid(this); node.removeClass(DDM.CSS_PREFIX + '-drop-active-valid'); node.addClass(DDM.CSS_PREFIX + '-drop-active-invalid'); } }, /** * @method sizeShim * @description Positions and sizes the shim with the raw data from the node, this can be used to programatically adjust the Targets shim for Animation.. */ sizeShim: function() { if (!DDM.activeDrag) { return false; //Nothing is dragging, no reason to activate. } if (this.get(NODE) === DDM.activeDrag.get(NODE)) { return false; } //if (this.get('lock') || !this.get('useShim')) { if (this.get('lock')) { return false; } if (!this.shim) { Y.later(100, this, this.sizeShim); return false; } var node = this.get(NODE), nh = node.get(OFFSET_HEIGHT), nw = node.get(OFFSET_WIDTH), xy = node.getXY(), p = this.get('padding'), dd, dH, dW; //Apply padding nw = nw + p.left + p.right; nh = nh + p.top + p.bottom; xy[0] = xy[0] - p.left; xy[1] = xy[1] - p.top; if (DDM.activeDrag.get('dragMode') === DDM.INTERSECT) { //Intersect Mode, make the shim bigger dd = DDM.activeDrag; dH = dd.get(NODE).get(OFFSET_HEIGHT); dW = dd.get(NODE).get(OFFSET_WIDTH); nh = (nh + dH); nw = (nw + dW); xy[0] = xy[0] - (dW - dd.deltaXY[0]); xy[1] = xy[1] - (dH - dd.deltaXY[1]); } if (this.get('useShim')) { //Set the style on the shim this.shim.setStyles({ height: nh + 'px', width: nw + 'px', top: xy[1] + 'px', left: xy[0] + 'px' }); } //Create the region to be used by intersect when a drag node is over us. this.region = { '0': xy[0], '1': xy[1], area: 0, top: xy[1], right: xy[0] + nw, bottom: xy[1] + nh, left: xy[0] }; }, /** * @private * @method _createShim * @description Creates the Target shim and adds it to the DDM's playground.. */ _createShim: function() { //No playground, defer if (!DDM._pg) { Y.later(10, this, this._createShim); return; } //Shim already here, cancel if (this.shim) { return; } var s = this.get('node'); if (this.get('useShim')) { s = Y.Node.create('
'); s.setStyles({ height: this.get(NODE).get(OFFSET_HEIGHT) + 'px', width: this.get(NODE).get(OFFSET_WIDTH) + 'px', backgroundColor: 'yellow', opacity: '.5', zIndex: '1', overflow: 'hidden', top: '-900px', left: '-900px', position: 'absolute' }); DDM._pg.appendChild(s); s.on('mouseover', Y.bind(this._handleOverEvent, this)); s.on('mouseout', Y.bind(this._handleOutEvent, this)); } this.shim = s; }, /** * @private * @method _handleOverTarget * @description This handles the over target call made from this object or from the DDM */ _handleTargetOver: function() { if (DDM.isOverTarget(this)) { this.get(NODE).addClass(DDM.CSS_PREFIX + '-drop-over'); DDM.activeDrop = this; DDM.otherDrops[this] = this; if (this.overTarget) { DDM.activeDrag.fire('drag:over', { drop: this, drag: DDM.activeDrag }); this.fire(EV_DROP_OVER, { drop: this, drag: DDM.activeDrag }); } else { //Prevent an enter before a start.. if (DDM.activeDrag.get('dragging')) { this.overTarget = true; this.fire(EV_DROP_ENTER, { drop: this, drag: DDM.activeDrag }); DDM.activeDrag.fire('drag:enter', { drop: this, drag: DDM.activeDrag }); DDM.activeDrag.get(NODE).addClass(DDM.CSS_PREFIX + '-drag-over'); //TODO - Is this needed?? //DDM._handleTargetOver(); } } } else { this._handleOut(); } }, /** * @private * @method _handleOverEvent * @description Handles the mouseover DOM event on the Target Shim */ _handleOverEvent: function() { this.shim.setStyle('zIndex', '999'); DDM._addActiveShim(this); }, /** * @private * @method _handleOutEvent * @description Handles the mouseout DOM event on the Target Shim */ _handleOutEvent: function() { this.shim.setStyle('zIndex', '1'); DDM._removeActiveShim(this); }, /** * @private * @method _handleOut * @description Handles out of target calls/checks */ _handleOut: function(force) { if (!DDM.isOverTarget(this) || force) { if (this.overTarget) { this.overTarget = false; if (!force) { DDM._removeActiveShim(this); } if (DDM.activeDrag) { this.get(NODE).removeClass(DDM.CSS_PREFIX + '-drop-over'); DDM.activeDrag.get(NODE).removeClass(DDM.CSS_PREFIX + '-drag-over'); this.fire(EV_DROP_EXIT); DDM.activeDrag.fire('drag:exit', { drop: this }); delete DDM.otherDrops[this]; } } } } }); Y.DD.Drop = Drop; }, '3.3.0' ,{requires:['dd-ddm-drop', 'dd-drag'], skinnable:false});