MDL-32843 import YUI 3.5.1
[moodle.git] / lib / yui / 3.5.1 / build / dd-drop / dd-drop.js
bloba3246781f84be8036c4eeee1d6fd9bdaaefc405b
1 /*
2 YUI 3.5.1 (build 22)
3 Copyright 2012 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
6 */
7 YUI.add('dd-drop', function(Y) {
10     /**
11      * Provides the ability to create a Drop Target.
12      * @module dd
13      * @submodule dd-drop
14      */     
15     /**
16      * Provides the ability to create a Drop Target.
17      * @class Drop
18      * @extends Base
19      * @constructor
20      * @namespace DD
21      */
23     var NODE = 'node',
24         DDM = Y.DD.DDM,
25         OFFSET_HEIGHT = 'offsetHeight',
26         OFFSET_WIDTH = 'offsetWidth',
27         /**
28         * @event drop:over
29         * @description Fires when a drag element is over this target.
30         * @param {EventFacade} event An Event Facade object with the following specific property added:
31         * <dl>
32         * <dt>drop</dt><dd>The drop object at the time of the event.</dd>
33         * <dt>drag</dt><dd>The drag object at the time of the event.</dd>
34         * </dl>        
35         * @bubbles DDM
36         * @type {CustomEvent}
37         */
38         EV_DROP_OVER = 'drop:over',
39         /**
40         * @event drop:enter
41         * @description Fires when a drag element enters this target.
42         * @param {EventFacade} event An Event Facade object with the following specific property added:
43         * <dl>
44         * <dt>drop</dt><dd>The drop object at the time of the event.</dd>
45         * <dt>drag</dt><dd>The drag object at the time of the event.</dd>
46         * </dl>        
47         * @bubbles DDM
48         * @type {CustomEvent}
49         */
50         EV_DROP_ENTER = 'drop:enter',
51         /**
52         * @event drop:exit
53         * @description Fires when a drag element exits this target.
54         * @param {EventFacade} event An Event Facade object
55         * @bubbles DDM
56         * @type {CustomEvent}
57         */
58         EV_DROP_EXIT = 'drop:exit',
60         /**
61         * @event drop:hit
62         * @description Fires when a draggable node is dropped on this Drop Target. (Fired from dd-ddm-drop)
63         * @param {EventFacade} event An Event Facade object with the following specific property added:
64         * <dl>
65         * <dt>drop</dt><dd>The best guess on what was dropped on.</dd>
66         * <dt>drag</dt><dd>The drag object at the time of the event.</dd>
67         * <dt>others</dt><dd>An array of all the other drop targets that was dropped on.</dd>
68         * </dl>        
69         * @bubbles DDM
70         * @type {CustomEvent}
71         */
72         
74     Drop = function() {
75         this._lazyAddAttrs = false;
76         Drop.superclass.constructor.apply(this, arguments);
79         //DD init speed up.
80         Y.on('domready', Y.bind(function() {
81             Y.later(100, this, this._createShim);
82         }, this));
83         DDM._regTarget(this);
85         /* TODO
86         if (Dom.getStyle(this.el, 'position') == 'fixed') {
87             Event.on(window, 'scroll', function() {
88                 this.activateShim();
89             }, this, true);
90         }
91         */
92     };
94     Drop.NAME = 'drop';
96     Drop.ATTRS = {
97         /**
98         * @attribute node
99         * @description Y.Node instanace to use as the element to make a Drop Target
100         * @type Node
101         */        
102         node: {
103             setter: function(node) {
104                 var n = Y.one(node);
105                 if (!n) {
106                     Y.error('DD.Drop: Invalid Node Given: ' + node);
107                 }
108                 return n;               
109             }
110         },
111         /**
112         * @attribute groups
113         * @description Array of groups to add this drop into.
114         * @type Array
115         */        
116         groups: {
117             value: ['default'],
118             getter: function() {
119                 if (!this._groups) {
120                     this._groups = {};
121                 }
122                 var ret = [];
123                 Y.each(this._groups, function(v, k) {
124                     ret[ret.length] = k;
125                 });
126                 return ret;
127             },            
128             setter: function(g) {
129                 this._groups = {};
130                 Y.each(g, function(v, k) {
131                     this._groups[v] = true;
132                 }, this);
133                 return g;
134             }
135         },   
136         /**
137         * @attribute padding
138         * @description CSS style padding to make the Drop Target bigger than the node.
139         * @type String
140         */
141         padding: {
142             value: '0',
143             setter: function(p) {
144                 return DDM.cssSizestoObject(p);
145             }
146         },
147         /**
148         * @attribute lock
149         * @description Set to lock this drop element.
150         * @type Boolean
151         */        
152         lock: {
153             value: false,
154             setter: function(lock) {
155                 if (lock) {
156                     this.get(NODE).addClass(DDM.CSS_PREFIX + '-drop-locked');
157                 } else {
158                     this.get(NODE).removeClass(DDM.CSS_PREFIX + '-drop-locked');
159                 }
160                 return lock;
161             }
162         },
163         /**
164         * @deprecated
165         * @attribute bubbles
166         * @description Controls the default bubble parent for this Drop instance. Default: Y.DD.DDM. Set to false to disable bubbling. Use bubbleTargets in config.
167         * @type Object
168         */
169         bubbles: {
170             setter: function(t) {
171                 this.addTarget(t);
172                 return t;
173             }
174         },
175         /**
176         * @deprecated
177         * @attribute useShim
178         * @description Use the Drop shim. Default: true
179         * @type Boolean
180         */
181         useShim: {
182             value: true,
183             setter: function(v) {
184                 Y.DD.DDM._noShim = !v;
185                 return v;
186             }
187         }
188     };
190     Y.extend(Drop, Y.Base, {
191         /**
192         * @private
193         * @property _bubbleTargets
194         * @description The default bubbleTarget for this object. Default: Y.DD.DDM
195         */
196         _bubbleTargets: Y.DD.DDM,
197         /**
198         * @method addToGroup
199         * @description Add this Drop instance to a group, this should be used for on-the-fly group additions.
200         * @param {String} g The group to add this Drop Instance to.
201         * @return {Self}
202         * @chainable
203         */
204         addToGroup: function(g) {
205             this._groups[g] = true;
206             return this;
207         },
208         /**
209         * @method removeFromGroup
210         * @description Remove this Drop instance from a group, this should be used for on-the-fly group removals.
211         * @param {String} g The group to remove this Drop Instance from.
212         * @return {Self}
213         * @chainable
214         */
215         removeFromGroup: function(g) {
216             delete this._groups[g];
217             return this;
218         },
219         /**
220         * @private
221         * @method _createEvents
222         * @description This method creates all the events for this Event Target and publishes them so we get Event Bubbling.
223         */
224         _createEvents: function() {
225             
226             var ev = [
227                 EV_DROP_OVER,
228                 EV_DROP_ENTER,
229                 EV_DROP_EXIT,
230                 'drop:hit'
231             ];
233             Y.each(ev, function(v, k) {
234                 this.publish(v, {
235                     type: v,
236                     emitFacade: true,
237                     preventable: false,
238                     bubbles: true,
239                     queuable: false,
240                     prefix: 'drop'
241                 });
242             }, this);
243         },
244         /**
245         * @private
246         * @property _valid
247         * @description Flag for determining if the target is valid in this operation.
248         * @type Boolean
249         */
250         _valid: null,
251         /**
252         * @private
253         * @property _groups
254         * @description The groups this target belongs to.
255         * @type Array
256         */
257         _groups: null,
258         /**
259         * @property shim
260         * @description Node reference to the targets shim
261         * @type {Object}
262         */
263         shim: null,
264         /**
265         * @property region
266         * @description A region object associated with this target, used for checking regions while dragging.
267         * @type Object
268         */
269         region: null,
270         /**
271         * @property overTarget
272         * @description This flag is tripped when a drag element is over this target.
273         * @type Boolean
274         */
275         overTarget: null,
276         /**
277         * @method inGroup
278         * @description Check if this target is in one of the supplied groups.
279         * @param {Array} groups The groups to check against
280         * @return Boolean
281         */
282         inGroup: function(groups) {
283             this._valid = false;
284             var ret = false;
285             Y.each(groups, function(v, k) {
286                 if (this._groups[v]) {
287                     ret = true;
288                     this._valid = true;
289                 }
290             }, this);
291             return ret;
292         },
293         /**
294         * @private
295         * @method initializer
296         * @description Private lifecycle method
297         */
298         initializer: function(cfg) {
299             Y.later(100, this, this._createEvents);
301             var node = this.get(NODE), id;
302             if (!node.get('id')) {
303                 id = Y.stamp(node);
304                 node.set('id', id);
305             }
306             node.addClass(DDM.CSS_PREFIX + '-drop');
307             //Shouldn't have to do this..
308             this.set('groups', this.get('groups'));           
309         },
310         /**
311         * @private
312         * @method destructor
313         * @description Lifecycle destructor, unreg the drag from the DDM and remove listeners
314         */
315         destructor: function() {
316             DDM._unregTarget(this);
317             if (this.shim && (this.shim !== this.get(NODE))) {
318                 this.shim.detachAll();
319                 this.shim.remove();
320                 this.shim = null;
321             }
322             this.get(NODE).removeClass(DDM.CSS_PREFIX + '-drop');
323             this.detachAll();
324         },
325         /**
326         * @private
327         * @method _deactivateShim
328         * @description Removes classes from the target, resets some flags and sets the shims deactive position [-999, -999]
329         */
330         _deactivateShim: function() {
331             if (!this.shim) {
332                 return false;
333             }
334             this.get(NODE).removeClass(DDM.CSS_PREFIX + '-drop-active-valid');
335             this.get(NODE).removeClass(DDM.CSS_PREFIX + '-drop-active-invalid');
336             this.get(NODE).removeClass(DDM.CSS_PREFIX + '-drop-over');
338             if (this.get('useShim')) {
339                 this.shim.setStyles({
340                     top: '-999px',
341                     left: '-999px',
342                     zIndex: '1'
343                 });
344             }
345             this.overTarget = false;
346         },
347         /**
348         * @private
349         * @method _activateShim
350         * @description Activates the shim and adds some interaction CSS classes
351         */
352         _activateShim: function() {
353             if (!DDM.activeDrag) {
354                 return false; //Nothing is dragging, no reason to activate.
355             }
356             if (this.get(NODE) === DDM.activeDrag.get(NODE)) {
357                 return false;
358             }
359             if (this.get('lock')) {
360                 return false;
361             }
362             var node = this.get(NODE);
363             //TODO Visibility Check..
364             //if (this.inGroup(DDM.activeDrag.get('groups')) && this.get(NODE).isVisible()) {
365             if (this.inGroup(DDM.activeDrag.get('groups'))) {
366                 node.removeClass(DDM.CSS_PREFIX + '-drop-active-invalid');
367                 node.addClass(DDM.CSS_PREFIX + '-drop-active-valid');
368                 DDM._addValid(this);
369                 this.overTarget = false;
370                 if (!this.get('useShim')) {
371                     this.shim = this.get(NODE);
372                 }
373                 this.sizeShim();
374             } else {
375                 DDM._removeValid(this);
376                 node.removeClass(DDM.CSS_PREFIX + '-drop-active-valid');
377                 node.addClass(DDM.CSS_PREFIX + '-drop-active-invalid');
378             }
379         },
380         /**
381         * @method sizeShim
382         * @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..
383         */
384         sizeShim: function() {
385             if (!DDM.activeDrag) {
386                 return false; //Nothing is dragging, no reason to activate.
387             }
388             if (this.get(NODE) === DDM.activeDrag.get(NODE)) {
389                 return false;
390             }
391             //if (this.get('lock') || !this.get('useShim')) {
392             if (this.get('lock')) {
393                 return false;
394             }
395             if (!this.shim) {
396                 Y.later(100, this, this.sizeShim);
397                 return false;
398             }
399             var node = this.get(NODE),
400                 nh = node.get(OFFSET_HEIGHT),
401                 nw = node.get(OFFSET_WIDTH),
402                 xy = node.getXY(),
403                 p = this.get('padding'),
404                 dd, dH, dW;
407             //Apply padding
408             nw = nw + p.left + p.right;
409             nh = nh + p.top + p.bottom;
410             xy[0] = xy[0] - p.left;
411             xy[1] = xy[1] - p.top;
412             
414             if (DDM.activeDrag.get('dragMode') === DDM.INTERSECT) {
415                 //Intersect Mode, make the shim bigger
416                 dd = DDM.activeDrag;
417                 dH = dd.get(NODE).get(OFFSET_HEIGHT);
418                 dW = dd.get(NODE).get(OFFSET_WIDTH);
419                 
420                 nh = (nh + dH);
421                 nw = (nw + dW);
422                 xy[0] = xy[0] - (dW - dd.deltaXY[0]);
423                 xy[1] = xy[1] - (dH - dd.deltaXY[1]);
425             }
426             
427             if (this.get('useShim')) {
428                 //Set the style on the shim
429                 this.shim.setStyles({
430                     height: nh + 'px',
431                     width: nw + 'px',
432                     top: xy[1] + 'px',
433                     left: xy[0] + 'px'
434                 });
435             }
437             //Create the region to be used by intersect when a drag node is over us.
438             this.region = {
439                 '0': xy[0], 
440                 '1': xy[1],
441                 area: 0,
442                 top: xy[1],
443                 right: xy[0] + nw,
444                 bottom: xy[1] + nh,
445                 left: xy[0]
446             };
447         },
448         /**
449         * @private
450         * @method _createShim
451         * @description Creates the Target shim and adds it to the DDM's playground..
452         */
453         _createShim: function() {
454             //No playground, defer
455             if (!DDM._pg) {
456                 Y.later(10, this, this._createShim);
457                 return;
458             }
459             //Shim already here, cancel
460             if (this.shim) {
461                 return;
462             }
463             var s = this.get('node');
465             if (this.get('useShim')) {
466                 s = Y.Node.create('<div id="' + this.get(NODE).get('id') + '_shim"></div>');
467                 s.setStyles({
468                     height: this.get(NODE).get(OFFSET_HEIGHT) + 'px',
469                     width: this.get(NODE).get(OFFSET_WIDTH) + 'px',
470                     backgroundColor: 'yellow',
471                     opacity: '.5',
472                     zIndex: '1',
473                     overflow: 'hidden',
474                     top: '-900px',
475                     left: '-900px',
476                     position:  'absolute'
477                 });
479                 DDM._pg.appendChild(s);
481                 s.on('mouseover', Y.bind(this._handleOverEvent, this));
482                 s.on('mouseout', Y.bind(this._handleOutEvent, this));
483             }
486             this.shim = s;
487         },
488         /**
489         * @private
490         * @method _handleOverTarget
491         * @description This handles the over target call made from this object or from the DDM
492         */
493         _handleTargetOver: function() {
494             if (DDM.isOverTarget(this)) {
495                 this.get(NODE).addClass(DDM.CSS_PREFIX + '-drop-over');
496                 DDM.activeDrop = this;
497                 DDM.otherDrops[this] = this;
498                 if (this.overTarget) {
499                     DDM.activeDrag.fire('drag:over', { drop: this, drag: DDM.activeDrag });
500                     this.fire(EV_DROP_OVER, { drop: this, drag: DDM.activeDrag });
501                 } else {
502                     //Prevent an enter before a start..
503                     if (DDM.activeDrag.get('dragging')) {
504                         this.overTarget = true;
505                         this.fire(EV_DROP_ENTER, { drop: this, drag: DDM.activeDrag });
506                         DDM.activeDrag.fire('drag:enter', { drop: this, drag: DDM.activeDrag });
507                         DDM.activeDrag.get(NODE).addClass(DDM.CSS_PREFIX + '-drag-over');
508                         //TODO - Is this needed??
509                         //DDM._handleTargetOver();
510                     }
511                 }
512             } else {
513                 this._handleOut();
514             }
515         },
516         /**
517         * @private
518         * @method _handleOverEvent
519         * @description Handles the mouseover DOM event on the Target Shim
520         */
521         _handleOverEvent: function() {
522             this.shim.setStyle('zIndex', '999');
523             DDM._addActiveShim(this);
524         },
525         /**
526         * @private
527         * @method _handleOutEvent
528         * @description Handles the mouseout DOM event on the Target Shim
529         */
530         _handleOutEvent: function() {
531             this.shim.setStyle('zIndex', '1');
532             DDM._removeActiveShim(this);
533         },
534         /**
535         * @private
536         * @method _handleOut
537         * @description Handles out of target calls/checks
538         */
539         _handleOut: function(force) {
540             if (!DDM.isOverTarget(this) || force) {
541                 if (this.overTarget) {
542                     this.overTarget = false;
543                     if (!force) {
544                         DDM._removeActiveShim(this);
545                     }
546                     if (DDM.activeDrag) {
547                         this.get(NODE).removeClass(DDM.CSS_PREFIX + '-drop-over');
548                         DDM.activeDrag.get(NODE).removeClass(DDM.CSS_PREFIX + '-drag-over');
549                         this.fire(EV_DROP_EXIT, { drop: this, drag: DDM.activeDrag });
550                         DDM.activeDrag.fire('drag:exit', { drop: this, drag: DDM.activeDrag });
551                         delete DDM.otherDrops[this];
552                     }
553                 }
554             }
555         }
556     });
558     Y.DD.Drop = Drop;
563 }, '3.5.1' ,{skinnable:false, requires:['dd-ddm-drop', 'dd-drag']});