resources/lib/jquery-ui/ui/widgets/draggable.js   F
last analyzed

Complexity

Total Complexity 233
Complexity/F 3.95

Size

Lines of Code 1234
Function Count 59

Duplication

Duplicated Lines 182
Ratio 14.75 %

Importance

Changes 0
Metric Value
cc 0
nc 0
dl 182
loc 1234
rs 2.4
c 0
b 0
f 0
wmc 233
mnd 5
bc 172
fnc 59
bpm 2.9152
cpm 3.9491
noi 0

1 Function

Rating   Name   Duplication   Size   Complexity  
B draggable.js ➔ ?!? 182 1214 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like resources/lib/jquery-ui/ui/widgets/draggable.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
/*!
2
 * jQuery UI Draggable 1.12.1
3
 * http://jqueryui.com
4
 *
5
 * Copyright jQuery Foundation and other contributors
6
 * Released under the MIT license.
7
 * http://jquery.org/license
8
 */
9
10
//>>label: Draggable
11
//>>group: Interactions
12
//>>description: Enables dragging functionality for any element.
13
//>>docs: http://api.jqueryui.com/draggable/
14
//>>demos: http://jqueryui.com/draggable/
15
//>>css.structure: ../../themes/base/draggable.css
16
17
( function( factory ) {
18
	if ( typeof define === "function" && define.amd ) {
19
20
		// AMD. Register as an anonymous module.
21
		define( [
22
			"jquery",
23
			"./mouse",
24
			"../data",
25
			"../plugin",
26
			"../safe-active-element",
27
			"../safe-blur",
28
			"../scroll-parent",
29
			"../version",
30
			"../widget"
31
		], factory );
32
	} else {
33
34
		// Browser globals
35
		factory( jQuery );
36
	}
37
}( function( $ ) {
38
39
$.widget( "ui.draggable", $.ui.mouse, {
40
	version: "1.12.1",
41
	widgetEventPrefix: "drag",
42
	options: {
43
		addClasses: true,
44
		appendTo: "parent",
45
		axis: false,
46
		connectToSortable: false,
47
		containment: false,
48
		cursor: "auto",
49
		cursorAt: false,
50
		grid: false,
51
		handle: false,
52
		helper: "original",
53
		iframeFix: false,
54
		opacity: false,
55
		refreshPositions: false,
56
		revert: false,
57
		revertDuration: 500,
58
		scope: "default",
59
		scroll: true,
60
		scrollSensitivity: 20,
61
		scrollSpeed: 20,
62
		snap: false,
63
		snapMode: "both",
64
		snapTolerance: 20,
65
		stack: false,
66
		zIndex: false,
67
68
		// Callbacks
69
		drag: null,
70
		start: null,
71
		stop: null
72
	},
73
	_create: function() {
74
75
		if ( this.options.helper === "original" ) {
76
			this._setPositionRelative();
77
		}
78
		if ( this.options.addClasses ) {
79
			this._addClass( "ui-draggable" );
80
		}
81
		this._setHandleClassName();
82
83
		this._mouseInit();
84
	},
85
86
	_setOption: function( key, value ) {
87
		this._super( key, value );
88
		if ( key === "handle" ) {
89
			this._removeHandleClassName();
90
			this._setHandleClassName();
91
		}
92
	},
93
94
	_destroy: function() {
95
		if ( ( this.helper || this.element ).is( ".ui-draggable-dragging" ) ) {
96
			this.destroyOnClear = true;
97
			return;
98
		}
99
		this._removeHandleClassName();
100
		this._mouseDestroy();
101
	},
102
103
	_mouseCapture: function( event ) {
104
		var o = this.options;
105
106
		// Among others, prevent a drag on a resizable-handle
107
		if ( this.helper || o.disabled ||
108
				$( event.target ).closest( ".ui-resizable-handle" ).length > 0 ) {
109
			return false;
110
		}
111
112
		//Quit if we're not on a valid handle
113
		this.handle = this._getHandle( event );
114
		if ( !this.handle ) {
115
			return false;
116
		}
117
118
		this._blurActiveElement( event );
119
120
		this._blockFrames( o.iframeFix === true ? "iframe" : o.iframeFix );
121
122
		return true;
123
124
	},
125
126
	_blockFrames: function( selector ) {
127
		this.iframeBlocks = this.document.find( selector ).map( function() {
128
			var iframe = $( this );
129
130
			return $( "<div>" )
131
				.css( "position", "absolute" )
132
				.appendTo( iframe.parent() )
133
				.outerWidth( iframe.outerWidth() )
134
				.outerHeight( iframe.outerHeight() )
135
				.offset( iframe.offset() )[ 0 ];
136
		} );
137
	},
138
139
	_unblockFrames: function() {
140
		if ( this.iframeBlocks ) {
141
			this.iframeBlocks.remove();
142
			delete this.iframeBlocks;
143
		}
144
	},
145
146
	_blurActiveElement: function( event ) {
147
		var activeElement = $.ui.safeActiveElement( this.document[ 0 ] ),
148
			target = $( event.target );
149
150
		// Don't blur if the event occurred on an element that is within
151
		// the currently focused element
152
		// See #10527, #12472
153
		if ( target.closest( activeElement ).length ) {
154
			return;
155
		}
156
157
		// Blur any element that currently has focus, see #4261
158
		$.ui.safeBlur( activeElement );
159
	},
160
161
	_mouseStart: function( event ) {
162
163
		var o = this.options;
164
165
		//Create and append the visible helper
166
		this.helper = this._createHelper( event );
167
168
		this._addClass( this.helper, "ui-draggable-dragging" );
169
170
		//Cache the helper size
171
		this._cacheHelperProportions();
172
173
		//If ddmanager is used for droppables, set the global draggable
174
		if ( $.ui.ddmanager ) {
175
			$.ui.ddmanager.current = this;
176
		}
177
178
		/*
179
		 * - Position generation -
180
		 * This block generates everything position related - it's the core of draggables.
181
		 */
182
183
		//Cache the margins of the original element
184
		this._cacheMargins();
185
186
		//Store the helper's css position
187
		this.cssPosition = this.helper.css( "position" );
188
		this.scrollParent = this.helper.scrollParent( true );
189
		this.offsetParent = this.helper.offsetParent();
190
		this.hasFixedAncestor = this.helper.parents().filter( function() {
191
				return $( this ).css( "position" ) === "fixed";
192
			} ).length > 0;
193
194
		//The element's absolute position on the page minus margins
195
		this.positionAbs = this.element.offset();
196
		this._refreshOffsets( event );
197
198
		//Generate the original position
199
		this.originalPosition = this.position = this._generatePosition( event, false );
200
		this.originalPageX = event.pageX;
201
		this.originalPageY = event.pageY;
202
203
		//Adjust the mouse offset relative to the helper if "cursorAt" is supplied
204
		( o.cursorAt && this._adjustOffsetFromHelper( o.cursorAt ) );
205
206
		//Set a containment if given in the options
207
		this._setContainment();
208
209
		//Trigger event + callbacks
210
		if ( this._trigger( "start", event ) === false ) {
211
			this._clear();
212
			return false;
213
		}
214
215
		//Recache the helper size
216
		this._cacheHelperProportions();
217
218
		//Prepare the droppable offsets
219
		if ( $.ui.ddmanager && !o.dropBehaviour ) {
220
			$.ui.ddmanager.prepareOffsets( this, event );
221
		}
222
223
		// Execute the drag once - this causes the helper not to be visible before getting its
224
		// correct position
225
		this._mouseDrag( event, true );
226
227
		// If the ddmanager is used for droppables, inform the manager that dragging has started
228
		// (see #5003)
229
		if ( $.ui.ddmanager ) {
230
			$.ui.ddmanager.dragStart( this, event );
231
		}
232
233
		return true;
234
	},
235
236
	_refreshOffsets: function( event ) {
237
		this.offset = {
238
			top: this.positionAbs.top - this.margins.top,
239
			left: this.positionAbs.left - this.margins.left,
240
			scroll: false,
241
			parent: this._getParentOffset(),
242
			relative: this._getRelativeOffset()
243
		};
244
245
		this.offset.click = {
246
			left: event.pageX - this.offset.left,
247
			top: event.pageY - this.offset.top
248
		};
249
	},
250
251
	_mouseDrag: function( event, noPropagation ) {
252
253
		// reset any necessary cached properties (see #5009)
254
		if ( this.hasFixedAncestor ) {
255
			this.offset.parent = this._getParentOffset();
256
		}
257
258
		//Compute the helpers position
259
		this.position = this._generatePosition( event, true );
260
		this.positionAbs = this._convertPositionTo( "absolute" );
261
262
		//Call plugins and callbacks and use the resulting position if something is returned
263
		if ( !noPropagation ) {
264
			var ui = this._uiHash();
265
			if ( this._trigger( "drag", event, ui ) === false ) {
266
				this._mouseUp( new $.Event( "mouseup", event ) );
267
				return false;
268
			}
269
			this.position = ui.position;
270
		}
271
272
		this.helper[ 0 ].style.left = this.position.left + "px";
273
		this.helper[ 0 ].style.top = this.position.top + "px";
274
275
		if ( $.ui.ddmanager ) {
276
			$.ui.ddmanager.drag( this, event );
277
		}
278
279
		return false;
280
	},
281
282
	_mouseStop: function( event ) {
283
284
		//If we are using droppables, inform the manager about the drop
285
		var that = this,
286
			dropped = false;
287
		if ( $.ui.ddmanager && !this.options.dropBehaviour ) {
288
			dropped = $.ui.ddmanager.drop( this, event );
289
		}
290
291
		//if a drop comes from outside (a sortable)
292
		if ( this.dropped ) {
293
			dropped = this.dropped;
294
			this.dropped = false;
295
		}
296
297
		if ( ( this.options.revert === "invalid" && !dropped ) ||
298
				( this.options.revert === "valid" && dropped ) ||
299
				this.options.revert === true || ( $.isFunction( this.options.revert ) &&
300
				this.options.revert.call( this.element, dropped ) )
301
		) {
302
			$( this.helper ).animate(
303
				this.originalPosition,
304
				parseInt( this.options.revertDuration, 10 ),
305
				function() {
306
					if ( that._trigger( "stop", event ) !== false ) {
307
						that._clear();
308
					}
309
				}
310
			);
311
		} else {
312
			if ( this._trigger( "stop", event ) !== false ) {
313
				this._clear();
314
			}
315
		}
316
317
		return false;
318
	},
319
320
	_mouseUp: function( event ) {
321
		this._unblockFrames();
322
323
		// If the ddmanager is used for droppables, inform the manager that dragging has stopped
324
		// (see #5003)
325
		if ( $.ui.ddmanager ) {
326
			$.ui.ddmanager.dragStop( this, event );
327
		}
328
329
		// Only need to focus if the event occurred on the draggable itself, see #10527
330
		if ( this.handleElement.is( event.target ) ) {
331
332
			// The interaction is over; whether or not the click resulted in a drag,
333
			// focus the element
334
			this.element.trigger( "focus" );
335
		}
336
337
		return $.ui.mouse.prototype._mouseUp.call( this, event );
338
	},
339
340
	cancel: function() {
341
342
		if ( this.helper.is( ".ui-draggable-dragging" ) ) {
343
			this._mouseUp( new $.Event( "mouseup", { target: this.element[ 0 ] } ) );
344
		} else {
345
			this._clear();
346
		}
347
348
		return this;
349
350
	},
351
352
	_getHandle: function( event ) {
353
		return this.options.handle ?
354
			!!$( event.target ).closest( this.element.find( this.options.handle ) ).length :
355
			true;
356
	},
357
358
	_setHandleClassName: function() {
359
		this.handleElement = this.options.handle ?
360
			this.element.find( this.options.handle ) : this.element;
361
		this._addClass( this.handleElement, "ui-draggable-handle" );
362
	},
363
364
	_removeHandleClassName: function() {
365
		this._removeClass( this.handleElement, "ui-draggable-handle" );
366
	},
367
368
	_createHelper: function( event ) {
369
370
		var o = this.options,
371
			helperIsFunction = $.isFunction( o.helper ),
372
			helper = helperIsFunction ?
373
				$( o.helper.apply( this.element[ 0 ], [ event ] ) ) :
374
				( o.helper === "clone" ?
375
					this.element.clone().removeAttr( "id" ) :
376
					this.element );
377
378
		if ( !helper.parents( "body" ).length ) {
379
			helper.appendTo( ( o.appendTo === "parent" ?
380
				this.element[ 0 ].parentNode :
381
				o.appendTo ) );
382
		}
383
384
		// Http://bugs.jqueryui.com/ticket/9446
385
		// a helper function can return the original element
386
		// which wouldn't have been set to relative in _create
387
		if ( helperIsFunction && helper[ 0 ] === this.element[ 0 ] ) {
388
			this._setPositionRelative();
389
		}
390
391
		if ( helper[ 0 ] !== this.element[ 0 ] &&
392
				!( /(fixed|absolute)/ ).test( helper.css( "position" ) ) ) {
393
			helper.css( "position", "absolute" );
394
		}
395
396
		return helper;
397
398
	},
399
400
	_setPositionRelative: function() {
401
		if ( !( /^(?:r|a|f)/ ).test( this.element.css( "position" ) ) ) {
402
			this.element[ 0 ].style.position = "relative";
403
		}
404
	},
405
406 View Code Duplication
	_adjustOffsetFromHelper: function( obj ) {
407
		if ( typeof obj === "string" ) {
408
			obj = obj.split( " " );
409
		}
410
		if ( $.isArray( obj ) ) {
411
			obj = { left: +obj[ 0 ], top: +obj[ 1 ] || 0 };
412
		}
413
		if ( "left" in obj ) {
414
			this.offset.click.left = obj.left + this.margins.left;
415
		}
416
		if ( "right" in obj ) {
417
			this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
418
		}
419
		if ( "top" in obj ) {
420
			this.offset.click.top = obj.top + this.margins.top;
421
		}
422
		if ( "bottom" in obj ) {
423
			this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
424
		}
425
	},
426
427
	_isRootNode: function( element ) {
428
		return ( /(html|body)/i ).test( element.tagName ) || element === this.document[ 0 ];
429
	},
430
431
	_getParentOffset: function() {
432
433
		//Get the offsetParent and cache its position
434
		var po = this.offsetParent.offset(),
435
			document = this.document[ 0 ];
436
437
		// This is a special case where we need to modify a offset calculated on start, since the
438
		// following happened:
439
		// 1. The position of the helper is absolute, so it's position is calculated based on the
440
		// next positioned parent
441
		// 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't
442
		// the document, which means that the scroll is included in the initial calculation of the
443
		// offset of the parent, and never recalculated upon drag
444
		if ( this.cssPosition === "absolute" && this.scrollParent[ 0 ] !== document &&
445
				$.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) {
446
			po.left += this.scrollParent.scrollLeft();
447
			po.top += this.scrollParent.scrollTop();
448
		}
449
450
		if ( this._isRootNode( this.offsetParent[ 0 ] ) ) {
451
			po = { top: 0, left: 0 };
452
		}
453
454
		return {
455
			top: po.top + ( parseInt( this.offsetParent.css( "borderTopWidth" ), 10 ) || 0 ),
456
			left: po.left + ( parseInt( this.offsetParent.css( "borderLeftWidth" ), 10 ) || 0 )
457
		};
458
459
	},
460
461
	_getRelativeOffset: function() {
462
		if ( this.cssPosition !== "relative" ) {
463
			return { top: 0, left: 0 };
464
		}
465
466
		var p = this.element.position(),
467
			scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] );
468
469
		return {
470
			top: p.top - ( parseInt( this.helper.css( "top" ), 10 ) || 0 ) +
471
				( !scrollIsRootNode ? this.scrollParent.scrollTop() : 0 ),
472
			left: p.left - ( parseInt( this.helper.css( "left" ), 10 ) || 0 ) +
473
				( !scrollIsRootNode ? this.scrollParent.scrollLeft() : 0 )
474
		};
475
476
	},
477
478
	_cacheMargins: function() {
479
		this.margins = {
480
			left: ( parseInt( this.element.css( "marginLeft" ), 10 ) || 0 ),
481
			top: ( parseInt( this.element.css( "marginTop" ), 10 ) || 0 ),
482
			right: ( parseInt( this.element.css( "marginRight" ), 10 ) || 0 ),
483
			bottom: ( parseInt( this.element.css( "marginBottom" ), 10 ) || 0 )
484
		};
485
	},
486
487
	_cacheHelperProportions: function() {
488
		this.helperProportions = {
489
			width: this.helper.outerWidth(),
490
			height: this.helper.outerHeight()
491
		};
492
	},
493
494
	_setContainment: function() {
495
496
		var isUserScrollable, c, ce,
497
			o = this.options,
498
			document = this.document[ 0 ];
499
500
		this.relativeContainer = null;
501
502
		if ( !o.containment ) {
503
			this.containment = null;
504
			return;
505
		}
506
507
		if ( o.containment === "window" ) {
508
			this.containment = [
509
				$( window ).scrollLeft() - this.offset.relative.left - this.offset.parent.left,
510
				$( window ).scrollTop() - this.offset.relative.top - this.offset.parent.top,
511
				$( window ).scrollLeft() + $( window ).width() -
512
					this.helperProportions.width - this.margins.left,
513
				$( window ).scrollTop() +
514
					( $( window ).height() || document.body.parentNode.scrollHeight ) -
515
					this.helperProportions.height - this.margins.top
516
			];
517
			return;
518
		}
519
520
		if ( o.containment === "document" ) {
521
			this.containment = [
522
				0,
523
				0,
524
				$( document ).width() - this.helperProportions.width - this.margins.left,
525
				( $( document ).height() || document.body.parentNode.scrollHeight ) -
526
					this.helperProportions.height - this.margins.top
527
			];
528
			return;
529
		}
530
531
		if ( o.containment.constructor === Array ) {
532
			this.containment = o.containment;
533
			return;
534
		}
535
536
		if ( o.containment === "parent" ) {
537
			o.containment = this.helper[ 0 ].parentNode;
538
		}
539
540
		c = $( o.containment );
541
		ce = c[ 0 ];
542
543
		if ( !ce ) {
544
			return;
545
		}
546
547
		isUserScrollable = /(scroll|auto)/.test( c.css( "overflow" ) );
548
549
		this.containment = [
550
			( parseInt( c.css( "borderLeftWidth" ), 10 ) || 0 ) +
551
				( parseInt( c.css( "paddingLeft" ), 10 ) || 0 ),
552
			( parseInt( c.css( "borderTopWidth" ), 10 ) || 0 ) +
553
				( parseInt( c.css( "paddingTop" ), 10 ) || 0 ),
554
			( isUserScrollable ? Math.max( ce.scrollWidth, ce.offsetWidth ) : ce.offsetWidth ) -
555
				( parseInt( c.css( "borderRightWidth" ), 10 ) || 0 ) -
556
				( parseInt( c.css( "paddingRight" ), 10 ) || 0 ) -
557
				this.helperProportions.width -
558
				this.margins.left -
559
				this.margins.right,
560
			( isUserScrollable ? Math.max( ce.scrollHeight, ce.offsetHeight ) : ce.offsetHeight ) -
561
				( parseInt( c.css( "borderBottomWidth" ), 10 ) || 0 ) -
562
				( parseInt( c.css( "paddingBottom" ), 10 ) || 0 ) -
563
				this.helperProportions.height -
564
				this.margins.top -
565
				this.margins.bottom
566
		];
567
		this.relativeContainer = c;
568
	},
569
570
	_convertPositionTo: function( d, pos ) {
571
572
		if ( !pos ) {
573
			pos = this.position;
574
		}
575
576
		var mod = d === "absolute" ? 1 : -1,
577
			scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] );
578
579
		return {
580
			top: (
581
582
				// The absolute mouse position
583
				pos.top	+
584
585
				// Only for relative positioned nodes: Relative offset from element to offset parent
586
				this.offset.relative.top * mod +
587
588
				// The offsetParent's offset without borders (offset + border)
589
				this.offset.parent.top * mod -
590
				( ( this.cssPosition === "fixed" ?
591
					-this.offset.scroll.top :
592
					( scrollIsRootNode ? 0 : this.offset.scroll.top ) ) * mod )
593
			),
594
			left: (
595
596
				// The absolute mouse position
597
				pos.left +
598
599
				// Only for relative positioned nodes: Relative offset from element to offset parent
600
				this.offset.relative.left * mod +
601
602
				// The offsetParent's offset without borders (offset + border)
603
				this.offset.parent.left * mod	-
604
				( ( this.cssPosition === "fixed" ?
605
					-this.offset.scroll.left :
606
					( scrollIsRootNode ? 0 : this.offset.scroll.left ) ) * mod )
607
			)
608
		};
609
610
	},
611
612
	_generatePosition: function( event, constrainPosition ) {
613
614
		var containment, co, top, left,
615
			o = this.options,
616
			scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] ),
617
			pageX = event.pageX,
618
			pageY = event.pageY;
619
620
		// Cache the scroll
621
		if ( !scrollIsRootNode || !this.offset.scroll ) {
622
			this.offset.scroll = {
623
				top: this.scrollParent.scrollTop(),
624
				left: this.scrollParent.scrollLeft()
625
			};
626
		}
627
628
		/*
629
		 * - Position constraining -
630
		 * Constrain the position to a mix of grid, containment.
631
		 */
632
633
		// If we are not dragging yet, we won't check for options
634 View Code Duplication
		if ( constrainPosition ) {
635
			if ( this.containment ) {
636
				if ( this.relativeContainer ) {
637
					co = this.relativeContainer.offset();
638
					containment = [
639
						this.containment[ 0 ] + co.left,
640
						this.containment[ 1 ] + co.top,
641
						this.containment[ 2 ] + co.left,
642
						this.containment[ 3 ] + co.top
643
					];
644
				} else {
645
					containment = this.containment;
646
				}
647
648
				if ( event.pageX - this.offset.click.left < containment[ 0 ] ) {
649
					pageX = containment[ 0 ] + this.offset.click.left;
650
				}
651
				if ( event.pageY - this.offset.click.top < containment[ 1 ] ) {
652
					pageY = containment[ 1 ] + this.offset.click.top;
653
				}
654
				if ( event.pageX - this.offset.click.left > containment[ 2 ] ) {
655
					pageX = containment[ 2 ] + this.offset.click.left;
656
				}
657
				if ( event.pageY - this.offset.click.top > containment[ 3 ] ) {
658
					pageY = containment[ 3 ] + this.offset.click.top;
659
				}
660
			}
661
662
			if ( o.grid ) {
663
664
				//Check for grid elements set to 0 to prevent divide by 0 error causing invalid
665
				// argument errors in IE (see ticket #6950)
666
				top = o.grid[ 1 ] ? this.originalPageY + Math.round( ( pageY -
667
					this.originalPageY ) / o.grid[ 1 ] ) * o.grid[ 1 ] : this.originalPageY;
668
				pageY = containment ? ( ( top - this.offset.click.top >= containment[ 1 ] ||
669
					top - this.offset.click.top > containment[ 3 ] ) ?
670
						top :
671
						( ( top - this.offset.click.top >= containment[ 1 ] ) ?
672
							top - o.grid[ 1 ] : top + o.grid[ 1 ] ) ) : top;
673
674
				left = o.grid[ 0 ] ? this.originalPageX +
675
					Math.round( ( pageX - this.originalPageX ) / o.grid[ 0 ] ) * o.grid[ 0 ] :
676
					this.originalPageX;
677
				pageX = containment ? ( ( left - this.offset.click.left >= containment[ 0 ] ||
678
					left - this.offset.click.left > containment[ 2 ] ) ?
679
						left :
680
						( ( left - this.offset.click.left >= containment[ 0 ] ) ?
681
							left - o.grid[ 0 ] : left + o.grid[ 0 ] ) ) : left;
682
			}
683
684
			if ( o.axis === "y" ) {
685
				pageX = this.originalPageX;
686
			}
687
688
			if ( o.axis === "x" ) {
689
				pageY = this.originalPageY;
690
			}
691
		}
692
693
		return {
694
			top: (
695
696
				// The absolute mouse position
697
				pageY -
698
699
				// Click offset (relative to the element)
700
				this.offset.click.top -
701
702
				// Only for relative positioned nodes: Relative offset from element to offset parent
703
				this.offset.relative.top -
704
705
				// The offsetParent's offset without borders (offset + border)
706
				this.offset.parent.top +
707
				( this.cssPosition === "fixed" ?
708
					-this.offset.scroll.top :
709
					( scrollIsRootNode ? 0 : this.offset.scroll.top ) )
710
			),
711
			left: (
712
713
				// The absolute mouse position
714
				pageX -
715
716
				// Click offset (relative to the element)
717
				this.offset.click.left -
718
719
				// Only for relative positioned nodes: Relative offset from element to offset parent
720
				this.offset.relative.left -
721
722
				// The offsetParent's offset without borders (offset + border)
723
				this.offset.parent.left +
724
				( this.cssPosition === "fixed" ?
725
					-this.offset.scroll.left :
726
					( scrollIsRootNode ? 0 : this.offset.scroll.left ) )
727
			)
728
		};
729
730
	},
731
732
	_clear: function() {
733
		this._removeClass( this.helper, "ui-draggable-dragging" );
734
		if ( this.helper[ 0 ] !== this.element[ 0 ] && !this.cancelHelperRemoval ) {
735
			this.helper.remove();
736
		}
737
		this.helper = null;
738
		this.cancelHelperRemoval = false;
739
		if ( this.destroyOnClear ) {
740
			this.destroy();
741
		}
742
	},
743
744
	// From now on bulk stuff - mainly helpers
745
746
	_trigger: function( type, event, ui ) {
747
		ui = ui || this._uiHash();
748
		$.ui.plugin.call( this, type, [ event, ui, this ], true );
749
750
		// Absolute position and offset (see #6884 ) have to be recalculated after plugins
751
		if ( /^(drag|start|stop)/.test( type ) ) {
752
			this.positionAbs = this._convertPositionTo( "absolute" );
753
			ui.offset = this.positionAbs;
754
		}
755
		return $.Widget.prototype._trigger.call( this, type, event, ui );
756
	},
757
758
	plugins: {},
759
760
	_uiHash: function() {
761
		return {
762
			helper: this.helper,
763
			position: this.position,
764
			originalPosition: this.originalPosition,
765
			offset: this.positionAbs
766
		};
767
	}
768
769
} );
770
771
$.ui.plugin.add( "draggable", "connectToSortable", {
772
	start: function( event, ui, draggable ) {
773
		var uiSortable = $.extend( {}, ui, {
774
			item: draggable.element
775
		} );
776
777
		draggable.sortables = [];
778
		$( draggable.options.connectToSortable ).each( function() {
779
			var sortable = $( this ).sortable( "instance" );
780
781
			if ( sortable && !sortable.options.disabled ) {
782
				draggable.sortables.push( sortable );
783
784
				// RefreshPositions is called at drag start to refresh the containerCache
785
				// which is used in drag. This ensures it's initialized and synchronized
786
				// with any changes that might have happened on the page since initialization.
787
				sortable.refreshPositions();
788
				sortable._trigger( "activate", event, uiSortable );
789
			}
790
		} );
791
	},
792
	stop: function( event, ui, draggable ) {
793
		var uiSortable = $.extend( {}, ui, {
794
			item: draggable.element
795
		} );
796
797
		draggable.cancelHelperRemoval = false;
798
799
		$.each( draggable.sortables, function() {
800
			var sortable = this;
801
802
			if ( sortable.isOver ) {
803
				sortable.isOver = 0;
804
805
				// Allow this sortable to handle removing the helper
806
				draggable.cancelHelperRemoval = true;
807
				sortable.cancelHelperRemoval = false;
808
809
				// Use _storedCSS To restore properties in the sortable,
810
				// as this also handles revert (#9675) since the draggable
811
				// may have modified them in unexpected ways (#8809)
812
				sortable._storedCSS = {
813
					position: sortable.placeholder.css( "position" ),
814
					top: sortable.placeholder.css( "top" ),
815
					left: sortable.placeholder.css( "left" )
816
				};
817
818
				sortable._mouseStop( event );
819
820
				// Once drag has ended, the sortable should return to using
821
				// its original helper, not the shared helper from draggable
822
				sortable.options.helper = sortable.options._helper;
823
			} else {
824
825
				// Prevent this Sortable from removing the helper.
826
				// However, don't set the draggable to remove the helper
827
				// either as another connected Sortable may yet handle the removal.
828
				sortable.cancelHelperRemoval = true;
829
830
				sortable._trigger( "deactivate", event, uiSortable );
831
			}
832
		} );
833
	},
834
	drag: function( event, ui, draggable ) {
835
		$.each( draggable.sortables, function() {
836
			var innermostIntersecting = false,
837
				sortable = this;
838
839
			// Copy over variables that sortable's _intersectsWith uses
840
			sortable.positionAbs = draggable.positionAbs;
841
			sortable.helperProportions = draggable.helperProportions;
842
			sortable.offset.click = draggable.offset.click;
843
844
			if ( sortable._intersectsWith( sortable.containerCache ) ) {
845
				innermostIntersecting = true;
846
847
				$.each( draggable.sortables, function() {
848
849
					// Copy over variables that sortable's _intersectsWith uses
850
					this.positionAbs = draggable.positionAbs;
851
					this.helperProportions = draggable.helperProportions;
852
					this.offset.click = draggable.offset.click;
853
854
					if ( this !== sortable &&
855
							this._intersectsWith( this.containerCache ) &&
856
							$.contains( sortable.element[ 0 ], this.element[ 0 ] ) ) {
857
						innermostIntersecting = false;
858
					}
859
860
					return innermostIntersecting;
861
				} );
862
			}
863
864
			if ( innermostIntersecting ) {
865
866
				// If it intersects, we use a little isOver variable and set it once,
867
				// so that the move-in stuff gets fired only once.
868
				if ( !sortable.isOver ) {
869
					sortable.isOver = 1;
870
871
					// Store draggable's parent in case we need to reappend to it later.
872
					draggable._parent = ui.helper.parent();
873
874
					sortable.currentItem = ui.helper
875
						.appendTo( sortable.element )
876
						.data( "ui-sortable-item", true );
877
878
					// Store helper option to later restore it
879
					sortable.options._helper = sortable.options.helper;
880
881
					sortable.options.helper = function() {
882
						return ui.helper[ 0 ];
883
					};
884
885
					// Fire the start events of the sortable with our passed browser event,
886
					// and our own helper (so it doesn't create a new one)
887
					event.target = sortable.currentItem[ 0 ];
888
					sortable._mouseCapture( event, true );
889
					sortable._mouseStart( event, true, true );
890
891
					// Because the browser event is way off the new appended portlet,
892
					// modify necessary variables to reflect the changes
893
					sortable.offset.click.top = draggable.offset.click.top;
894
					sortable.offset.click.left = draggable.offset.click.left;
895
					sortable.offset.parent.left -= draggable.offset.parent.left -
896
						sortable.offset.parent.left;
897
					sortable.offset.parent.top -= draggable.offset.parent.top -
898
						sortable.offset.parent.top;
899
900
					draggable._trigger( "toSortable", event );
901
902
					// Inform draggable that the helper is in a valid drop zone,
903
					// used solely in the revert option to handle "valid/invalid".
904
					draggable.dropped = sortable.element;
905
906
					// Need to refreshPositions of all sortables in the case that
907
					// adding to one sortable changes the location of the other sortables (#9675)
908
					$.each( draggable.sortables, function() {
909
						this.refreshPositions();
910
					} );
911
912
					// Hack so receive/update callbacks work (mostly)
913
					draggable.currentItem = draggable.element;
914
					sortable.fromOutside = draggable;
915
				}
916
917
				if ( sortable.currentItem ) {
918
					sortable._mouseDrag( event );
919
920
					// Copy the sortable's position because the draggable's can potentially reflect
921
					// a relative position, while sortable is always absolute, which the dragged
922
					// element has now become. (#8809)
923
					ui.position = sortable.position;
924
				}
925
			} else {
926
927
				// If it doesn't intersect with the sortable, and it intersected before,
928
				// we fake the drag stop of the sortable, but make sure it doesn't remove
929
				// the helper by using cancelHelperRemoval.
930
				if ( sortable.isOver ) {
931
932
					sortable.isOver = 0;
933
					sortable.cancelHelperRemoval = true;
934
935
					// Calling sortable's mouseStop would trigger a revert,
936
					// so revert must be temporarily false until after mouseStop is called.
937
					sortable.options._revert = sortable.options.revert;
938
					sortable.options.revert = false;
939
940
					sortable._trigger( "out", event, sortable._uiHash( sortable ) );
941
					sortable._mouseStop( event, true );
942
943
					// Restore sortable behaviors that were modfied
944
					// when the draggable entered the sortable area (#9481)
945
					sortable.options.revert = sortable.options._revert;
946
					sortable.options.helper = sortable.options._helper;
947
948
					if ( sortable.placeholder ) {
949
						sortable.placeholder.remove();
950
					}
951
952
					// Restore and recalculate the draggable's offset considering the sortable
953
					// may have modified them in unexpected ways. (#8809, #10669)
954
					ui.helper.appendTo( draggable._parent );
955
					draggable._refreshOffsets( event );
956
					ui.position = draggable._generatePosition( event, true );
957
958
					draggable._trigger( "fromSortable", event );
959
960
					// Inform draggable that the helper is no longer in a valid drop zone
961
					draggable.dropped = false;
962
963
					// Need to refreshPositions of all sortables just in case removing
964
					// from one sortable changes the location of other sortables (#9675)
965
					$.each( draggable.sortables, function() {
966
						this.refreshPositions();
967
					} );
968
				}
969
			}
970
		} );
971
	}
972
} );
973
974
$.ui.plugin.add( "draggable", "cursor", {
975
	start: function( event, ui, instance ) {
976
		var t = $( "body" ),
977
			o = instance.options;
978
979
		if ( t.css( "cursor" ) ) {
980
			o._cursor = t.css( "cursor" );
981
		}
982
		t.css( "cursor", o.cursor );
983
	},
984
	stop: function( event, ui, instance ) {
985
		var o = instance.options;
986
		if ( o._cursor ) {
987
			$( "body" ).css( "cursor", o._cursor );
988
		}
989
	}
990
} );
991
992
$.ui.plugin.add( "draggable", "opacity", {
993
	start: function( event, ui, instance ) {
994
		var t = $( ui.helper ),
995
			o = instance.options;
996
		if ( t.css( "opacity" ) ) {
997
			o._opacity = t.css( "opacity" );
998
		}
999
		t.css( "opacity", o.opacity );
1000
	},
1001
	stop: function( event, ui, instance ) {
1002
		var o = instance.options;
1003
		if ( o._opacity ) {
1004
			$( ui.helper ).css( "opacity", o._opacity );
1005
		}
1006
	}
1007
} );
1008
1009
$.ui.plugin.add( "draggable", "scroll", {
1010
	start: function( event, ui, i ) {
1011
		if ( !i.scrollParentNotHidden ) {
1012
			i.scrollParentNotHidden = i.helper.scrollParent( false );
1013
		}
1014
1015
		if ( i.scrollParentNotHidden[ 0 ] !== i.document[ 0 ] &&
1016
				i.scrollParentNotHidden[ 0 ].tagName !== "HTML" ) {
1017
			i.overflowOffset = i.scrollParentNotHidden.offset();
1018
		}
1019
	},
1020
	drag: function( event, ui, i  ) {
1021
1022
		var o = i.options,
1023
			scrolled = false,
1024
			scrollParent = i.scrollParentNotHidden[ 0 ],
1025
			document = i.document[ 0 ];
1026
1027 View Code Duplication
		if ( scrollParent !== document && scrollParent.tagName !== "HTML" ) {
1028
			if ( !o.axis || o.axis !== "x" ) {
1029
				if ( ( i.overflowOffset.top + scrollParent.offsetHeight ) - event.pageY <
1030
						o.scrollSensitivity ) {
1031
					scrollParent.scrollTop = scrolled = scrollParent.scrollTop + o.scrollSpeed;
1032
				} else if ( event.pageY - i.overflowOffset.top < o.scrollSensitivity ) {
1033
					scrollParent.scrollTop = scrolled = scrollParent.scrollTop - o.scrollSpeed;
1034
				}
1035
			}
1036
1037
			if ( !o.axis || o.axis !== "y" ) {
1038
				if ( ( i.overflowOffset.left + scrollParent.offsetWidth ) - event.pageX <
1039
						o.scrollSensitivity ) {
1040
					scrollParent.scrollLeft = scrolled = scrollParent.scrollLeft + o.scrollSpeed;
1041
				} else if ( event.pageX - i.overflowOffset.left < o.scrollSensitivity ) {
1042
					scrollParent.scrollLeft = scrolled = scrollParent.scrollLeft - o.scrollSpeed;
1043
				}
1044
			}
1045
1046
		} else {
1047
1048
			if ( !o.axis || o.axis !== "x" ) {
1049
				if ( event.pageY - $( document ).scrollTop() < o.scrollSensitivity ) {
1050
					scrolled = $( document ).scrollTop( $( document ).scrollTop() - o.scrollSpeed );
1051
				} else if ( $( window ).height() - ( event.pageY - $( document ).scrollTop() ) <
1052
						o.scrollSensitivity ) {
1053
					scrolled = $( document ).scrollTop( $( document ).scrollTop() + o.scrollSpeed );
1054
				}
1055
			}
1056
1057
			if ( !o.axis || o.axis !== "y" ) {
1058
				if ( event.pageX - $( document ).scrollLeft() < o.scrollSensitivity ) {
1059
					scrolled = $( document ).scrollLeft(
1060
						$( document ).scrollLeft() - o.scrollSpeed
1061
					);
1062
				} else if ( $( window ).width() - ( event.pageX - $( document ).scrollLeft() ) <
1063
						o.scrollSensitivity ) {
1064
					scrolled = $( document ).scrollLeft(
1065
						$( document ).scrollLeft() + o.scrollSpeed
1066
					);
1067
				}
1068
			}
1069
1070
		}
1071
1072
		if ( scrolled !== false && $.ui.ddmanager && !o.dropBehaviour ) {
1073
			$.ui.ddmanager.prepareOffsets( i, event );
1074
		}
1075
1076
	}
1077
} );
1078
1079
$.ui.plugin.add( "draggable", "snap", {
1080
	start: function( event, ui, i ) {
1081
1082
		var o = i.options;
1083
1084
		i.snapElements = [];
1085
1086
		$( o.snap.constructor !== String ? ( o.snap.items || ":data(ui-draggable)" ) : o.snap )
1087
			.each( function() {
1088
				var $t = $( this ),
1089
					$o = $t.offset();
1090
				if ( this !== i.element[ 0 ] ) {
1091
					i.snapElements.push( {
1092
						item: this,
1093
						width: $t.outerWidth(), height: $t.outerHeight(),
1094
						top: $o.top, left: $o.left
1095
					} );
1096
				}
1097
			} );
1098
1099
	},
1100
	drag: function( event, ui, inst ) {
1101
1102
		var ts, bs, ls, rs, l, r, t, b, i, first,
1103
			o = inst.options,
1104
			d = o.snapTolerance,
1105
			x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width,
1106
			y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height;
1107
1108
		for ( i = inst.snapElements.length - 1; i >= 0; i-- ) {
1109
1110
			l = inst.snapElements[ i ].left - inst.margins.left;
1111
			r = l + inst.snapElements[ i ].width;
1112
			t = inst.snapElements[ i ].top - inst.margins.top;
1113
			b = t + inst.snapElements[ i ].height;
1114
1115
			if ( x2 < l - d || x1 > r + d || y2 < t - d || y1 > b + d ||
1116
					!$.contains( inst.snapElements[ i ].item.ownerDocument,
1117
					inst.snapElements[ i ].item ) ) {
1118
				if ( inst.snapElements[ i ].snapping ) {
1119
					( inst.options.snap.release &&
1120
						inst.options.snap.release.call(
1121
							inst.element,
1122
							event,
1123
							$.extend( inst._uiHash(), { snapItem: inst.snapElements[ i ].item } )
1124
						) );
1125
				}
1126
				inst.snapElements[ i ].snapping = false;
1127
				continue;
1128
			}
1129
1130 View Code Duplication
			if ( o.snapMode !== "inner" ) {
1131
				ts = Math.abs( t - y2 ) <= d;
1132
				bs = Math.abs( b - y1 ) <= d;
1133
				ls = Math.abs( l - x2 ) <= d;
1134
				rs = Math.abs( r - x1 ) <= d;
1135
				if ( ts ) {
1136
					ui.position.top = inst._convertPositionTo( "relative", {
1137
						top: t - inst.helperProportions.height,
1138
						left: 0
1139
					} ).top;
1140
				}
1141
				if ( bs ) {
1142
					ui.position.top = inst._convertPositionTo( "relative", {
1143
						top: b,
1144
						left: 0
1145
					} ).top;
1146
				}
1147
				if ( ls ) {
1148
					ui.position.left = inst._convertPositionTo( "relative", {
1149
						top: 0,
1150
						left: l - inst.helperProportions.width
1151
					} ).left;
1152
				}
1153
				if ( rs ) {
1154
					ui.position.left = inst._convertPositionTo( "relative", {
1155
						top: 0,
1156
						left: r
1157
					} ).left;
1158
				}
1159
			}
1160
1161
			first = ( ts || bs || ls || rs );
1162
1163 View Code Duplication
			if ( o.snapMode !== "outer" ) {
1164
				ts = Math.abs( t - y1 ) <= d;
1165
				bs = Math.abs( b - y2 ) <= d;
1166
				ls = Math.abs( l - x1 ) <= d;
1167
				rs = Math.abs( r - x2 ) <= d;
1168
				if ( ts ) {
1169
					ui.position.top = inst._convertPositionTo( "relative", {
1170
						top: t,
1171
						left: 0
1172
					} ).top;
1173
				}
1174
				if ( bs ) {
1175
					ui.position.top = inst._convertPositionTo( "relative", {
1176
						top: b - inst.helperProportions.height,
1177
						left: 0
1178
					} ).top;
1179
				}
1180
				if ( ls ) {
1181
					ui.position.left = inst._convertPositionTo( "relative", {
1182
						top: 0,
1183
						left: l
1184
					} ).left;
1185
				}
1186
				if ( rs ) {
1187
					ui.position.left = inst._convertPositionTo( "relative", {
1188
						top: 0,
1189
						left: r - inst.helperProportions.width
1190
					} ).left;
1191
				}
1192
			}
1193
1194
			if ( !inst.snapElements[ i ].snapping && ( ts || bs || ls || rs || first ) ) {
1195
				( inst.options.snap.snap &&
1196
					inst.options.snap.snap.call(
1197
						inst.element,
1198
						event,
1199
						$.extend( inst._uiHash(), {
1200
							snapItem: inst.snapElements[ i ].item
1201
						} ) ) );
1202
			}
1203
			inst.snapElements[ i ].snapping = ( ts || bs || ls || rs || first );
1204
1205
		}
1206
1207
	}
1208
} );
1209
1210
$.ui.plugin.add( "draggable", "stack", {
1211
	start: function( event, ui, instance ) {
1212
		var min,
1213
			o = instance.options,
1214
			group = $.makeArray( $( o.stack ) ).sort( function( a, b ) {
1215
				return ( parseInt( $( a ).css( "zIndex" ), 10 ) || 0 ) -
1216
					( parseInt( $( b ).css( "zIndex" ), 10 ) || 0 );
1217
			} );
1218
1219
		if ( !group.length ) { return; }
1220
1221
		min = parseInt( $( group[ 0 ] ).css( "zIndex" ), 10 ) || 0;
1222
		$( group ).each( function( i ) {
1223
			$( this ).css( "zIndex", min + i );
1224
		} );
1225
		this.css( "zIndex", ( min + group.length ) );
1226
	}
1227
} );
1228
1229
$.ui.plugin.add( "draggable", "zIndex", {
1230
	start: function( event, ui, instance ) {
1231
		var t = $( ui.helper ),
1232
			o = instance.options;
1233
1234
		if ( t.css( "zIndex" ) ) {
1235
			o._zIndex = t.css( "zIndex" );
1236
		}
1237
		t.css( "zIndex", o.zIndex );
1238
	},
1239
	stop: function( event, ui, instance ) {
1240
		var o = instance.options;
1241
1242
		if ( o._zIndex ) {
1243
			$( ui.helper ).css( "zIndex", o._zIndex );
1244
		}
1245
	}
1246
} );
1247
1248
return $.ui.draggable;
1249
1250
} ) );
1251