ru-se.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

sortable.js (46033B)


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