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

Complexity

Total Complexity 333
Complexity/F 5.29

Size

Lines of Code 1538
Function Count 63

Duplication

Duplicated Lines 197
Ratio 12.81 %

Importance

Changes 0
Metric Value
cc 0
nc 0
dl 197
loc 1538
rs 2.4
c 0
b 0
f 0
wmc 333
mnd 5
bc 227
fnc 63
bpm 3.6031
cpm 5.2857
noi 1

1 Function

Rating   Name   Duplication   Size   Complexity  
B sortable.js ➔ ?!? 197 1520 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/sortable.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 Sortable 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: Sortable
11
//>>group: Interactions
12
//>>description: Enables items in a list to be sorted using the mouse.
13
//>>docs: http://api.jqueryui.com/sortable/
14
//>>demos: http://jqueryui.com/sortable/
15
//>>css.structure: ../../themes/base/sortable.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
			"../ie",
26
			"../scroll-parent",
27
			"../version",
28
			"../widget"
29
		], factory );
30
	} else {
31
32
		// Browser globals
33
		factory( jQuery );
34
	}
35
}( function( $ ) {
36
37
return $.widget( "ui.sortable", $.ui.mouse, {
38
	version: "1.12.1",
39
	widgetEventPrefix: "sort",
40
	ready: false,
41
	options: {
42
		appendTo: "parent",
43
		axis: false,
44
		connectWith: false,
45
		containment: false,
46
		cursor: "auto",
47
		cursorAt: false,
48
		dropOnEmpty: true,
49
		forcePlaceholderSize: false,
50
		forceHelperSize: false,
51
		grid: false,
52
		handle: false,
53
		helper: "original",
54
		items: "> *",
55
		opacity: false,
56
		placeholder: false,
57
		revert: false,
58
		scroll: true,
59
		scrollSensitivity: 20,
60
		scrollSpeed: 20,
61
		scope: "default",
62
		tolerance: "intersect",
63
		zIndex: 1000,
64
65
		// Callbacks
66
		activate: null,
67
		beforeStop: null,
68
		change: null,
69
		deactivate: null,
70
		out: null,
71
		over: null,
72
		receive: null,
73
		remove: null,
74
		sort: null,
75
		start: null,
76
		stop: null,
77
		update: null
78
	},
79
80
	_isOverAxis: function( x, reference, size ) {
81
		return ( x >= reference ) && ( x < ( reference + size ) );
82
	},
83
84
	_isFloating: function( item ) {
85
		return ( /left|right/ ).test( item.css( "float" ) ) ||
86
			( /inline|table-cell/ ).test( item.css( "display" ) );
87
	},
88
89
	_create: function() {
90
		this.containerCache = {};
91
		this._addClass( "ui-sortable" );
92
93
		//Get the items
94
		this.refresh();
95
96
		//Let's determine the parent's offset
97
		this.offset = this.element.offset();
98
99
		//Initialize mouse events for interaction
100
		this._mouseInit();
101
102
		this._setHandleClassName();
103
104
		//We're ready to go
105
		this.ready = true;
106
107
	},
108
109
	_setOption: function( key, value ) {
110
		this._super( key, value );
111
112
		if ( key === "handle" ) {
113
			this._setHandleClassName();
114
		}
115
	},
116
117
	_setHandleClassName: function() {
118
		var that = this;
119
		this._removeClass( this.element.find( ".ui-sortable-handle" ), "ui-sortable-handle" );
120
		$.each( this.items, function() {
121
			that._addClass(
122
				this.instance.options.handle ?
123
					this.item.find( this.instance.options.handle ) :
124
					this.item,
125
				"ui-sortable-handle"
126
			);
127
		} );
128
	},
129
130
	_destroy: function() {
131
		this._mouseDestroy();
132
133
		for ( var i = this.items.length - 1; i >= 0; i-- ) {
134
			this.items[ i ].item.removeData( this.widgetName + "-item" );
135
		}
136
137
		return this;
138
	},
139
140
	_mouseCapture: function( event, overrideHandle ) {
141
		var currentItem = null,
142
			validHandle = false,
143
			that = this;
144
145
		if ( this.reverting ) {
146
			return false;
147
		}
148
149
		if ( this.options.disabled || this.options.type === "static" ) {
150
			return false;
151
		}
152
153
		//We have to refresh the items data once first
154
		this._refreshItems( event );
155
156
		//Find out if the clicked node (or one of its parents) is a actual item in this.items
157
		$( event.target ).parents().each( function() {
158
			if ( $.data( this, that.widgetName + "-item" ) === that ) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if $.data(this, that.widgetName + "-item") === that is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
159
				currentItem = $( this );
160
				return false;
161
			}
162
		} );
163
		if ( $.data( event.target, that.widgetName + "-item" ) === that ) {
164
			currentItem = $( event.target );
165
		}
166
167
		if ( !currentItem ) {
168
			return false;
169
		}
170
		if ( this.options.handle && !overrideHandle ) {
171
			$( this.options.handle, currentItem ).find( "*" ).addBack().each( function() {
172
				if ( this === event.target ) {
173
					validHandle = true;
174
				}
175
			} );
176
			if ( !validHandle ) {
177
				return false;
178
			}
179
		}
180
181
		this.currentItem = currentItem;
182
		this._removeCurrentsFromItems();
183
		return true;
184
185
	},
186
187
	_mouseStart: function( event, overrideHandle, noActivation ) {
188
189
		var i, body,
190
			o = this.options;
191
192
		this.currentContainer = this;
193
194
		//We only need to call refreshPositions, because the refreshItems call has been moved to
195
		// mouseCapture
196
		this.refreshPositions();
197
198
		//Create and append the visible helper
199
		this.helper = this._createHelper( event );
200
201
		//Cache the helper size
202
		this._cacheHelperProportions();
203
204
		/*
205
		 * - Position generation -
206
		 * This block generates everything position related - it's the core of draggables.
207
		 */
208
209
		//Cache the margins of the original element
210
		this._cacheMargins();
211
212
		//Get the next scrolling parent
213
		this.scrollParent = this.helper.scrollParent();
214
215
		//The element's absolute position on the page minus margins
216
		this.offset = this.currentItem.offset();
217
		this.offset = {
218
			top: this.offset.top - this.margins.top,
219
			left: this.offset.left - this.margins.left
220
		};
221
222
		$.extend( this.offset, {
223
			click: { //Where the click happened, relative to the element
224
				left: event.pageX - this.offset.left,
225
				top: event.pageY - this.offset.top
226
			},
227
			parent: this._getParentOffset(),
228
229
			// This is a relative to absolute position minus the actual position calculation -
230
			// only used for relative positioned helper
231
			relative: this._getRelativeOffset()
232
		} );
233
234
		// Only after we got the offset, we can change the helper's position to absolute
235
		// TODO: Still need to figure out a way to make relative sorting possible
236
		this.helper.css( "position", "absolute" );
237
		this.cssPosition = this.helper.css( "position" );
238
239
		//Generate the original position
240
		this.originalPosition = this._generatePosition( event );
241
		this.originalPageX = event.pageX;
242
		this.originalPageY = event.pageY;
243
244
		//Adjust the mouse offset relative to the helper if "cursorAt" is supplied
245
		( o.cursorAt && this._adjustOffsetFromHelper( o.cursorAt ) );
246
247
		//Cache the former DOM position
248
		this.domPosition = {
249
			prev: this.currentItem.prev()[ 0 ],
250
			parent: this.currentItem.parent()[ 0 ]
251
		};
252
253
		// If the helper is not the original, hide the original so it's not playing any role during
254
		// the drag, won't cause anything bad this way
255
		if ( this.helper[ 0 ] !== this.currentItem[ 0 ] ) {
256
			this.currentItem.hide();
257
		}
258
259
		//Create the placeholder
260
		this._createPlaceholder();
261
262
		//Set a containment if given in the options
263
		if ( o.containment ) {
264
			this._setContainment();
265
		}
266
267
		if ( o.cursor && o.cursor !== "auto" ) { // cursor option
268
			body = this.document.find( "body" );
269
270
			// Support: IE
271
			this.storedCursor = body.css( "cursor" );
272
			body.css( "cursor", o.cursor );
273
274
			this.storedStylesheet =
275
				$( "<style>*{ cursor: " + o.cursor + " !important; }</style>" ).appendTo( body );
276
		}
277
278
		if ( o.opacity ) { // opacity option
279
			if ( this.helper.css( "opacity" ) ) {
280
				this._storedOpacity = this.helper.css( "opacity" );
281
			}
282
			this.helper.css( "opacity", o.opacity );
283
		}
284
285
		if ( o.zIndex ) { // zIndex option
286
			if ( this.helper.css( "zIndex" ) ) {
287
				this._storedZIndex = this.helper.css( "zIndex" );
288
			}
289
			this.helper.css( "zIndex", o.zIndex );
290
		}
291
292
		//Prepare scrolling
293
		if ( this.scrollParent[ 0 ] !== this.document[ 0 ] &&
294
				this.scrollParent[ 0 ].tagName !== "HTML" ) {
295
			this.overflowOffset = this.scrollParent.offset();
296
		}
297
298
		//Call callbacks
299
		this._trigger( "start", event, this._uiHash() );
300
301
		//Recache the helper size
302
		if ( !this._preserveHelperProportions ) {
303
			this._cacheHelperProportions();
304
		}
305
306
		//Post "activate" events to possible containers
307
		if ( !noActivation ) {
308
			for ( i = this.containers.length - 1; i >= 0; i-- ) {
309
				this.containers[ i ]._trigger( "activate", event, this._uiHash( this ) );
310
			}
311
		}
312
313
		//Prepare possible droppables
314
		if ( $.ui.ddmanager ) {
315
			$.ui.ddmanager.current = this;
316
		}
317
318
		if ( $.ui.ddmanager && !o.dropBehaviour ) {
319
			$.ui.ddmanager.prepareOffsets( this, event );
320
		}
321
322
		this.dragging = true;
323
324
		this._addClass( this.helper, "ui-sortable-helper" );
325
326
		// Execute the drag once - this causes the helper not to be visiblebefore getting its
327
		// correct position
328
		this._mouseDrag( event );
329
		return true;
330
331
	},
332
333
	_mouseDrag: function( event ) {
334
		var i, item, itemElement, intersection,
335
			o = this.options,
336
			scrolled = false;
337
338
		//Compute the helpers position
339
		this.position = this._generatePosition( event );
340
		this.positionAbs = this._convertPositionTo( "absolute" );
341
342
		if ( !this.lastPositionAbs ) {
343
			this.lastPositionAbs = this.positionAbs;
344
		}
345
346
		//Do scrolling
347 View Code Duplication
		if ( this.options.scroll ) {
348
			if ( this.scrollParent[ 0 ] !== this.document[ 0 ] &&
349
					this.scrollParent[ 0 ].tagName !== "HTML" ) {
350
351
				if ( ( this.overflowOffset.top + this.scrollParent[ 0 ].offsetHeight ) -
352
						event.pageY < o.scrollSensitivity ) {
353
					this.scrollParent[ 0 ].scrollTop =
354
						scrolled = this.scrollParent[ 0 ].scrollTop + o.scrollSpeed;
355
				} else if ( event.pageY - this.overflowOffset.top < o.scrollSensitivity ) {
356
					this.scrollParent[ 0 ].scrollTop =
357
						scrolled = this.scrollParent[ 0 ].scrollTop - o.scrollSpeed;
358
				}
359
360
				if ( ( this.overflowOffset.left + this.scrollParent[ 0 ].offsetWidth ) -
361
						event.pageX < o.scrollSensitivity ) {
362
					this.scrollParent[ 0 ].scrollLeft = scrolled =
363
						this.scrollParent[ 0 ].scrollLeft + o.scrollSpeed;
364
				} else if ( event.pageX - this.overflowOffset.left < o.scrollSensitivity ) {
365
					this.scrollParent[ 0 ].scrollLeft = scrolled =
366
						this.scrollParent[ 0 ].scrollLeft - o.scrollSpeed;
367
				}
368
369
			} else {
370
371
				if ( event.pageY - this.document.scrollTop() < o.scrollSensitivity ) {
372
					scrolled = this.document.scrollTop( this.document.scrollTop() - o.scrollSpeed );
373
				} else if ( this.window.height() - ( event.pageY - this.document.scrollTop() ) <
374
						o.scrollSensitivity ) {
375
					scrolled = this.document.scrollTop( this.document.scrollTop() + o.scrollSpeed );
376
				}
377
378
				if ( event.pageX - this.document.scrollLeft() < o.scrollSensitivity ) {
379
					scrolled = this.document.scrollLeft(
380
						this.document.scrollLeft() - o.scrollSpeed
381
					);
382
				} else if ( this.window.width() - ( event.pageX - this.document.scrollLeft() ) <
383
						o.scrollSensitivity ) {
384
					scrolled = this.document.scrollLeft(
385
						this.document.scrollLeft() + o.scrollSpeed
386
					);
387
				}
388
389
			}
390
391
			if ( scrolled !== false && $.ui.ddmanager && !o.dropBehaviour ) {
392
				$.ui.ddmanager.prepareOffsets( this, event );
393
			}
394
		}
395
396
		//Regenerate the absolute position used for position checks
397
		this.positionAbs = this._convertPositionTo( "absolute" );
398
399
		//Set the helper position
400
		if ( !this.options.axis || this.options.axis !== "y" ) {
401
			this.helper[ 0 ].style.left = this.position.left + "px";
402
		}
403
		if ( !this.options.axis || this.options.axis !== "x" ) {
404
			this.helper[ 0 ].style.top = this.position.top + "px";
405
		}
406
407
		//Rearrange
408
		for ( i = this.items.length - 1; i >= 0; i-- ) {
409
410
			//Cache variables and intersection, continue if no intersection
411
			item = this.items[ i ];
412
			itemElement = item.item[ 0 ];
413
			intersection = this._intersectsWithPointer( item );
414
			if ( !intersection ) {
415
				continue;
416
			}
417
418
			// Only put the placeholder inside the current Container, skip all
419
			// items from other containers. This works because when moving
420
			// an item from one container to another the
421
			// currentContainer is switched before the placeholder is moved.
422
			//
423
			// Without this, moving items in "sub-sortables" can cause
424
			// the placeholder to jitter between the outer and inner container.
425
			if ( item.instance !== this.currentContainer ) {
426
				continue;
427
			}
428
429
			// Cannot intersect with itself
430
			// no useless actions that have been done before
431
			// no action if the item moved is the parent of the item checked
432
			if ( itemElement !== this.currentItem[ 0 ] &&
433
				this.placeholder[ intersection === 1 ? "next" : "prev" ]()[ 0 ] !== itemElement &&
434
				!$.contains( this.placeholder[ 0 ], itemElement ) &&
435
				( this.options.type === "semi-dynamic" ?
436
					!$.contains( this.element[ 0 ], itemElement ) :
437
					true
438
				)
439
			) {
440
441
				this.direction = intersection === 1 ? "down" : "up";
442
443
				if ( this.options.tolerance === "pointer" || this._intersectsWithSides( item ) ) {
444
					this._rearrange( event, item );
445
				} else {
446
					break;
447
				}
448
449
				this._trigger( "change", event, this._uiHash() );
450
				break;
451
			}
452
		}
453
454
		//Post events to containers
455
		this._contactContainers( event );
456
457
		//Interconnect with droppables
458
		if ( $.ui.ddmanager ) {
459
			$.ui.ddmanager.drag( this, event );
460
		}
461
462
		//Call callbacks
463
		this._trigger( "sort", event, this._uiHash() );
464
465
		this.lastPositionAbs = this.positionAbs;
466
		return false;
467
468
	},
469
470
	_mouseStop: function( event, noPropagation ) {
471
472
		if ( !event ) {
473
			return;
474
		}
475
476
		//If we are using droppables, inform the manager about the drop
477
		if ( $.ui.ddmanager && !this.options.dropBehaviour ) {
478
			$.ui.ddmanager.drop( this, event );
479
		}
480
481
		if ( this.options.revert ) {
482
			var that = this,
483
				cur = this.placeholder.offset(),
484
				axis = this.options.axis,
485
				animation = {};
486
487
			if ( !axis || axis === "x" ) {
488
				animation.left = cur.left - this.offset.parent.left - this.margins.left +
489
					( this.offsetParent[ 0 ] === this.document[ 0 ].body ?
490
						0 :
491
						this.offsetParent[ 0 ].scrollLeft
492
					);
493
			}
494
			if ( !axis || axis === "y" ) {
495
				animation.top = cur.top - this.offset.parent.top - this.margins.top +
496
					( this.offsetParent[ 0 ] === this.document[ 0 ].body ?
497
						0 :
498
						this.offsetParent[ 0 ].scrollTop
499
					);
500
			}
501
			this.reverting = true;
502
			$( this.helper ).animate(
503
				animation,
504
				parseInt( this.options.revert, 10 ) || 500,
505
				function() {
506
					that._clear( event );
507
				}
508
			);
509
		} else {
510
			this._clear( event, noPropagation );
511
		}
512
513
		return false;
514
515
	},
516
517
	cancel: function() {
518
519
		if ( this.dragging ) {
520
521
			this._mouseUp( new $.Event( "mouseup", { target: null } ) );
522
523
			if ( this.options.helper === "original" ) {
524
				this.currentItem.css( this._storedCSS );
525
				this._removeClass( this.currentItem, "ui-sortable-helper" );
526
			} else {
527
				this.currentItem.show();
528
			}
529
530
			//Post deactivating events to containers
531
			for ( var i = this.containers.length - 1; i >= 0; i-- ) {
532
				this.containers[ i ]._trigger( "deactivate", null, this._uiHash( this ) );
533
				if ( this.containers[ i ].containerCache.over ) {
534
					this.containers[ i ]._trigger( "out", null, this._uiHash( this ) );
535
					this.containers[ i ].containerCache.over = 0;
536
				}
537
			}
538
539
		}
540
541
		if ( this.placeholder ) {
542
543
			//$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately,
544
			// it unbinds ALL events from the original node!
545
			if ( this.placeholder[ 0 ].parentNode ) {
546
				this.placeholder[ 0 ].parentNode.removeChild( this.placeholder[ 0 ] );
547
			}
548
			if ( this.options.helper !== "original" && this.helper &&
549
					this.helper[ 0 ].parentNode ) {
550
				this.helper.remove();
551
			}
552
553
			$.extend( this, {
554
				helper: null,
555
				dragging: false,
556
				reverting: false,
557
				_noFinalSort: null
558
			} );
559
560
			if ( this.domPosition.prev ) {
561
				$( this.domPosition.prev ).after( this.currentItem );
562
			} else {
563
				$( this.domPosition.parent ).prepend( this.currentItem );
564
			}
565
		}
566
567
		return this;
568
569
	},
570
571
	serialize: function( o ) {
572
573
		var items = this._getItemsAsjQuery( o && o.connected ),
574
			str = [];
575
		o = o || {};
576
577
		$( items ).each( function() {
578
			var res = ( $( o.item || this ).attr( o.attribute || "id" ) || "" )
579
				.match( o.expression || ( /(.+)[\-=_](.+)/ ) );
580
			if ( res ) {
581
				str.push(
582
					( o.key || res[ 1 ] + "[]" ) +
583
					"=" + ( o.key && o.expression ? res[ 1 ] : res[ 2 ] ) );
584
			}
585
		} );
586
587
		if ( !str.length && o.key ) {
588
			str.push( o.key + "=" );
589
		}
590
591
		return str.join( "&" );
592
593
	},
594
595
	toArray: function( o ) {
596
597
		var items = this._getItemsAsjQuery( o && o.connected ),
598
			ret = [];
599
600
		o = o || {};
601
602
		items.each( function() {
603
			ret.push( $( o.item || this ).attr( o.attribute || "id" ) || "" );
604
		} );
605
		return ret;
606
607
	},
608
609
	/* Be careful with the following core functions */
610
	_intersectsWith: function( item ) {
611
612
		var x1 = this.positionAbs.left,
613
			x2 = x1 + this.helperProportions.width,
614
			y1 = this.positionAbs.top,
615
			y2 = y1 + this.helperProportions.height,
616
			l = item.left,
617
			r = l + item.width,
618
			t = item.top,
619
			b = t + item.height,
620
			dyClick = this.offset.click.top,
621
			dxClick = this.offset.click.left,
622
			isOverElementHeight = ( this.options.axis === "x" ) || ( ( y1 + dyClick ) > t &&
623
				( y1 + dyClick ) < b ),
624
			isOverElementWidth = ( this.options.axis === "y" ) || ( ( x1 + dxClick ) > l &&
625
				( x1 + dxClick ) < r ),
626
			isOverElement = isOverElementHeight && isOverElementWidth;
627
628
		if ( this.options.tolerance === "pointer" ||
629
			this.options.forcePointerForContainers ||
630
			( this.options.tolerance !== "pointer" &&
631
				this.helperProportions[ this.floating ? "width" : "height" ] >
632
				item[ this.floating ? "width" : "height" ] )
633
		) {
634
			return isOverElement;
635
		} else {
636
637
			return ( l < x1 + ( this.helperProportions.width / 2 ) && // Right Half
638
				x2 - ( this.helperProportions.width / 2 ) < r && // Left Half
639
				t < y1 + ( this.helperProportions.height / 2 ) && // Bottom Half
640
				y2 - ( this.helperProportions.height / 2 ) < b ); // Top Half
641
642
		}
643
	},
644
645
	_intersectsWithPointer: function( item ) {
646
		var verticalDirection, horizontalDirection,
647
			isOverElementHeight = ( this.options.axis === "x" ) ||
648
				this._isOverAxis(
649
					this.positionAbs.top + this.offset.click.top, item.top, item.height ),
650
			isOverElementWidth = ( this.options.axis === "y" ) ||
651
				this._isOverAxis(
652
					this.positionAbs.left + this.offset.click.left, item.left, item.width ),
653
			isOverElement = isOverElementHeight && isOverElementWidth;
654
655
		if ( !isOverElement ) {
656
			return false;
657
		}
658
659
		verticalDirection = this._getDragVerticalDirection();
660
		horizontalDirection = this._getDragHorizontalDirection();
661
662
		return this.floating ?
663
			( ( horizontalDirection === "right" || verticalDirection === "down" ) ? 2 : 1 )
664
			: ( verticalDirection && ( verticalDirection === "down" ? 2 : 1 ) );
665
666
	},
667
668
	_intersectsWithSides: function( item ) {
669
670
		var isOverBottomHalf = this._isOverAxis( this.positionAbs.top +
671
				this.offset.click.top, item.top + ( item.height / 2 ), item.height ),
672
			isOverRightHalf = this._isOverAxis( this.positionAbs.left +
673
				this.offset.click.left, item.left + ( item.width / 2 ), item.width ),
674
			verticalDirection = this._getDragVerticalDirection(),
675
			horizontalDirection = this._getDragHorizontalDirection();
676
677
		if ( this.floating && horizontalDirection ) {
678
			return ( ( horizontalDirection === "right" && isOverRightHalf ) ||
679
				( horizontalDirection === "left" && !isOverRightHalf ) );
680
		} else {
681
			return verticalDirection && ( ( verticalDirection === "down" && isOverBottomHalf ) ||
682
				( verticalDirection === "up" && !isOverBottomHalf ) );
683
		}
684
685
	},
686
687
	_getDragVerticalDirection: function() {
688
		var delta = this.positionAbs.top - this.lastPositionAbs.top;
689
		return delta !== 0 && ( delta > 0 ? "down" : "up" );
690
	},
691
692
	_getDragHorizontalDirection: function() {
693
		var delta = this.positionAbs.left - this.lastPositionAbs.left;
694
		return delta !== 0 && ( delta > 0 ? "right" : "left" );
695
	},
696
697
	refresh: function( event ) {
698
		this._refreshItems( event );
699
		this._setHandleClassName();
700
		this.refreshPositions();
701
		return this;
702
	},
703
704
	_connectWith: function() {
705
		var options = this.options;
706
		return options.connectWith.constructor === String ?
707
			[ options.connectWith ] :
708
			options.connectWith;
709
	},
710
711 View Code Duplication
	_getItemsAsjQuery: function( connected ) {
712
713
		var i, j, cur, inst,
714
			items = [],
715
			queries = [],
716
			connectWith = this._connectWith();
717
718
		if ( connectWith && connected ) {
719
			for ( i = connectWith.length - 1; i >= 0; i-- ) {
720
				cur = $( connectWith[ i ], this.document[ 0 ] );
721
				for ( j = cur.length - 1; j >= 0; j-- ) {
722
					inst = $.data( cur[ j ], this.widgetFullName );
723
					if ( inst && inst !== this && !inst.options.disabled ) {
724
						queries.push( [ $.isFunction( inst.options.items ) ?
725
							inst.options.items.call( inst.element ) :
726
							$( inst.options.items, inst.element )
727
								.not( ".ui-sortable-helper" )
728
								.not( ".ui-sortable-placeholder" ), inst ] );
729
					}
730
				}
731
			}
732
		}
733
734
		queries.push( [ $.isFunction( this.options.items ) ?
735
			this.options.items
736
				.call( this.element, null, { options: this.options, item: this.currentItem } ) :
737
			$( this.options.items, this.element )
738
				.not( ".ui-sortable-helper" )
739
				.not( ".ui-sortable-placeholder" ), this ] );
740
741
		function addItems() {
742
			items.push( this );
743
		}
744
		for ( i = queries.length - 1; i >= 0; i-- ) {
745
			queries[ i ][ 0 ].each( addItems );
746
		}
747
748
		return $( items );
749
750
	},
751
752
	_removeCurrentsFromItems: function() {
753
754
		var list = this.currentItem.find( ":data(" + this.widgetName + "-item)" );
755
756
		this.items = $.grep( this.items, function( item ) {
757
			for ( var j = 0; j < list.length; j++ ) {
758
				if ( list[ j ] === item.item[ 0 ] ) {
759
					return false;
760
				}
761
			}
762
			return true;
763
		} );
764
765
	},
766
767 View Code Duplication
	_refreshItems: function( event ) {
768
769
		this.items = [];
770
		this.containers = [ this ];
771
772
		var i, j, cur, inst, targetData, _queries, item, queriesLength,
773
			items = this.items,
774
			queries = [ [ $.isFunction( this.options.items ) ?
775
				this.options.items.call( this.element[ 0 ], event, { item: this.currentItem } ) :
776
				$( this.options.items, this.element ), this ] ],
777
			connectWith = this._connectWith();
778
779
		//Shouldn't be run the first time through due to massive slow-down
780
		if ( connectWith && this.ready ) {
781
			for ( i = connectWith.length - 1; i >= 0; i-- ) {
782
				cur = $( connectWith[ i ], this.document[ 0 ] );
783
				for ( j = cur.length - 1; j >= 0; j-- ) {
784
					inst = $.data( cur[ j ], this.widgetFullName );
785
					if ( inst && inst !== this && !inst.options.disabled ) {
786
						queries.push( [ $.isFunction( inst.options.items ) ?
787
							inst.options.items
788
								.call( inst.element[ 0 ], event, { item: this.currentItem } ) :
789
							$( inst.options.items, inst.element ), inst ] );
790
						this.containers.push( inst );
791
					}
792
				}
793
			}
794
		}
795
796
		for ( i = queries.length - 1; i >= 0; i-- ) {
797
			targetData = queries[ i ][ 1 ];
798
			_queries = queries[ i ][ 0 ];
799
800
			for ( j = 0, queriesLength = _queries.length; j < queriesLength; j++ ) {
801
				item = $( _queries[ j ] );
802
803
				// Data for target checking (mouse manager)
804
				item.data( this.widgetName + "-item", targetData );
805
806
				items.push( {
807
					item: item,
808
					instance: targetData,
809
					width: 0, height: 0,
810
					left: 0, top: 0
811
				} );
812
			}
813
		}
814
815
	},
816
817
	refreshPositions: function( fast ) {
818
819
		// Determine whether items are being displayed horizontally
820
		this.floating = this.items.length ?
821
			this.options.axis === "x" || this._isFloating( this.items[ 0 ].item ) :
822
			false;
823
824
		//This has to be redone because due to the item being moved out/into the offsetParent,
825
		// the offsetParent's position will change
826
		if ( this.offsetParent && this.helper ) {
827
			this.offset.parent = this._getParentOffset();
828
		}
829
830
		var i, item, t, p;
831
832
		for ( i = this.items.length - 1; i >= 0; i-- ) {
833
			item = this.items[ i ];
834
835
			//We ignore calculating positions of all connected containers when we're not over them
836
			if ( item.instance !== this.currentContainer && this.currentContainer &&
837
					item.item[ 0 ] !== this.currentItem[ 0 ] ) {
838
				continue;
839
			}
840
841
			t = this.options.toleranceElement ?
842
				$( this.options.toleranceElement, item.item ) :
843
				item.item;
844
845
			if ( !fast ) {
846
				item.width = t.outerWidth();
847
				item.height = t.outerHeight();
848
			}
849
850
			p = t.offset();
851
			item.left = p.left;
852
			item.top = p.top;
853
		}
854
855
		if ( this.options.custom && this.options.custom.refreshContainers ) {
856
			this.options.custom.refreshContainers.call( this );
857
		} else {
858
			for ( i = this.containers.length - 1; i >= 0; i-- ) {
859
				p = this.containers[ i ].element.offset();
860
				this.containers[ i ].containerCache.left = p.left;
861
				this.containers[ i ].containerCache.top = p.top;
862
				this.containers[ i ].containerCache.width =
863
					this.containers[ i ].element.outerWidth();
864
				this.containers[ i ].containerCache.height =
865
					this.containers[ i ].element.outerHeight();
866
			}
867
		}
868
869
		return this;
870
	},
871
872
	_createPlaceholder: function( that ) {
873
		that = that || this;
874
		var className,
875
			o = that.options;
876
877
		if ( !o.placeholder || o.placeholder.constructor === String ) {
878
			className = o.placeholder;
879
			o.placeholder = {
880
				element: function() {
881
882
					var nodeName = that.currentItem[ 0 ].nodeName.toLowerCase(),
883
						element = $( "<" + nodeName + ">", that.document[ 0 ] );
884
885
						that._addClass( element, "ui-sortable-placeholder",
886
								className || that.currentItem[ 0 ].className )
887
							._removeClass( element, "ui-sortable-helper" );
888
889
					if ( nodeName === "tbody" ) {
890
						that._createTrPlaceholder(
891
							that.currentItem.find( "tr" ).eq( 0 ),
892
							$( "<tr>", that.document[ 0 ] ).appendTo( element )
893
						);
894
					} else if ( nodeName === "tr" ) {
895
						that._createTrPlaceholder( that.currentItem, element );
896
					} else if ( nodeName === "img" ) {
897
						element.attr( "src", that.currentItem.attr( "src" ) );
898
					}
899
900
					if ( !className ) {
901
						element.css( "visibility", "hidden" );
902
					}
903
904
					return element;
905
				},
906
				update: function( container, p ) {
907
908
					// 1. If a className is set as 'placeholder option, we don't force sizes -
909
					// the class is responsible for that
910
					// 2. The option 'forcePlaceholderSize can be enabled to force it even if a
911
					// class name is specified
912
					if ( className && !o.forcePlaceholderSize ) {
913
						return;
914
					}
915
916
					//If the element doesn't have a actual height by itself (without styles coming
917
					// from a stylesheet), it receives the inline height from the dragged item
918
					if ( !p.height() ) {
919
						p.height(
920
							that.currentItem.innerHeight() -
921
							parseInt( that.currentItem.css( "paddingTop" ) || 0, 10 ) -
922
							parseInt( that.currentItem.css( "paddingBottom" ) || 0, 10 ) );
923
					}
924
					if ( !p.width() ) {
925
						p.width(
926
							that.currentItem.innerWidth() -
927
							parseInt( that.currentItem.css( "paddingLeft" ) || 0, 10 ) -
928
							parseInt( that.currentItem.css( "paddingRight" ) || 0, 10 ) );
929
					}
930
				}
931
			};
932
		}
933
934
		//Create the placeholder
935
		that.placeholder = $( o.placeholder.element.call( that.element, that.currentItem ) );
936
937
		//Append it after the actual current item
938
		that.currentItem.after( that.placeholder );
939
940
		//Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317)
941
		o.placeholder.update( that, that.placeholder );
942
943
	},
944
945
	_createTrPlaceholder: function( sourceTr, targetTr ) {
946
		var that = this;
947
948
		sourceTr.children().each( function() {
949
			$( "<td>&#160;</td>", that.document[ 0 ] )
950
				.attr( "colspan", $( this ).attr( "colspan" ) || 1 )
951
				.appendTo( targetTr );
952
		} );
953
	},
954
955
	_contactContainers: function( event ) {
956
		var i, j, dist, itemWithLeastDistance, posProperty, sizeProperty, cur, nearBottom,
957
			floating, axis,
958
			innermostContainer = null,
959
			innermostIndex = null;
960
961
		// Get innermost container that intersects with item
962
		for ( i = this.containers.length - 1; i >= 0; i-- ) {
963
964
			// Never consider a container that's located within the item itself
965
			if ( $.contains( this.currentItem[ 0 ], this.containers[ i ].element[ 0 ] ) ) {
966
				continue;
967
			}
968
969
			if ( this._intersectsWith( this.containers[ i ].containerCache ) ) {
970
971
				// If we've already found a container and it's more "inner" than this, then continue
972
				if ( innermostContainer &&
973
						$.contains(
974
							this.containers[ i ].element[ 0 ],
975
							innermostContainer.element[ 0 ] ) ) {
976
					continue;
977
				}
978
979
				innermostContainer = this.containers[ i ];
980
				innermostIndex = i;
981
982
			} else {
983
984
				// container doesn't intersect. trigger "out" event if necessary
985
				if ( this.containers[ i ].containerCache.over ) {
986
					this.containers[ i ]._trigger( "out", event, this._uiHash( this ) );
987
					this.containers[ i ].containerCache.over = 0;
988
				}
989
			}
990
991
		}
992
993
		// If no intersecting containers found, return
994
		if ( !innermostContainer ) {
995
			return;
996
		}
997
998
		// Move the item into the container if it's not there already
999
		if ( this.containers.length === 1 ) {
1000
			if ( !this.containers[ innermostIndex ].containerCache.over ) {
1001
				this.containers[ innermostIndex ]._trigger( "over", event, this._uiHash( this ) );
1002
				this.containers[ innermostIndex ].containerCache.over = 1;
1003
			}
1004
		} else {
1005
1006
			// When entering a new container, we will find the item with the least distance and
1007
			// append our item near it
1008
			dist = 10000;
1009
			itemWithLeastDistance = null;
1010
			floating = innermostContainer.floating || this._isFloating( this.currentItem );
1011
			posProperty = floating ? "left" : "top";
1012
			sizeProperty = floating ? "width" : "height";
1013
			axis = floating ? "pageX" : "pageY";
1014
1015
			for ( j = this.items.length - 1; j >= 0; j-- ) {
1016
				if ( !$.contains(
1017
						this.containers[ innermostIndex ].element[ 0 ], this.items[ j ].item[ 0 ] )
1018
				) {
1019
					continue;
1020
				}
1021
				if ( this.items[ j ].item[ 0 ] === this.currentItem[ 0 ] ) {
1022
					continue;
1023
				}
1024
1025
				cur = this.items[ j ].item.offset()[ posProperty ];
1026
				nearBottom = false;
1027
				if ( event[ axis ] - cur > this.items[ j ][ sizeProperty ] / 2 ) {
1028
					nearBottom = true;
1029
				}
1030
1031
				if ( Math.abs( event[ axis ] - cur ) < dist ) {
1032
					dist = Math.abs( event[ axis ] - cur );
1033
					itemWithLeastDistance = this.items[ j ];
1034
					this.direction = nearBottom ? "up" : "down";
1035
				}
1036
			}
1037
1038
			//Check if dropOnEmpty is enabled
1039
			if ( !itemWithLeastDistance && !this.options.dropOnEmpty ) {
1040
				return;
1041
			}
1042
1043
			if ( this.currentContainer === this.containers[ innermostIndex ] ) {
1044
				if ( !this.currentContainer.containerCache.over ) {
1045
					this.containers[ innermostIndex ]._trigger( "over", event, this._uiHash() );
1046
					this.currentContainer.containerCache.over = 1;
1047
				}
1048
				return;
1049
			}
1050
1051
			itemWithLeastDistance ?
1052
				this._rearrange( event, itemWithLeastDistance, null, true ) :
1053
				this._rearrange( event, null, this.containers[ innermostIndex ].element, true );
1054
			this._trigger( "change", event, this._uiHash() );
1055
			this.containers[ innermostIndex ]._trigger( "change", event, this._uiHash( this ) );
1056
			this.currentContainer = this.containers[ innermostIndex ];
1057
1058
			//Update the placeholder
1059
			this.options.placeholder.update( this.currentContainer, this.placeholder );
1060
1061
			this.containers[ innermostIndex ]._trigger( "over", event, this._uiHash( this ) );
1062
			this.containers[ innermostIndex ].containerCache.over = 1;
1063
		}
1064
1065
	},
1066
1067
	_createHelper: function( event ) {
1068
1069
		var o = this.options,
1070
			helper = $.isFunction( o.helper ) ?
1071
				$( o.helper.apply( this.element[ 0 ], [ event, this.currentItem ] ) ) :
1072
				( o.helper === "clone" ? this.currentItem.clone() : this.currentItem );
1073
1074
		//Add the helper to the DOM if that didn't happen already
1075
		if ( !helper.parents( "body" ).length ) {
1076
			$( o.appendTo !== "parent" ?
1077
				o.appendTo :
1078
				this.currentItem[ 0 ].parentNode )[ 0 ].appendChild( helper[ 0 ] );
1079
		}
1080
1081
		if ( helper[ 0 ] === this.currentItem[ 0 ] ) {
1082
			this._storedCSS = {
1083
				width: this.currentItem[ 0 ].style.width,
1084
				height: this.currentItem[ 0 ].style.height,
1085
				position: this.currentItem.css( "position" ),
1086
				top: this.currentItem.css( "top" ),
1087
				left: this.currentItem.css( "left" )
1088
			};
1089
		}
1090
1091
		if ( !helper[ 0 ].style.width || o.forceHelperSize ) {
1092
			helper.width( this.currentItem.width() );
1093
		}
1094
		if ( !helper[ 0 ].style.height || o.forceHelperSize ) {
1095
			helper.height( this.currentItem.height() );
1096
		}
1097
1098
		return helper;
1099
1100
	},
1101
1102 View Code Duplication
	_adjustOffsetFromHelper: function( obj ) {
1103
		if ( typeof obj === "string" ) {
1104
			obj = obj.split( " " );
1105
		}
1106
		if ( $.isArray( obj ) ) {
1107
			obj = { left: +obj[ 0 ], top: +obj[ 1 ] || 0 };
1108
		}
1109
		if ( "left" in obj ) {
1110
			this.offset.click.left = obj.left + this.margins.left;
1111
		}
1112
		if ( "right" in obj ) {
1113
			this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
1114
		}
1115
		if ( "top" in obj ) {
1116
			this.offset.click.top = obj.top + this.margins.top;
1117
		}
1118
		if ( "bottom" in obj ) {
1119
			this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
1120
		}
1121
	},
1122
1123
	_getParentOffset: function() {
1124
1125
		//Get the offsetParent and cache its position
1126
		this.offsetParent = this.helper.offsetParent();
1127
		var po = this.offsetParent.offset();
1128
1129
		// This is a special case where we need to modify a offset calculated on start, since the
1130
		// following happened:
1131
		// 1. The position of the helper is absolute, so it's position is calculated based on the
1132
		// next positioned parent
1133
		// 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't
1134
		// the document, which means that the scroll is included in the initial calculation of the
1135
		// offset of the parent, and never recalculated upon drag
1136
		if ( this.cssPosition === "absolute" && this.scrollParent[ 0 ] !== this.document[ 0 ] &&
1137
				$.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) {
1138
			po.left += this.scrollParent.scrollLeft();
1139
			po.top += this.scrollParent.scrollTop();
1140
		}
1141
1142
		// This needs to be actually done for all browsers, since pageX/pageY includes this
1143
		// information with an ugly IE fix
1144
		if ( this.offsetParent[ 0 ] === this.document[ 0 ].body ||
1145
				( this.offsetParent[ 0 ].tagName &&
1146
				this.offsetParent[ 0 ].tagName.toLowerCase() === "html" && $.ui.ie ) ) {
1147
			po = { top: 0, left: 0 };
1148
		}
1149
1150
		return {
1151
			top: po.top + ( parseInt( this.offsetParent.css( "borderTopWidth" ), 10 ) || 0 ),
1152
			left: po.left + ( parseInt( this.offsetParent.css( "borderLeftWidth" ), 10 ) || 0 )
1153
		};
1154
1155
	},
1156
1157
	_getRelativeOffset: function() {
1158
1159
		if ( this.cssPosition === "relative" ) {
1160
			var p = this.currentItem.position();
1161
			return {
1162
				top: p.top - ( parseInt( this.helper.css( "top" ), 10 ) || 0 ) +
1163
					this.scrollParent.scrollTop(),
1164
				left: p.left - ( parseInt( this.helper.css( "left" ), 10 ) || 0 ) +
1165
					this.scrollParent.scrollLeft()
1166
			};
1167
		} else {
1168
			return { top: 0, left: 0 };
1169
		}
1170
1171
	},
1172
1173
	_cacheMargins: function() {
1174
		this.margins = {
1175
			left: ( parseInt( this.currentItem.css( "marginLeft" ), 10 ) || 0 ),
1176
			top: ( parseInt( this.currentItem.css( "marginTop" ), 10 ) || 0 )
1177
		};
1178
	},
1179
1180
	_cacheHelperProportions: function() {
1181
		this.helperProportions = {
1182
			width: this.helper.outerWidth(),
1183
			height: this.helper.outerHeight()
1184
		};
1185
	},
1186
1187
	_setContainment: function() {
1188
1189
		var ce, co, over,
1190
			o = this.options;
1191
		if ( o.containment === "parent" ) {
1192
			o.containment = this.helper[ 0 ].parentNode;
1193
		}
1194
		if ( o.containment === "document" || o.containment === "window" ) {
1195
			this.containment = [
1196
				0 - this.offset.relative.left - this.offset.parent.left,
1197
				0 - this.offset.relative.top - this.offset.parent.top,
1198
				o.containment === "document" ?
1199
					this.document.width() :
1200
					this.window.width() - this.helperProportions.width - this.margins.left,
1201
				( o.containment === "document" ?
1202
					( this.document.height() || document.body.parentNode.scrollHeight ) :
1203
					this.window.height() || this.document[ 0 ].body.parentNode.scrollHeight
1204
				) - this.helperProportions.height - this.margins.top
1205
			];
1206
		}
1207
1208
		if ( !( /^(document|window|parent)$/ ).test( o.containment ) ) {
1209
			ce = $( o.containment )[ 0 ];
1210
			co = $( o.containment ).offset();
1211
			over = ( $( ce ).css( "overflow" ) !== "hidden" );
1212
1213
			this.containment = [
1214
				co.left + ( parseInt( $( ce ).css( "borderLeftWidth" ), 10 ) || 0 ) +
1215
					( parseInt( $( ce ).css( "paddingLeft" ), 10 ) || 0 ) - this.margins.left,
1216
				co.top + ( parseInt( $( ce ).css( "borderTopWidth" ), 10 ) || 0 ) +
1217
					( parseInt( $( ce ).css( "paddingTop" ), 10 ) || 0 ) - this.margins.top,
1218
				co.left + ( over ? Math.max( ce.scrollWidth, ce.offsetWidth ) : ce.offsetWidth ) -
1219
					( parseInt( $( ce ).css( "borderLeftWidth" ), 10 ) || 0 ) -
1220
					( parseInt( $( ce ).css( "paddingRight" ), 10 ) || 0 ) -
1221
					this.helperProportions.width - this.margins.left,
1222
				co.top + ( over ? Math.max( ce.scrollHeight, ce.offsetHeight ) : ce.offsetHeight ) -
1223
					( parseInt( $( ce ).css( "borderTopWidth" ), 10 ) || 0 ) -
1224
					( parseInt( $( ce ).css( "paddingBottom" ), 10 ) || 0 ) -
1225
					this.helperProportions.height - this.margins.top
1226
			];
1227
		}
1228
1229
	},
1230
1231
	_convertPositionTo: function( d, pos ) {
1232
1233
		if ( !pos ) {
1234
			pos = this.position;
1235
		}
1236
		var mod = d === "absolute" ? 1 : -1,
1237
			scroll = this.cssPosition === "absolute" &&
1238
				!( this.scrollParent[ 0 ] !== this.document[ 0 ] &&
1239
				$.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ?
1240
					this.offsetParent :
1241
					this.scrollParent,
1242
			scrollIsRootNode = ( /(html|body)/i ).test( scroll[ 0 ].tagName );
1243
1244
		return {
1245
			top: (
1246
1247
				// The absolute mouse position
1248
				pos.top	+
1249
1250
				// Only for relative positioned nodes: Relative offset from element to offset parent
1251
				this.offset.relative.top * mod +
1252
1253
				// The offsetParent's offset without borders (offset + border)
1254
				this.offset.parent.top * mod -
1255
				( ( this.cssPosition === "fixed" ?
1256
					-this.scrollParent.scrollTop() :
1257
					( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod )
1258
			),
1259
			left: (
1260
1261
				// The absolute mouse position
1262
				pos.left +
1263
1264
				// Only for relative positioned nodes: Relative offset from element to offset parent
1265
				this.offset.relative.left * mod +
1266
1267
				// The offsetParent's offset without borders (offset + border)
1268
				this.offset.parent.left * mod	-
1269
				( ( this.cssPosition === "fixed" ?
1270
					-this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 :
1271
					scroll.scrollLeft() ) * mod )
1272
			)
1273
		};
1274
1275
	},
1276
1277
	_generatePosition: function( event ) {
1278
1279
		var top, left,
1280
			o = this.options,
1281
			pageX = event.pageX,
1282
			pageY = event.pageY,
1283
			scroll = this.cssPosition === "absolute" &&
1284
				!( this.scrollParent[ 0 ] !== this.document[ 0 ] &&
1285
				$.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ?
1286
					this.offsetParent :
1287
					this.scrollParent,
1288
				scrollIsRootNode = ( /(html|body)/i ).test( scroll[ 0 ].tagName );
1289
1290
		// This is another very weird special case that only happens for relative elements:
1291
		// 1. If the css position is relative
1292
		// 2. and the scroll parent is the document or similar to the offset parent
1293
		// we have to refresh the relative offset during the scroll so there are no jumps
1294
		if ( this.cssPosition === "relative" && !( this.scrollParent[ 0 ] !== this.document[ 0 ] &&
1295
				this.scrollParent[ 0 ] !== this.offsetParent[ 0 ] ) ) {
1296
			this.offset.relative = this._getRelativeOffset();
1297
		}
1298
1299
		/*
1300
		 * - Position constraining -
1301
		 * Constrain the position to a mix of grid, containment.
1302
		 */
1303
1304 View Code Duplication
		if ( this.originalPosition ) { //If we are not dragging yet, we won't check for options
1305
1306
			if ( this.containment ) {
1307
				if ( event.pageX - this.offset.click.left < this.containment[ 0 ] ) {
1308
					pageX = this.containment[ 0 ] + this.offset.click.left;
1309
				}
1310
				if ( event.pageY - this.offset.click.top < this.containment[ 1 ] ) {
1311
					pageY = this.containment[ 1 ] + this.offset.click.top;
1312
				}
1313
				if ( event.pageX - this.offset.click.left > this.containment[ 2 ] ) {
1314
					pageX = this.containment[ 2 ] + this.offset.click.left;
1315
				}
1316
				if ( event.pageY - this.offset.click.top > this.containment[ 3 ] ) {
1317
					pageY = this.containment[ 3 ] + this.offset.click.top;
1318
				}
1319
			}
1320
1321
			if ( o.grid ) {
1322
				top = this.originalPageY + Math.round( ( pageY - this.originalPageY ) /
1323
					o.grid[ 1 ] ) * o.grid[ 1 ];
1324
				pageY = this.containment ?
1325
					( ( top - this.offset.click.top >= this.containment[ 1 ] &&
1326
						top - this.offset.click.top <= this.containment[ 3 ] ) ?
1327
							top :
1328
							( ( top - this.offset.click.top >= this.containment[ 1 ] ) ?
1329
								top - o.grid[ 1 ] : top + o.grid[ 1 ] ) ) :
1330
								top;
1331
1332
				left = this.originalPageX + Math.round( ( pageX - this.originalPageX ) /
1333
					o.grid[ 0 ] ) * o.grid[ 0 ];
1334
				pageX = this.containment ?
1335
					( ( left - this.offset.click.left >= this.containment[ 0 ] &&
1336
						left - this.offset.click.left <= this.containment[ 2 ] ) ?
1337
							left :
1338
							( ( left - this.offset.click.left >= this.containment[ 0 ] ) ?
1339
								left - o.grid[ 0 ] : left + o.grid[ 0 ] ) ) :
1340
								left;
1341
			}
1342
1343
		}
1344
1345
		return {
1346
			top: (
1347
1348
				// The absolute mouse position
1349
				pageY -
1350
1351
				// Click offset (relative to the element)
1352
				this.offset.click.top -
1353
1354
				// Only for relative positioned nodes: Relative offset from element to offset parent
1355
				this.offset.relative.top -
1356
1357
				// The offsetParent's offset without borders (offset + border)
1358
				this.offset.parent.top +
1359
				( ( this.cssPosition === "fixed" ?
1360
					-this.scrollParent.scrollTop() :
1361
					( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) )
1362
			),
1363
			left: (
1364
1365
				// The absolute mouse position
1366
				pageX -
1367
1368
				// Click offset (relative to the element)
1369
				this.offset.click.left -
1370
1371
				// Only for relative positioned nodes: Relative offset from element to offset parent
1372
				this.offset.relative.left -
1373
1374
				// The offsetParent's offset without borders (offset + border)
1375
				this.offset.parent.left +
1376
				( ( this.cssPosition === "fixed" ?
1377
					-this.scrollParent.scrollLeft() :
1378
					scrollIsRootNode ? 0 : scroll.scrollLeft() ) )
1379
			)
1380
		};
1381
1382
	},
1383
1384
	_rearrange: function( event, i, a, hardRefresh ) {
1385
1386
		a ? a[ 0 ].appendChild( this.placeholder[ 0 ] ) :
1387
			i.item[ 0 ].parentNode.insertBefore( this.placeholder[ 0 ],
1388
				( this.direction === "down" ? i.item[ 0 ] : i.item[ 0 ].nextSibling ) );
1389
1390
		//Various things done here to improve the performance:
1391
		// 1. we create a setTimeout, that calls refreshPositions
1392
		// 2. on the instance, we have a counter variable, that get's higher after every append
1393
		// 3. on the local scope, we copy the counter variable, and check in the timeout,
1394
		// if it's still the same
1395
		// 4. this lets only the last addition to the timeout stack through
1396
		this.counter = this.counter ? ++this.counter : 1;
1397
		var counter = this.counter;
1398
1399
		this._delay( function() {
1400
			if ( counter === this.counter ) {
1401
1402
				//Precompute after each DOM insertion, NOT on mousemove
1403
				this.refreshPositions( !hardRefresh );
1404
			}
1405
		} );
1406
1407
	},
1408
1409
	_clear: function( event, noPropagation ) {
1410
1411
		this.reverting = false;
1412
1413
		// We delay all events that have to be triggered to after the point where the placeholder
1414
		// has been removed and everything else normalized again
1415
		var i,
1416
			delayedTriggers = [];
1417
1418
		// We first have to update the dom position of the actual currentItem
1419
		// Note: don't do it if the current item is already removed (by a user), or it gets
1420
		// reappended (see #4088)
1421
		if ( !this._noFinalSort && this.currentItem.parent().length ) {
1422
			this.placeholder.before( this.currentItem );
1423
		}
1424
		this._noFinalSort = null;
1425
1426
		if ( this.helper[ 0 ] === this.currentItem[ 0 ] ) {
1427
			for ( i in this._storedCSS ) {
1428
				if ( this._storedCSS[ i ] === "auto" || this._storedCSS[ i ] === "static" ) {
1429
					this._storedCSS[ i ] = "";
1430
				}
1431
			}
1432
			this.currentItem.css( this._storedCSS );
1433
			this._removeClass( this.currentItem, "ui-sortable-helper" );
1434
		} else {
1435
			this.currentItem.show();
1436
		}
1437
1438
		if ( this.fromOutside && !noPropagation ) {
1439
			delayedTriggers.push( function( event ) {
1440
				this._trigger( "receive", event, this._uiHash( this.fromOutside ) );
1441
			} );
1442
		}
1443
		if ( ( this.fromOutside ||
1444
				this.domPosition.prev !==
1445
				this.currentItem.prev().not( ".ui-sortable-helper" )[ 0 ] ||
1446
				this.domPosition.parent !== this.currentItem.parent()[ 0 ] ) && !noPropagation ) {
1447
1448
			// Trigger update callback if the DOM position has changed
1449
			delayedTriggers.push( function( event ) {
1450
				this._trigger( "update", event, this._uiHash() );
1451
			} );
1452
		}
1453
1454
		// Check if the items Container has Changed and trigger appropriate
1455
		// events.
1456
		if ( this !== this.currentContainer ) {
1457
			if ( !noPropagation ) {
1458
				delayedTriggers.push( function( event ) {
1459
					this._trigger( "remove", event, this._uiHash() );
1460
				} );
1461
				delayedTriggers.push( ( function( c ) {
1462
					return function( event ) {
1463
						c._trigger( "receive", event, this._uiHash( this ) );
1464
					};
1465
				} ).call( this, this.currentContainer ) );
1466
				delayedTriggers.push( ( function( c ) {
1467
					return function( event ) {
1468
						c._trigger( "update", event, this._uiHash( this ) );
1469
					};
1470
				} ).call( this, this.currentContainer ) );
1471
			}
1472
		}
1473
1474
		//Post events to containers
1475
		function delayEvent( type, instance, container ) {
1476
			return function( event ) {
1477
				container._trigger( type, event, instance._uiHash( instance ) );
1478
			};
1479
		}
1480
		for ( i = this.containers.length - 1; i >= 0; i-- ) {
1481
			if ( !noPropagation ) {
1482
				delayedTriggers.push( delayEvent( "deactivate", this, this.containers[ i ] ) );
1483
			}
1484
			if ( this.containers[ i ].containerCache.over ) {
1485
				delayedTriggers.push( delayEvent( "out", this, this.containers[ i ] ) );
1486
				this.containers[ i ].containerCache.over = 0;
1487
			}
1488
		}
1489
1490
		//Do what was originally in plugins
1491
		if ( this.storedCursor ) {
1492
			this.document.find( "body" ).css( "cursor", this.storedCursor );
1493
			this.storedStylesheet.remove();
1494
		}
1495
		if ( this._storedOpacity ) {
1496
			this.helper.css( "opacity", this._storedOpacity );
1497
		}
1498
		if ( this._storedZIndex ) {
1499
			this.helper.css( "zIndex", this._storedZIndex === "auto" ? "" : this._storedZIndex );
1500
		}
1501
1502
		this.dragging = false;
1503
1504
		if ( !noPropagation ) {
1505
			this._trigger( "beforeStop", event, this._uiHash() );
1506
		}
1507
1508
		//$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately,
1509
		// it unbinds ALL events from the original node!
1510
		this.placeholder[ 0 ].parentNode.removeChild( this.placeholder[ 0 ] );
1511
1512
		if ( !this.cancelHelperRemoval ) {
1513
			if ( this.helper[ 0 ] !== this.currentItem[ 0 ] ) {
1514
				this.helper.remove();
1515
			}
1516
			this.helper = null;
1517
		}
1518
1519
		if ( !noPropagation ) {
1520
			for ( i = 0; i < delayedTriggers.length; i++ ) {
1521
1522
				// Trigger all delayed events
1523
				delayedTriggers[ i ].call( this, event );
1524
			}
1525
			this._trigger( "stop", event, this._uiHash() );
1526
		}
1527
1528
		this.fromOutside = false;
1529
		return !this.cancelHelperRemoval;
1530
1531
	},
1532
1533
	_trigger: function() {
1534
		if ( $.Widget.prototype._trigger.apply( this, arguments ) === false ) {
1535
			this.cancel();
1536
		}
1537
	},
1538
1539
	_uiHash: function( _inst ) {
1540
		var inst = _inst || this;
1541
		return {
1542
			helper: inst.helper,
1543
			placeholder: inst.placeholder || $( [] ),
1544
			position: inst.position,
1545
			originalPosition: inst.originalPosition,
1546
			offset: inst.positionAbs,
1547
			item: inst.currentItem,
1548
			sender: _inst ? _inst.element : null
1549
		};
1550
	}
1551
1552
} );
1553
1554
} ) );
1555