2 * jQuery UI Droppable 1.11.4
5 * Copyright jQuery Foundation and other contributors
6 * Released under the MIT license.
7 * http://jquery.org/license
9 * http://api.jqueryui.com/droppable/
11 (function( factory
) {
12 if ( typeof define
=== "function" && define
.amd
) {
14 // AMD. Register as an anonymous module.
29 $.widget( "ui.droppable", {
31 widgetEventPrefix
: "drop",
39 tolerance
: "intersect",
57 this.accept
= $.isFunction( accept
) ? accept : function( d
) {
58 return d
.is( accept
);
61 this.proportions = function( /* valueToWrite */ ) {
62 if ( arguments
.length
) {
63 // Store the droppable's proportions
64 proportions
= arguments
[ 0 ];
66 // Retrieve or derive the droppable's proportions
70 width
: this.element
[ 0 ].offsetWidth
,
71 height
: this.element
[ 0 ].offsetHeight
76 this._addToManager( o
.scope
);
78 o
.addClasses
&& this.element
.addClass( "ui-droppable" );
82 _addToManager: function( scope
) {
83 // Add the reference and positions to the manager
84 $.ui
.ddmanager
.droppables
[ scope
] = $.ui
.ddmanager
.droppables
[ scope
] || [];
85 $.ui
.ddmanager
.droppables
[ scope
].push( this );
88 _splice: function( drop
) {
90 for ( ; i
< drop
.length
; i
++ ) {
91 if ( drop
[ i
] === this ) {
97 _destroy: function() {
98 var drop
= $.ui
.ddmanager
.droppables
[ this.options
.scope
];
100 this._splice( drop
);
102 this.element
.removeClass( "ui-droppable ui-droppable-disabled" );
105 _setOption: function( key
, value
) {
107 if ( key
=== "accept" ) {
108 this.accept
= $.isFunction( value
) ? value : function( d
) {
109 return d
.is( value
);
111 } else if ( key
=== "scope" ) {
112 var drop
= $.ui
.ddmanager
.droppables
[ this.options
.scope
];
114 this._splice( drop
);
115 this._addToManager( value
);
118 this._super( key
, value
);
121 _activate: function( event
) {
122 var draggable
= $.ui
.ddmanager
.current
;
123 if ( this.options
.activeClass
) {
124 this.element
.addClass( this.options
.activeClass
);
127 this._trigger( "activate", event
, this.ui( draggable
) );
131 _deactivate: function( event
) {
132 var draggable
= $.ui
.ddmanager
.current
;
133 if ( this.options
.activeClass
) {
134 this.element
.removeClass( this.options
.activeClass
);
137 this._trigger( "deactivate", event
, this.ui( draggable
) );
141 _over: function( event
) {
143 var draggable
= $.ui
.ddmanager
.current
;
145 // Bail if draggable and droppable are same element
146 if ( !draggable
|| ( draggable
.currentItem
|| draggable
.element
)[ 0 ] === this.element
[ 0 ] ) {
150 if ( this.accept
.call( this.element
[ 0 ], ( draggable
.currentItem
|| draggable
.element
) ) ) {
151 if ( this.options
.hoverClass
) {
152 this.element
.addClass( this.options
.hoverClass
);
154 this._trigger( "over", event
, this.ui( draggable
) );
159 _out: function( event
) {
161 var draggable
= $.ui
.ddmanager
.current
;
163 // Bail if draggable and droppable are same element
164 if ( !draggable
|| ( draggable
.currentItem
|| draggable
.element
)[ 0 ] === this.element
[ 0 ] ) {
168 if ( this.accept
.call( this.element
[ 0 ], ( draggable
.currentItem
|| draggable
.element
) ) ) {
169 if ( this.options
.hoverClass
) {
170 this.element
.removeClass( this.options
.hoverClass
);
172 this._trigger( "out", event
, this.ui( draggable
) );
177 _drop: function( event
, custom
) {
179 var draggable
= custom
|| $.ui
.ddmanager
.current
,
180 childrenIntersection
= false;
182 // Bail if draggable and droppable are same element
183 if ( !draggable
|| ( draggable
.currentItem
|| draggable
.element
)[ 0 ] === this.element
[ 0 ] ) {
187 this.element
.find( ":data(ui-droppable)" ).not( ".ui-draggable-dragging" ).each(function() {
188 var inst
= $( this ).droppable( "instance" );
190 inst
.options
.greedy
&&
191 !inst
.options
.disabled
&&
192 inst
.options
.scope
=== draggable
.options
.scope
&&
193 inst
.accept
.call( inst
.element
[ 0 ], ( draggable
.currentItem
|| draggable
.element
) ) &&
194 $.ui
.intersect( draggable
, $.extend( inst
, { offset
: inst
.element
.offset() } ), inst
.options
.tolerance
, event
)
195 ) { childrenIntersection
= true; return false; }
197 if ( childrenIntersection
) {
201 if ( this.accept
.call( this.element
[ 0 ], ( draggable
.currentItem
|| draggable
.element
) ) ) {
202 if ( this.options
.activeClass
) {
203 this.element
.removeClass( this.options
.activeClass
);
205 if ( this.options
.hoverClass
) {
206 this.element
.removeClass( this.options
.hoverClass
);
208 this._trigger( "drop", event
, this.ui( draggable
) );
218 draggable
: ( c
.currentItem
|| c
.element
),
220 position
: c
.position
,
221 offset
: c
.positionAbs
227 $.ui
.intersect
= (function() {
228 function isOverAxis( x
, reference
, size
) {
229 return ( x
>= reference
) && ( x
< ( reference
+ size
) );
232 return function( draggable
, droppable
, toleranceMode
, event
) {
234 if ( !droppable
.offset
) {
238 var x1
= ( draggable
.positionAbs
|| draggable
.position
.absolute
).left
+ draggable
.margins
.left
,
239 y1
= ( draggable
.positionAbs
|| draggable
.position
.absolute
).top
+ draggable
.margins
.top
,
240 x2
= x1
+ draggable
.helperProportions
.width
,
241 y2
= y1
+ draggable
.helperProportions
.height
,
242 l
= droppable
.offset
.left
,
243 t
= droppable
.offset
.top
,
244 r
= l
+ droppable
.proportions().width
,
245 b
= t
+ droppable
.proportions().height
;
247 switch ( toleranceMode
) {
249 return ( l
<= x1
&& x2
<= r
&& t
<= y1
&& y2
<= b
);
251 return ( l
< x1
+ ( draggable
.helperProportions
.width
/ 2 ) && // Right Half
252 x2
- ( draggable
.helperProportions
.width
/ 2 ) < r
&& // Left Half
253 t
< y1
+ ( draggable
.helperProportions
.height
/ 2 ) && // Bottom Half
254 y2
- ( draggable
.helperProportions
.height
/ 2 ) < b
); // Top Half
256 return isOverAxis( event
.pageY
, t
, droppable
.proportions().height
) && isOverAxis( event
.pageX
, l
, droppable
.proportions().width
);
259 ( y1
>= t
&& y1
<= b
) || // Top edge touching
260 ( y2
>= t
&& y2
<= b
) || // Bottom edge touching
261 ( y1
< t
&& y2
> b
) // Surrounded vertically
263 ( x1
>= l
&& x1
<= r
) || // Left edge touching
264 ( x2
>= l
&& x2
<= r
) || // Right edge touching
265 ( x1
< l
&& x2
> r
) // Surrounded horizontally
274 This manager tracks offsets of draggables and droppables
278 droppables
: { "default": [] },
279 prepareOffsets: function( t
, event
) {
282 m
= $.ui
.ddmanager
.droppables
[ t
.options
.scope
] || [],
283 type
= event
? event
.type
: null, // workaround for #2317
284 list
= ( t
.currentItem
|| t
.element
).find( ":data(ui-droppable)" ).addBack();
286 droppablesLoop
: for ( i
= 0; i
< m
.length
; i
++ ) {
288 // No disabled and non-accepted
289 if ( m
[ i
].options
.disabled
|| ( t
&& !m
[ i
].accept
.call( m
[ i
].element
[ 0 ], ( t
.currentItem
|| t
.element
) ) ) ) {
293 // Filter out elements in the current dragged item
294 for ( j
= 0; j
< list
.length
; j
++ ) {
295 if ( list
[ j
] === m
[ i
].element
[ 0 ] ) {
296 m
[ i
].proportions().height
= 0;
297 continue droppablesLoop
;
301 m
[ i
].visible
= m
[ i
].element
.css( "display" ) !== "none";
302 if ( !m
[ i
].visible
) {
306 // Activate the droppable if used directly from draggables
307 if ( type
=== "mousedown" ) {
308 m
[ i
]._activate
.call( m
[ i
], event
);
311 m
[ i
].offset
= m
[ i
].element
.offset();
312 m
[ i
].proportions({ width
: m
[ i
].element
[ 0 ].offsetWidth
, height
: m
[ i
].element
[ 0 ].offsetHeight
});
317 drop: function( draggable
, event
) {
320 // Create a copy of the droppables in case the list changes during the drop (#9116)
321 $.each( ( $.ui
.ddmanager
.droppables
[ draggable
.options
.scope
] || [] ).slice(), function() {
323 if ( !this.options
) {
326 if ( !this.options
.disabled
&& this.visible
&& $.ui
.intersect( draggable
, this, this.options
.tolerance
, event
) ) {
327 dropped
= this._drop
.call( this, event
) || dropped
;
330 if ( !this.options
.disabled
&& this.visible
&& this.accept
.call( this.element
[ 0 ], ( draggable
.currentItem
|| draggable
.element
) ) ) {
333 this._deactivate
.call( this, event
);
340 dragStart: function( draggable
, event
) {
341 // Listen for scrolling so that if the dragging causes scrolling the position of the droppables can be recalculated (see #5003)
342 draggable
.element
.parentsUntil( "body" ).bind( "scroll.droppable", function() {
343 if ( !draggable
.options
.refreshPositions
) {
344 $.ui
.ddmanager
.prepareOffsets( draggable
, event
);
348 drag: function( draggable
, event
) {
350 // If you have a highly dynamic page, you might try this option. It renders positions every time you move the mouse.
351 if ( draggable
.options
.refreshPositions
) {
352 $.ui
.ddmanager
.prepareOffsets( draggable
, event
);
355 // Run through all droppables and check their positions based on specific tolerance options
356 $.each( $.ui
.ddmanager
.droppables
[ draggable
.options
.scope
] || [], function() {
358 if ( this.options
.disabled
|| this.greedyChild
|| !this.visible
) {
362 var parentInstance
, scope
, parent
,
363 intersects
= $.ui
.intersect( draggable
, this, this.options
.tolerance
, event
),
364 c
= !intersects
&& this.isover
? "isout" : ( intersects
&& !this.isover
? "isover" : null );
369 if ( this.options
.greedy
) {
370 // find droppable parents with same scope
371 scope
= this.options
.scope
;
372 parent
= this.element
.parents( ":data(ui-droppable)" ).filter(function() {
373 return $( this ).droppable( "instance" ).options
.scope
=== scope
;
376 if ( parent
.length
) {
377 parentInstance
= $( parent
[ 0 ] ).droppable( "instance" );
378 parentInstance
.greedyChild
= ( c
=== "isover" );
382 // we just moved into a greedy child
383 if ( parentInstance
&& c
=== "isover" ) {
384 parentInstance
.isover
= false;
385 parentInstance
.isout
= true;
386 parentInstance
._out
.call( parentInstance
, event
);
390 this[c
=== "isout" ? "isover" : "isout"] = false;
391 this[c
=== "isover" ? "_over" : "_out"].call( this, event
);
393 // we just moved out of a greedy child
394 if ( parentInstance
&& c
=== "isout" ) {
395 parentInstance
.isout
= false;
396 parentInstance
.isover
= true;
397 parentInstance
._over
.call( parentInstance
, event
);
402 dragStop: function( draggable
, event
) {
403 draggable
.element
.parentsUntil( "body" ).unbind( "scroll.droppable" );
404 // Call prepareOffsets one final time since IE does not fire return scroll events when overflow was caused by drag (see #5003)
405 if ( !draggable
.options
.refreshPositions
) {
406 $.ui
.ddmanager
.prepareOffsets( draggable
, event
);
411 return $.ui
.droppable
;