1 /*! jQuery UI - v1.10.4 - 2017-01-14
3 * Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.sortable.js
4 * Copyright jQuery Foundation and other contributors; Licensed MIT */
6 (function( $, undefined ) {
9 runiqueId = /^ui-id-\d+$/;
11 // $.ui might exist from components with no dependencies, e.g., $.ui.position
45 focus: (function( orig ) {
46 return function( delay, fn ) {
47 return typeof delay === "number" ?
48 this.each(function() {
50 setTimeout(function() {
57 orig.apply( this, arguments );
61 scrollParent: function() {
63 if (($.ui.ie && (/(static|relative)/).test(this.css("position"))) || (/absolute/).test(this.css("position"))) {
64 scrollParent = this.parents().filter(function() {
65 return (/(relative|absolute|fixed)/).test($.css(this,"position")) && (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x"));
68 scrollParent = this.parents().filter(function() {
69 return (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x"));
73 return (/fixed/).test(this.css("position")) || !scrollParent.length ? $(document) : scrollParent;
76 zIndex: function( zIndex ) {
77 if ( zIndex !== undefined ) {
78 return this.css( "zIndex", zIndex );
82 var elem = $( this[ 0 ] ), position, value;
83 while ( elem.length && elem[ 0 ] !== document ) {
84 // Ignore z-index if position is set to a value where z-index is ignored by the browser
85 // This makes behavior of this function consistent across browsers
86 // WebKit always returns auto if the element is positioned
87 position = elem.css( "position" );
88 if ( position === "absolute" || position === "relative" || position === "fixed" ) {
89 // IE returns 0 when zIndex is not specified
90 // other browsers return a string
91 // we ignore the case of nested elements with an explicit value of 0
92 // <div style="z-index: -10;"><div style="z-index: 0;"></div></div>
93 value = parseInt( elem.css( "zIndex" ), 10 );
94 if ( !isNaN( value ) && value !== 0 ) {
105 uniqueId: function() {
106 return this.each(function() {
108 this.id = "ui-id-" + (++uuid);
113 removeUniqueId: function() {
114 return this.each(function() {
115 if ( runiqueId.test( this.id ) ) {
116 $( this ).removeAttr( "id" );
123 function focusable( element, isTabIndexNotNaN ) {
124 var map, mapName, img,
125 nodeName = element.nodeName.toLowerCase();
126 if ( "area" === nodeName ) {
127 map = element.parentNode;
129 if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) {
132 img = $( "img[usemap=#" + mapName + "]" )[0];
133 return !!img && visible( img );
135 return ( /input|select|textarea|button|object/.test( nodeName ) ?
138 element.href || isTabIndexNotNaN :
140 // the element and all of its ancestors must be visible
144 function visible( element ) {
145 return $.expr.filters.visible( element ) &&
146 !$( element ).parents().addBack().filter(function() {
147 return $.css( this, "visibility" ) === "hidden";
151 $.extend( $.expr[ ":" ], {
152 data: $.expr.createPseudo ?
153 $.expr.createPseudo(function( dataName ) {
154 return function( elem ) {
155 return !!$.data( elem, dataName );
158 // support: jQuery <1.8
159 function( elem, i, match ) {
160 return !!$.data( elem, match[ 3 ] );
163 focusable: function( element ) {
164 return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) );
167 tabbable: function( element ) {
168 var tabIndex = $.attr( element, "tabindex" ),
169 isTabIndexNaN = isNaN( tabIndex );
170 return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN );
174 // support: jQuery <1.8
175 if ( !$( "<a>" ).outerWidth( 1 ).jquery ) {
176 $.each( [ "Width", "Height" ], function( i, name ) {
177 var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ],
178 type = name.toLowerCase(),
180 innerWidth: $.fn.innerWidth,
181 innerHeight: $.fn.innerHeight,
182 outerWidth: $.fn.outerWidth,
183 outerHeight: $.fn.outerHeight
186 function reduce( elem, size, border, margin ) {
187 $.each( side, function() {
188 size -= parseFloat( $.css( elem, "padding" + this ) ) || 0;
190 size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0;
193 size -= parseFloat( $.css( elem, "margin" + this ) ) || 0;
199 $.fn[ "inner" + name ] = function( size ) {
200 if ( size === undefined ) {
201 return orig[ "inner" + name ].call( this );
204 return this.each(function() {
205 $( this ).css( type, reduce( this, size ) + "px" );
209 $.fn[ "outer" + name] = function( size, margin ) {
210 if ( typeof size !== "number" ) {
211 return orig[ "outer" + name ].call( this, size );
214 return this.each(function() {
215 $( this).css( type, reduce( this, size, true, margin ) + "px" );
221 // support: jQuery <1.8
222 if ( !$.fn.addBack ) {
223 $.fn.addBack = function( selector ) {
224 return this.add( selector == null ?
225 this.prevObject : this.prevObject.filter( selector )
230 // support: jQuery 1.6.1, 1.6.2 (http://bugs.jquery.com/ticket/9413)
231 if ( $( "<a>" ).data( "a-b", "a" ).removeData( "a-b" ).data( "a-b" ) ) {
232 $.fn.removeData = (function( removeData ) {
233 return function( key ) {
234 if ( arguments.length ) {
235 return removeData.call( this, $.camelCase( key ) );
237 return removeData.call( this );
240 })( $.fn.removeData );
248 $.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() );
250 $.support.selectstart = "onselectstart" in document.createElement( "div" );
252 disableSelection: function() {
253 return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) +
254 ".ui-disableSelection", function( event ) {
255 event.preventDefault();
259 enableSelection: function() {
260 return this.unbind( ".ui-disableSelection" );
265 // $.ui.plugin is deprecated. Use $.widget() extensions instead.
267 add: function( module, option, set ) {
269 proto = $.ui[ module ].prototype;
271 proto.plugins[ i ] = proto.plugins[ i ] || [];
272 proto.plugins[ i ].push( [ option, set[ i ] ] );
275 call: function( instance, name, args ) {
277 set = instance.plugins[ name ];
278 if ( !set || !instance.element[ 0 ].parentNode || instance.element[ 0 ].parentNode.nodeType === 11 ) {
282 for ( i = 0; i < set.length; i++ ) {
283 if ( instance.options[ set[ i ][ 0 ] ] ) {
284 set[ i ][ 1 ].apply( instance.element, args );
290 // only used by resizable
291 hasScroll: function( el, a ) {
293 //If overflow is hidden, the element might have extra content, but the user wants to hide it
294 if ( $( el ).css( "overflow" ) === "hidden") {
298 var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop",
301 if ( el[ scroll ] > 0 ) {
305 // TODO: determine which cases actually cause this to happen
306 // if the element doesn't have the scroll set, see if it's possible to
309 has = ( el[ scroll ] > 0 );
316 (function( $, undefined ) {
319 slice = Array.prototype.slice,
320 _cleanData = $.cleanData;
321 $.cleanData = function( elems ) {
322 for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
324 $( elem ).triggerHandler( "remove" );
325 // http://bugs.jquery.com/ticket/8235
331 $.widget = function( name, base, prototype ) {
332 var fullName, existingConstructor, constructor, basePrototype,
333 // proxiedPrototype allows the provided prototype to remain unmodified
334 // so that it can be used as a mixin for multiple widgets (#8876)
335 proxiedPrototype = {},
336 namespace = name.split( "." )[ 0 ];
338 name = name.split( "." )[ 1 ];
339 fullName = namespace + "-" + name;
346 // create selector for plugin
347 $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
348 return !!$.data( elem, fullName );
351 $[ namespace ] = $[ namespace ] || {};
352 existingConstructor = $[ namespace ][ name ];
353 constructor = $[ namespace ][ name ] = function( options, element ) {
354 // allow instantiation without "new" keyword
355 if ( !this._createWidget ) {
356 return new constructor( options, element );
359 // allow instantiation without initializing for simple inheritance
360 // must use "new" keyword (the code above always passes args)
361 if ( arguments.length ) {
362 this._createWidget( options, element );
365 // extend with the existing constructor to carry over any static properties
366 $.extend( constructor, existingConstructor, {
367 version: prototype.version,
368 // copy the object used to create the prototype in case we need to
369 // redefine the widget later
370 _proto: $.extend( {}, prototype ),
371 // track widgets that inherit from this widget in case this widget is
372 // redefined after a widget inherits from it
373 _childConstructors: []
376 basePrototype = new base();
377 // we need to make the options hash a property directly on the new instance
378 // otherwise we'll modify the options hash on the prototype that we're
380 basePrototype.options = $.widget.extend( {}, basePrototype.options );
381 $.each( prototype, function( prop, value ) {
382 if ( !$.isFunction( value ) ) {
383 proxiedPrototype[ prop ] = value;
386 proxiedPrototype[ prop ] = (function() {
387 var _super = function() {
388 return base.prototype[ prop ].apply( this, arguments );
390 _superApply = function( args ) {
391 return base.prototype[ prop ].apply( this, args );
394 var __super = this._super,
395 __superApply = this._superApply,
398 this._super = _super;
399 this._superApply = _superApply;
401 returnValue = value.apply( this, arguments );
403 this._super = __super;
404 this._superApply = __superApply;
410 constructor.prototype = $.widget.extend( basePrototype, {
411 // TODO: remove support for widgetEventPrefix
412 // always use the name + a colon as the prefix, e.g., draggable:start
413 // don't prefix for widgets that aren't DOM-based
414 widgetEventPrefix: existingConstructor ? (basePrototype.widgetEventPrefix || name) : name
415 }, proxiedPrototype, {
416 constructor: constructor,
417 namespace: namespace,
419 widgetFullName: fullName
422 // If this widget is being redefined then we need to find all widgets that
423 // are inheriting from it and redefine all of them so that they inherit from
424 // the new version of this widget. We're essentially trying to replace one
425 // level in the prototype chain.
426 if ( existingConstructor ) {
427 $.each( existingConstructor._childConstructors, function( i, child ) {
428 var childPrototype = child.prototype;
430 // redefine the child widget using the same prototype that was
431 // originally used, but inherit from the new version of the base
432 $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
434 // remove the list of existing child constructors from the old constructor
435 // so the old child constructors can be garbage collected
436 delete existingConstructor._childConstructors;
438 base._childConstructors.push( constructor );
441 $.widget.bridge( name, constructor );
444 $.widget.extend = function( target ) {
445 var input = slice.call( arguments, 1 ),
447 inputLength = input.length,
450 for ( ; inputIndex < inputLength; inputIndex++ ) {
451 for ( key in input[ inputIndex ] ) {
452 value = input[ inputIndex ][ key ];
453 if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
455 if ( $.isPlainObject( value ) ) {
456 target[ key ] = $.isPlainObject( target[ key ] ) ?
457 $.widget.extend( {}, target[ key ], value ) :
458 // Don't extend strings, arrays, etc. with objects
459 $.widget.extend( {}, value );
460 // Copy everything else by reference
462 target[ key ] = value;
470 $.widget.bridge = function( name, object ) {
471 var fullName = object.prototype.widgetFullName || name;
472 $.fn[ name ] = function( options ) {
473 var isMethodCall = typeof options === "string",
474 args = slice.call( arguments, 1 ),
477 // allow multiple hashes to be passed on init
478 options = !isMethodCall && args.length ?
479 $.widget.extend.apply( null, [ options ].concat(args) ) :
482 if ( isMethodCall ) {
483 this.each(function() {
485 instance = $.data( this, fullName );
487 return $.error( "cannot call methods on " + name + " prior to initialization; " +
488 "attempted to call method '" + options + "'" );
490 if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
491 return $.error( "no such method '" + options + "' for " + name + " widget instance" );
493 methodValue = instance[ options ].apply( instance, args );
494 if ( methodValue !== instance && methodValue !== undefined ) {
495 returnValue = methodValue && methodValue.jquery ?
496 returnValue.pushStack( methodValue.get() ) :
502 this.each(function() {
503 var instance = $.data( this, fullName );
505 instance.option( options || {} )._init();
507 $.data( this, fullName, new object( options, this ) );
516 $.Widget = function( /* options, element */ ) {};
517 $.Widget._childConstructors = [];
519 $.Widget.prototype = {
520 widgetName: "widget",
521 widgetEventPrefix: "",
522 defaultElement: "<div>",
529 _createWidget: function( options, element ) {
530 element = $( element || this.defaultElement || this )[ 0 ];
531 this.element = $( element );
533 this.eventNamespace = "." + this.widgetName + this.uuid;
534 this.options = $.widget.extend( {},
536 this._getCreateOptions(),
540 this.hoverable = $();
541 this.focusable = $();
543 if ( element !== this ) {
544 $.data( element, this.widgetFullName, this );
545 this._on( true, this.element, {
546 remove: function( event ) {
547 if ( event.target === element ) {
552 this.document = $( element.style ?
553 // element within the document
554 element.ownerDocument :
555 // element is window or document
556 element.document || element );
557 this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
561 this._trigger( "create", null, this._getCreateEventData() );
564 _getCreateOptions: $.noop,
565 _getCreateEventData: $.noop,
569 destroy: function() {
571 // we can probably remove the unbind calls in 2.0
572 // all event bindings should go through this._on()
574 .unbind( this.eventNamespace )
576 // TODO remove dual storage
577 .removeData( this.widgetName )
578 .removeData( this.widgetFullName )
579 // support: jquery <1.6.3
580 // http://bugs.jquery.com/ticket/9413
581 .removeData( $.camelCase( this.widgetFullName ) );
583 .unbind( this.eventNamespace )
584 .removeAttr( "aria-disabled" )
586 this.widgetFullName + "-disabled " +
587 "ui-state-disabled" );
589 // clean up events and states
590 this.bindings.unbind( this.eventNamespace );
591 this.hoverable.removeClass( "ui-state-hover" );
592 this.focusable.removeClass( "ui-state-focus" );
600 option: function( key, value ) {
606 if ( arguments.length === 0 ) {
607 // don't return a reference to the internal hash
608 return $.widget.extend( {}, this.options );
611 if ( typeof key === "string" ) {
612 // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
614 parts = key.split( "." );
616 if ( parts.length ) {
617 curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
618 for ( i = 0; i < parts.length - 1; i++ ) {
619 curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
620 curOption = curOption[ parts[ i ] ];
623 if ( arguments.length === 1 ) {
624 return curOption[ key ] === undefined ? null : curOption[ key ];
626 curOption[ key ] = value;
628 if ( arguments.length === 1 ) {
629 return this.options[ key ] === undefined ? null : this.options[ key ];
631 options[ key ] = value;
635 this._setOptions( options );
639 _setOptions: function( options ) {
642 for ( key in options ) {
643 this._setOption( key, options[ key ] );
648 _setOption: function( key, value ) {
649 this.options[ key ] = value;
651 if ( key === "disabled" ) {
653 .toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value )
654 .attr( "aria-disabled", value );
655 this.hoverable.removeClass( "ui-state-hover" );
656 this.focusable.removeClass( "ui-state-focus" );
663 return this._setOption( "disabled", false );
665 disable: function() {
666 return this._setOption( "disabled", true );
669 _on: function( suppressDisabledCheck, element, handlers ) {
673 // no suppressDisabledCheck flag, shuffle arguments
674 if ( typeof suppressDisabledCheck !== "boolean" ) {
676 element = suppressDisabledCheck;
677 suppressDisabledCheck = false;
680 // no element argument, shuffle and use this.element
683 element = this.element;
684 delegateElement = this.widget();
686 // accept selectors, DOM elements
687 element = delegateElement = $( element );
688 this.bindings = this.bindings.add( element );
691 $.each( handlers, function( event, handler ) {
692 function handlerProxy() {
693 // allow widgets to customize the disabled handling
694 // - disabled as an array instead of boolean
695 // - disabled class as method for disabling individual parts
696 if ( !suppressDisabledCheck &&
697 ( instance.options.disabled === true ||
698 $( this ).hasClass( "ui-state-disabled" ) ) ) {
701 return ( typeof handler === "string" ? instance[ handler ] : handler )
702 .apply( instance, arguments );
705 // copy the guid so direct unbinding works
706 if ( typeof handler !== "string" ) {
707 handlerProxy.guid = handler.guid =
708 handler.guid || handlerProxy.guid || $.guid++;
711 var match = event.match( /^(\w+)\s*(.*)$/ ),
712 eventName = match[1] + instance.eventNamespace,
715 delegateElement.delegate( selector, eventName, handlerProxy );
717 element.bind( eventName, handlerProxy );
722 _off: function( element, eventName ) {
723 eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace;
724 element.unbind( eventName ).undelegate( eventName );
727 _delay: function( handler, delay ) {
728 function handlerProxy() {
729 return ( typeof handler === "string" ? instance[ handler ] : handler )
730 .apply( instance, arguments );
733 return setTimeout( handlerProxy, delay || 0 );
736 _hoverable: function( element ) {
737 this.hoverable = this.hoverable.add( element );
739 mouseenter: function( event ) {
740 $( event.currentTarget ).addClass( "ui-state-hover" );
742 mouseleave: function( event ) {
743 $( event.currentTarget ).removeClass( "ui-state-hover" );
748 _focusable: function( element ) {
749 this.focusable = this.focusable.add( element );
751 focusin: function( event ) {
752 $( event.currentTarget ).addClass( "ui-state-focus" );
754 focusout: function( event ) {
755 $( event.currentTarget ).removeClass( "ui-state-focus" );
760 _trigger: function( type, event, data ) {
762 callback = this.options[ type ];
765 event = $.Event( event );
766 event.type = ( type === this.widgetEventPrefix ?
768 this.widgetEventPrefix + type ).toLowerCase();
769 // the original event may come from any element
770 // so we need to reset the target on the new event
771 event.target = this.element[ 0 ];
773 // copy original event properties over to the new event
774 orig = event.originalEvent;
776 for ( prop in orig ) {
777 if ( !( prop in event ) ) {
778 event[ prop ] = orig[ prop ];
783 this.element.trigger( event, data );
784 return !( $.isFunction( callback ) &&
785 callback.apply( this.element[0], [ event ].concat( data ) ) === false ||
786 event.isDefaultPrevented() );
790 $.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
791 $.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
792 if ( typeof options === "string" ) {
793 options = { effect: options };
796 effectName = !options ?
798 options === true || typeof options === "number" ?
800 options.effect || defaultEffect;
801 options = options || {};
802 if ( typeof options === "number" ) {
803 options = { duration: options };
805 hasOptions = !$.isEmptyObject( options );
806 options.complete = callback;
807 if ( options.delay ) {
808 element.delay( options.delay );
810 if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {
811 element[ method ]( options );
812 } else if ( effectName !== method && element[ effectName ] ) {
813 element[ effectName ]( options.duration, options.easing, callback );
815 element.queue(function( next ) {
816 $( this )[ method ]();
818 callback.call( element[ 0 ] );
827 (function( $, undefined ) {
829 var mouseHandled = false;
830 $( document ).mouseup( function() {
831 mouseHandled = false;
834 $.widget("ui.mouse", {
837 cancel: "input,textarea,button,select,option",
841 _mouseInit: function() {
845 .bind("mousedown."+this.widgetName, function(event) {
846 return that._mouseDown(event);
848 .bind("click."+this.widgetName, function(event) {
849 if (true === $.data(event.target, that.widgetName + ".preventClickEvent")) {
850 $.removeData(event.target, that.widgetName + ".preventClickEvent");
851 event.stopImmediatePropagation();
856 this.started = false;
859 // TODO: make sure destroying one instance of mouse doesn't mess with
860 // other instances of mouse
861 _mouseDestroy: function() {
862 this.element.unbind("."+this.widgetName);
863 if ( this._mouseMoveDelegate ) {
865 .unbind("mousemove."+this.widgetName, this._mouseMoveDelegate)
866 .unbind("mouseup."+this.widgetName, this._mouseUpDelegate);
870 _mouseDown: function(event) {
871 // don't let more than one widget handle mouseStart
872 if( mouseHandled ) { return; }
874 // we may have missed mouseup (out of window)
875 (this._mouseStarted && this._mouseUp(event));
877 this._mouseDownEvent = event;
880 btnIsLeft = (event.which === 1),
881 // event.target.nodeName works around a bug in IE 8 with
882 // disabled inputs (#7620)
883 elIsCancel = (typeof this.options.cancel === "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false);
884 if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) {
888 this.mouseDelayMet = !this.options.delay;
889 if (!this.mouseDelayMet) {
890 this._mouseDelayTimer = setTimeout(function() {
891 that.mouseDelayMet = true;
892 }, this.options.delay);
895 if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
896 this._mouseStarted = (this._mouseStart(event) !== false);
897 if (!this._mouseStarted) {
898 event.preventDefault();
903 // Click event may never have fired (Gecko & Opera)
904 if (true === $.data(event.target, this.widgetName + ".preventClickEvent")) {
905 $.removeData(event.target, this.widgetName + ".preventClickEvent");
908 // these delegates are required to keep context
909 this._mouseMoveDelegate = function(event) {
910 return that._mouseMove(event);
912 this._mouseUpDelegate = function(event) {
913 return that._mouseUp(event);
916 .bind("mousemove."+this.widgetName, this._mouseMoveDelegate)
917 .bind("mouseup."+this.widgetName, this._mouseUpDelegate);
919 event.preventDefault();
925 _mouseMove: function(event) {
926 // IE mouseup check - mouseup happened when mouse was out of window
927 if ($.ui.ie && ( !document.documentMode || document.documentMode < 9 ) && !event.button) {
928 return this._mouseUp(event);
931 if (this._mouseStarted) {
932 this._mouseDrag(event);
933 return event.preventDefault();
936 if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
938 (this._mouseStart(this._mouseDownEvent, event) !== false);
939 (this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event));
942 return !this._mouseStarted;
945 _mouseUp: function(event) {
947 .unbind("mousemove."+this.widgetName, this._mouseMoveDelegate)
948 .unbind("mouseup."+this.widgetName, this._mouseUpDelegate);
950 if (this._mouseStarted) {
951 this._mouseStarted = false;
953 if (event.target === this._mouseDownEvent.target) {
954 $.data(event.target, this.widgetName + ".preventClickEvent", true);
957 this._mouseStop(event);
963 _mouseDistanceMet: function(event) {
965 Math.abs(this._mouseDownEvent.pageX - event.pageX),
966 Math.abs(this._mouseDownEvent.pageY - event.pageY)
967 ) >= this.options.distance
971 _mouseDelayMet: function(/* event */) {
972 return this.mouseDelayMet;
975 // These are placeholder methods, to be overriden by extending plugin
976 _mouseStart: function(/* event */) {},
977 _mouseDrag: function(/* event */) {},
978 _mouseStop: function(/* event */) {},
979 _mouseCapture: function(/* event */) { return true; }
983 (function( $, undefined ) {
985 function isOverAxis( x, reference, size ) {
986 return ( x > reference ) && ( x < ( reference + size ) );
989 function isFloating(item) {
990 return (/left|right/).test(item.css("float")) || (/inline|table-cell/).test(item.css("display"));
993 $.widget("ui.sortable", $.ui.mouse, {
995 widgetEventPrefix: "sort",
1005 forcePlaceholderSize: false,
1006 forceHelperSize: false,
1015 scrollSensitivity: 20,
1018 tolerance: "intersect",
1035 _create: function() {
1037 var o = this.options;
1038 this.containerCache = {};
1039 this.element.addClass("ui-sortable");
1044 //Let's determine if the items are being displayed horizontally
1045 this.floating = this.items.length ? o.axis === "x" || isFloating(this.items[0].item) : false;
1047 //Let's determine the parent's offset
1048 this.offset = this.element.offset();
1050 //Initialize mouse events for interaction
1058 _destroy: function() {
1060 .removeClass("ui-sortable ui-sortable-disabled");
1061 this._mouseDestroy();
1063 for ( var i = this.items.length - 1; i >= 0; i-- ) {
1064 this.items[i].item.removeData(this.widgetName + "-item");
1070 _setOption: function(key, value){
1071 if ( key === "disabled" ) {
1072 this.options[ key ] = value;
1074 this.widget().toggleClass( "ui-sortable-disabled", !!value );
1076 // Don't call widget base _setOption for disable as it adds ui-state-disabled class
1077 $.Widget.prototype._setOption.apply(this, arguments);
1081 _mouseCapture: function(event, overrideHandle) {
1082 var currentItem = null,
1083 validHandle = false,
1086 if (this.reverting) {
1090 if(this.options.disabled || this.options.type === "static") {
1094 //We have to refresh the items data once first
1095 this._refreshItems(event);
1097 //Find out if the clicked node (or one of its parents) is a actual item in this.items
1098 $(event.target).parents().each(function() {
1099 if($.data(this, that.widgetName + "-item") === that) {
1100 currentItem = $(this);
1104 if($.data(event.target, that.widgetName + "-item") === that) {
1105 currentItem = $(event.target);
1111 if(this.options.handle && !overrideHandle) {
1112 $(this.options.handle, currentItem).find("*").addBack().each(function() {
1113 if(this === event.target) {
1122 this.currentItem = currentItem;
1123 this._removeCurrentsFromItems();
1128 _mouseStart: function(event, overrideHandle, noActivation) {
1133 this.currentContainer = this;
1135 //We only need to call refreshPositions, because the refreshItems call has been moved to mouseCapture
1136 this.refreshPositions();
1138 //Create and append the visible helper
1139 this.helper = this._createHelper(event);
1141 //Cache the helper size
1142 this._cacheHelperProportions();
1145 * - Position generation -
1146 * This block generates everything position related - it's the core of draggables.
1149 //Cache the margins of the original element
1150 this._cacheMargins();
1152 //Get the next scrolling parent
1153 this.scrollParent = this.helper.scrollParent();
1155 //The element's absolute position on the page minus margins
1156 this.offset = this.currentItem.offset();
1158 top: this.offset.top - this.margins.top,
1159 left: this.offset.left - this.margins.left
1162 $.extend(this.offset, {
1163 click: { //Where the click happened, relative to the element
1164 left: event.pageX - this.offset.left,
1165 top: event.pageY - this.offset.top
1167 parent: this._getParentOffset(),
1168 relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
1171 // Only after we got the offset, we can change the helper's position to absolute
1172 // TODO: Still need to figure out a way to make relative sorting possible
1173 this.helper.css("position", "absolute");
1174 this.cssPosition = this.helper.css("position");
1176 //Generate the original position
1177 this.originalPosition = this._generatePosition(event);
1178 this.originalPageX = event.pageX;
1179 this.originalPageY = event.pageY;
1181 //Adjust the mouse offset relative to the helper if "cursorAt" is supplied
1182 (o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
1184 //Cache the former DOM position
1185 this.domPosition = { prev: this.currentItem.prev()[0], parent: this.currentItem.parent()[0] };
1187 //If the helper is not the original, hide the original so it's not playing any role during the drag, won't cause anything bad this way
1188 if(this.helper[0] !== this.currentItem[0]) {
1189 this.currentItem.hide();
1192 //Create the placeholder
1193 this._createPlaceholder();
1195 //Set a containment if given in the options
1197 this._setContainment();
1200 if( o.cursor && o.cursor !== "auto" ) { // cursor option
1201 body = this.document.find( "body" );
1204 this.storedCursor = body.css( "cursor" );
1205 body.css( "cursor", o.cursor );
1207 this.storedStylesheet = $( "<style>*{ cursor: "+o.cursor+" !important; }</style>" ).appendTo( body );
1210 if(o.opacity) { // opacity option
1211 if (this.helper.css("opacity")) {
1212 this._storedOpacity = this.helper.css("opacity");
1214 this.helper.css("opacity", o.opacity);
1217 if(o.zIndex) { // zIndex option
1218 if (this.helper.css("zIndex")) {
1219 this._storedZIndex = this.helper.css("zIndex");
1221 this.helper.css("zIndex", o.zIndex);
1225 if(this.scrollParent[0] !== document && this.scrollParent[0].tagName !== "HTML") {
1226 this.overflowOffset = this.scrollParent.offset();
1230 this._trigger("start", event, this._uiHash());
1232 //Recache the helper size
1233 if(!this._preserveHelperProportions) {
1234 this._cacheHelperProportions();
1238 //Post "activate" events to possible containers
1239 if( !noActivation ) {
1240 for ( i = this.containers.length - 1; i >= 0; i-- ) {
1241 this.containers[ i ]._trigger( "activate", event, this._uiHash( this ) );
1245 //Prepare possible droppables
1246 if($.ui.ddmanager) {
1247 $.ui.ddmanager.current = this;
1250 if ($.ui.ddmanager && !o.dropBehaviour) {
1251 $.ui.ddmanager.prepareOffsets(this, event);
1254 this.dragging = true;
1256 this.helper.addClass("ui-sortable-helper");
1257 this._mouseDrag(event); //Execute the drag once - this causes the helper not to be visible before getting its correct position
1262 _mouseDrag: function(event) {
1263 var i, item, itemElement, intersection,
1267 //Compute the helpers position
1268 this.position = this._generatePosition(event);
1269 this.positionAbs = this._convertPositionTo("absolute");
1271 if (!this.lastPositionAbs) {
1272 this.lastPositionAbs = this.positionAbs;
1276 if(this.options.scroll) {
1277 if(this.scrollParent[0] !== document && this.scrollParent[0].tagName !== "HTML") {
1279 if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) {
1280 this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed;
1281 } else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity) {
1282 this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed;
1285 if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) {
1286 this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed;
1287 } else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity) {
1288 this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed;
1293 if(event.pageY - $(document).scrollTop() < o.scrollSensitivity) {
1294 scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
1295 } else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) {
1296 scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
1299 if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity) {
1300 scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
1301 } else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) {
1302 scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
1307 if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) {
1308 $.ui.ddmanager.prepareOffsets(this, event);
1312 //Regenerate the absolute position used for position checks
1313 this.positionAbs = this._convertPositionTo("absolute");
1315 //Set the helper position
1316 if(!this.options.axis || this.options.axis !== "y") {
1317 this.helper[0].style.left = this.position.left+"px";
1319 if(!this.options.axis || this.options.axis !== "x") {
1320 this.helper[0].style.top = this.position.top+"px";
1324 for (i = this.items.length - 1; i >= 0; i--) {
1326 //Cache variables and intersection, continue if no intersection
1327 item = this.items[i];
1328 itemElement = item.item[0];
1329 intersection = this._intersectsWithPointer(item);
1330 if (!intersection) {
1334 // Only put the placeholder inside the current Container, skip all
1335 // items from other containers. This works because when moving
1336 // an item from one container to another the
1337 // currentContainer is switched before the placeholder is moved.
1339 // Without this, moving items in "sub-sortables" can cause
1340 // the placeholder to jitter beetween the outer and inner container.
1341 if (item.instance !== this.currentContainer) {
1345 // cannot intersect with itself
1346 // no useless actions that have been done before
1347 // no action if the item moved is the parent of the item checked
1348 if (itemElement !== this.currentItem[0] &&
1349 this.placeholder[intersection === 1 ? "next" : "prev"]()[0] !== itemElement &&
1350 !$.contains(this.placeholder[0], itemElement) &&
1351 (this.options.type === "semi-dynamic" ? !$.contains(this.element[0], itemElement) : true)
1354 this.direction = intersection === 1 ? "down" : "up";
1356 if (this.options.tolerance === "pointer" || this._intersectsWithSides(item)) {
1357 this._rearrange(event, item);
1362 this._trigger("change", event, this._uiHash());
1367 //Post events to containers
1368 this._contactContainers(event);
1370 //Interconnect with droppables
1371 if($.ui.ddmanager) {
1372 $.ui.ddmanager.drag(this, event);
1376 this._trigger("sort", event, this._uiHash());
1378 this.lastPositionAbs = this.positionAbs;
1383 _mouseStop: function(event, noPropagation) {
1389 //If we are using droppables, inform the manager about the drop
1390 if ($.ui.ddmanager && !this.options.dropBehaviour) {
1391 $.ui.ddmanager.drop(this, event);
1394 if(this.options.revert) {
1396 cur = this.placeholder.offset(),
1397 axis = this.options.axis,
1400 if ( !axis || axis === "x" ) {
1401 animation.left = cur.left - this.offset.parent.left - this.margins.left + (this.offsetParent[0] === document.body ? 0 : this.offsetParent[0].scrollLeft);
1403 if ( !axis || axis === "y" ) {
1404 animation.top = cur.top - this.offset.parent.top - this.margins.top + (this.offsetParent[0] === document.body ? 0 : this.offsetParent[0].scrollTop);
1406 this.reverting = true;
1407 $(this.helper).animate( animation, parseInt(this.options.revert, 10) || 500, function() {
1411 this._clear(event, noPropagation);
1418 cancel: function() {
1422 this._mouseUp({ target: null });
1424 if(this.options.helper === "original") {
1425 this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
1427 this.currentItem.show();
1430 //Post deactivating events to containers
1431 for (var i = this.containers.length - 1; i >= 0; i--){
1432 this.containers[i]._trigger("deactivate", null, this._uiHash(this));
1433 if(this.containers[i].containerCache.over) {
1434 this.containers[i]._trigger("out", null, this._uiHash(this));
1435 this.containers[i].containerCache.over = 0;
1441 if (this.placeholder) {
1442 //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
1443 if(this.placeholder[0].parentNode) {
1444 this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
1446 if(this.options.helper !== "original" && this.helper && this.helper[0].parentNode) {
1447 this.helper.remove();
1457 if(this.domPosition.prev) {
1458 $(this.domPosition.prev).after(this.currentItem);
1460 $(this.domPosition.parent).prepend(this.currentItem);
1468 serialize: function(o) {
1470 var items = this._getItemsAsjQuery(o && o.connected),
1474 $(items).each(function() {
1475 var res = ($(o.item || this).attr(o.attribute || "id") || "").match(o.expression || (/(.+)[\-=_](.+)/));
1477 str.push((o.key || res[1]+"[]")+"="+(o.key && o.expression ? res[1] : res[2]));
1481 if(!str.length && o.key) {
1482 str.push(o.key + "=");
1485 return str.join("&");
1489 toArray: function(o) {
1491 var items = this._getItemsAsjQuery(o && o.connected),
1496 items.each(function() { ret.push($(o.item || this).attr(o.attribute || "id") || ""); });
1501 /* Be careful with the following core functions */
1502 _intersectsWith: function(item) {
1504 var x1 = this.positionAbs.left,
1505 x2 = x1 + this.helperProportions.width,
1506 y1 = this.positionAbs.top,
1507 y2 = y1 + this.helperProportions.height,
1511 b = t + item.height,
1512 dyClick = this.offset.click.top,
1513 dxClick = this.offset.click.left,
1514 isOverElementHeight = ( this.options.axis === "x" ) || ( ( y1 + dyClick ) > t && ( y1 + dyClick ) < b ),
1515 isOverElementWidth = ( this.options.axis === "y" ) || ( ( x1 + dxClick ) > l && ( x1 + dxClick ) < r ),
1516 isOverElement = isOverElementHeight && isOverElementWidth;
1518 if ( this.options.tolerance === "pointer" ||
1519 this.options.forcePointerForContainers ||
1520 (this.options.tolerance !== "pointer" && this.helperProportions[this.floating ? "width" : "height"] > item[this.floating ? "width" : "height"])
1522 return isOverElement;
1525 return (l < x1 + (this.helperProportions.width / 2) && // Right Half
1526 x2 - (this.helperProportions.width / 2) < r && // Left Half
1527 t < y1 + (this.helperProportions.height / 2) && // Bottom Half
1528 y2 - (this.helperProportions.height / 2) < b ); // Top Half
1533 _intersectsWithPointer: function(item) {
1535 var isOverElementHeight = (this.options.axis === "x") || isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height),
1536 isOverElementWidth = (this.options.axis === "y") || isOverAxis(this.positionAbs.left + this.offset.click.left, item.left, item.width),
1537 isOverElement = isOverElementHeight && isOverElementWidth,
1538 verticalDirection = this._getDragVerticalDirection(),
1539 horizontalDirection = this._getDragHorizontalDirection();
1541 if (!isOverElement) {
1545 return this.floating ?
1546 ( ((horizontalDirection && horizontalDirection === "right") || verticalDirection === "down") ? 2 : 1 )
1547 : ( verticalDirection && (verticalDirection === "down" ? 2 : 1) );
1551 _intersectsWithSides: function(item) {
1553 var isOverBottomHalf = isOverAxis(this.positionAbs.top + this.offset.click.top, item.top + (item.height/2), item.height),
1554 isOverRightHalf = isOverAxis(this.positionAbs.left + this.offset.click.left, item.left + (item.width/2), item.width),
1555 verticalDirection = this._getDragVerticalDirection(),
1556 horizontalDirection = this._getDragHorizontalDirection();
1558 if (this.floating && horizontalDirection) {
1559 return ((horizontalDirection === "right" && isOverRightHalf) || (horizontalDirection === "left" && !isOverRightHalf));
1561 return verticalDirection && ((verticalDirection === "down" && isOverBottomHalf) || (verticalDirection === "up" && !isOverBottomHalf));
1566 _getDragVerticalDirection: function() {
1567 var delta = this.positionAbs.top - this.lastPositionAbs.top;
1568 return delta !== 0 && (delta > 0 ? "down" : "up");
1571 _getDragHorizontalDirection: function() {
1572 var delta = this.positionAbs.left - this.lastPositionAbs.left;
1573 return delta !== 0 && (delta > 0 ? "right" : "left");
1576 refresh: function(event) {
1577 this._refreshItems(event);
1578 this.refreshPositions();
1582 _connectWith: function() {
1583 var options = this.options;
1584 return options.connectWith.constructor === String ? [options.connectWith] : options.connectWith;
1587 _getItemsAsjQuery: function(connected) {
1589 var i, j, cur, inst,
1592 connectWith = this._connectWith();
1594 if(connectWith && connected) {
1595 for (i = connectWith.length - 1; i >= 0; i--){
1596 cur = $(connectWith[i]);
1597 for ( j = cur.length - 1; j >= 0; j--){
1598 inst = $.data(cur[j], this.widgetFullName);
1599 if(inst && inst !== this && !inst.options.disabled) {
1600 queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element) : $(inst.options.items, inst.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"), inst]);
1606 queries.push([$.isFunction(this.options.items) ? this.options.items.call(this.element, null, { options: this.options, item: this.currentItem }) : $(this.options.items, this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"), this]);
1608 function addItems() {
1611 for (i = queries.length - 1; i >= 0; i--){
1612 queries[i][0].each( addItems );
1619 _removeCurrentsFromItems: function() {
1621 var list = this.currentItem.find(":data(" + this.widgetName + "-item)");
1623 this.items = $.grep(this.items, function (item) {
1624 for (var j=0; j < list.length; j++) {
1625 if(list[j] === item.item[0]) {
1634 _refreshItems: function(event) {
1637 this.containers = [this];
1639 var i, j, cur, inst, targetData, _queries, item, queriesLength,
1641 queries = [[$.isFunction(this.options.items) ? this.options.items.call(this.element[0], event, { item: this.currentItem }) : $(this.options.items, this.element), this]],
1642 connectWith = this._connectWith();
1644 if(connectWith && this.ready) { //Shouldn't be run the first time through due to massive slow-down
1645 for (i = connectWith.length - 1; i >= 0; i--){
1646 cur = $(connectWith[i]);
1647 for (j = cur.length - 1; j >= 0; j--){
1648 inst = $.data(cur[j], this.widgetFullName);
1649 if(inst && inst !== this && !inst.options.disabled) {
1650 queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element[0], event, { item: this.currentItem }) : $(inst.options.items, inst.element), inst]);
1651 this.containers.push(inst);
1657 for (i = queries.length - 1; i >= 0; i--) {
1658 targetData = queries[i][1];
1659 _queries = queries[i][0];
1661 for (j=0, queriesLength = _queries.length; j < queriesLength; j++) {
1662 item = $(_queries[j]);
1664 item.data(this.widgetName + "-item", targetData); // Data for target checking (mouse manager)
1668 instance: targetData,
1669 width: 0, height: 0,
1677 refreshPositions: function(fast) {
1679 //This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change
1680 if(this.offsetParent && this.helper) {
1681 this.offset.parent = this._getParentOffset();
1686 for (i = this.items.length - 1; i >= 0; i--){
1687 item = this.items[i];
1689 //We ignore calculating positions of all connected containers when we're not over them
1690 if(item.instance !== this.currentContainer && this.currentContainer && item.item[0] !== this.currentItem[0]) {
1694 t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item;
1697 item.width = t.outerWidth();
1698 item.height = t.outerHeight();
1706 if(this.options.custom && this.options.custom.refreshContainers) {
1707 this.options.custom.refreshContainers.call(this);
1709 for (i = this.containers.length - 1; i >= 0; i--){
1710 p = this.containers[i].element.offset();
1711 this.containers[i].containerCache.left = p.left;
1712 this.containers[i].containerCache.top = p.top;
1713 this.containers[i].containerCache.width = this.containers[i].element.outerWidth();
1714 this.containers[i].containerCache.height = this.containers[i].element.outerHeight();
1721 _createPlaceholder: function(that) {
1722 that = that || this;
1726 if(!o.placeholder || o.placeholder.constructor === String) {
1727 className = o.placeholder;
1729 element: function() {
1731 var nodeName = that.currentItem[0].nodeName.toLowerCase(),
1732 element = $( "<" + nodeName + ">", that.document[0] )
1733 .addClass(className || that.currentItem[0].className+" ui-sortable-placeholder")
1734 .removeClass("ui-sortable-helper");
1736 if ( nodeName === "tr" ) {
1737 that.currentItem.children().each(function() {
1738 $( "<td> </td>", that.document[0] )
1739 .attr( "colspan", $( this ).attr( "colspan" ) || 1 )
1740 .appendTo( element );
1742 } else if ( nodeName === "img" ) {
1743 element.attr( "src", that.currentItem.attr( "src" ) );
1747 element.css( "visibility", "hidden" );
1752 update: function(container, p) {
1754 // 1. If a className is set as 'placeholder option, we don't force sizes - the class is responsible for that
1755 // 2. The option 'forcePlaceholderSize can be enabled to force it even if a class name is specified
1756 if(className && !o.forcePlaceholderSize) {
1760 //If the element doesn't have a actual height by itself (without styles coming from a stylesheet), it receives the inline height from the dragged item
1761 if(!p.height()) { p.height(that.currentItem.innerHeight() - parseInt(that.currentItem.css("paddingTop")||0, 10) - parseInt(that.currentItem.css("paddingBottom")||0, 10)); }
1762 if(!p.width()) { p.width(that.currentItem.innerWidth() - parseInt(that.currentItem.css("paddingLeft")||0, 10) - parseInt(that.currentItem.css("paddingRight")||0, 10)); }
1767 //Create the placeholder
1768 that.placeholder = $(o.placeholder.element.call(that.element, that.currentItem));
1770 //Append it after the actual current item
1771 that.currentItem.after(that.placeholder);
1773 //Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317)
1774 o.placeholder.update(that, that.placeholder);
1778 _contactContainers: function(event) {
1779 var i, j, dist, itemWithLeastDistance, posProperty, sizeProperty, base, cur, nearBottom, floating,
1780 innermostContainer = null,
1781 innermostIndex = null;
1783 // get innermost container that intersects with item
1784 for (i = this.containers.length - 1; i >= 0; i--) {
1786 // never consider a container that's located within the item itself
1787 if($.contains(this.currentItem[0], this.containers[i].element[0])) {
1791 if(this._intersectsWith(this.containers[i].containerCache)) {
1793 // if we've already found a container and it's more "inner" than this, then continue
1794 if(innermostContainer && $.contains(this.containers[i].element[0], innermostContainer.element[0])) {
1798 innermostContainer = this.containers[i];
1802 // container doesn't intersect. trigger "out" event if necessary
1803 if(this.containers[i].containerCache.over) {
1804 this.containers[i]._trigger("out", event, this._uiHash(this));
1805 this.containers[i].containerCache.over = 0;
1811 // if no intersecting containers found, return
1812 if(!innermostContainer) {
1816 // move the item into the container if it's not there already
1817 if(this.containers.length === 1) {
1818 if (!this.containers[innermostIndex].containerCache.over) {
1819 this.containers[innermostIndex]._trigger("over", event, this._uiHash(this));
1820 this.containers[innermostIndex].containerCache.over = 1;
1824 //When entering a new container, we will find the item with the least distance and append our item near it
1826 itemWithLeastDistance = null;
1827 floating = innermostContainer.floating || isFloating(this.currentItem);
1828 posProperty = floating ? "left" : "top";
1829 sizeProperty = floating ? "width" : "height";
1830 base = this.positionAbs[posProperty] + this.offset.click[posProperty];
1831 for (j = this.items.length - 1; j >= 0; j--) {
1832 if(!$.contains(this.containers[innermostIndex].element[0], this.items[j].item[0])) {
1835 if(this.items[j].item[0] === this.currentItem[0]) {
1838 if (floating && !isOverAxis(this.positionAbs.top + this.offset.click.top, this.items[j].top, this.items[j].height)) {
1841 cur = this.items[j].item.offset()[posProperty];
1843 if(Math.abs(cur - base) > Math.abs(cur + this.items[j][sizeProperty] - base)){
1845 cur += this.items[j][sizeProperty];
1848 if(Math.abs(cur - base) < dist) {
1849 dist = Math.abs(cur - base); itemWithLeastDistance = this.items[j];
1850 this.direction = nearBottom ? "up": "down";
1854 //Check if dropOnEmpty is enabled
1855 if(!itemWithLeastDistance && !this.options.dropOnEmpty) {
1859 if(this.currentContainer === this.containers[innermostIndex]) {
1863 itemWithLeastDistance ? this._rearrange(event, itemWithLeastDistance, null, true) : this._rearrange(event, null, this.containers[innermostIndex].element, true);
1864 this._trigger("change", event, this._uiHash());
1865 this.containers[innermostIndex]._trigger("change", event, this._uiHash(this));
1866 this.currentContainer = this.containers[innermostIndex];
1868 //Update the placeholder
1869 this.options.placeholder.update(this.currentContainer, this.placeholder);
1871 this.containers[innermostIndex]._trigger("over", event, this._uiHash(this));
1872 this.containers[innermostIndex].containerCache.over = 1;
1878 _createHelper: function(event) {
1880 var o = this.options,
1881 helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event, this.currentItem])) : (o.helper === "clone" ? this.currentItem.clone() : this.currentItem);
1883 //Add the helper to the DOM if that didn't happen already
1884 if(!helper.parents("body").length) {
1885 $(o.appendTo !== "parent" ? o.appendTo : this.currentItem[0].parentNode)[0].appendChild(helper[0]);
1888 if(helper[0] === this.currentItem[0]) {
1889 this._storedCSS = { width: this.currentItem[0].style.width, height: this.currentItem[0].style.height, position: this.currentItem.css("position"), top: this.currentItem.css("top"), left: this.currentItem.css("left") };
1892 if(!helper[0].style.width || o.forceHelperSize) {
1893 helper.width(this.currentItem.width());
1895 if(!helper[0].style.height || o.forceHelperSize) {
1896 helper.height(this.currentItem.height());
1903 _adjustOffsetFromHelper: function(obj) {
1904 if (typeof obj === "string") {
1905 obj = obj.split(" ");
1907 if ($.isArray(obj)) {
1908 obj = {left: +obj[0], top: +obj[1] || 0};
1910 if ("left" in obj) {
1911 this.offset.click.left = obj.left + this.margins.left;
1913 if ("right" in obj) {
1914 this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
1917 this.offset.click.top = obj.top + this.margins.top;
1919 if ("bottom" in obj) {
1920 this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
1924 _getParentOffset: function() {
1927 //Get the offsetParent and cache its position
1928 this.offsetParent = this.helper.offsetParent();
1929 var po = this.offsetParent.offset();
1931 // This is a special case where we need to modify a offset calculated on start, since the following happened:
1932 // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
1933 // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
1934 // the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
1935 if(this.cssPosition === "absolute" && this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) {
1936 po.left += this.scrollParent.scrollLeft();
1937 po.top += this.scrollParent.scrollTop();
1940 // This needs to be actually done for all browsers, since pageX/pageY includes this information
1941 // with an ugly IE fix
1942 if( this.offsetParent[0] === document.body || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() === "html" && $.ui.ie)) {
1943 po = { top: 0, left: 0 };
1947 top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
1948 left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
1953 _getRelativeOffset: function() {
1955 if(this.cssPosition === "relative") {
1956 var p = this.currentItem.position();
1958 top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
1959 left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
1962 return { top: 0, left: 0 };
1967 _cacheMargins: function() {
1969 left: (parseInt(this.currentItem.css("marginLeft"),10) || 0),
1970 top: (parseInt(this.currentItem.css("marginTop"),10) || 0)
1974 _cacheHelperProportions: function() {
1975 this.helperProportions = {
1976 width: this.helper.outerWidth(),
1977 height: this.helper.outerHeight()
1981 _setContainment: function() {
1985 if(o.containment === "parent") {
1986 o.containment = this.helper[0].parentNode;
1988 if(o.containment === "document" || o.containment === "window") {
1989 this.containment = [
1990 0 - this.offset.relative.left - this.offset.parent.left,
1991 0 - this.offset.relative.top - this.offset.parent.top,
1992 $(o.containment === "document" ? document : window).width() - this.helperProportions.width - this.margins.left,
1993 ($(o.containment === "document" ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top
1997 if(!(/^(document|window|parent)$/).test(o.containment)) {
1998 ce = $(o.containment)[0];
1999 co = $(o.containment).offset();
2000 over = ($(ce).css("overflow") !== "hidden");
2002 this.containment = [
2003 co.left + (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0) - this.margins.left,
2004 co.top + (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0) - this.margins.top,
2005 co.left+(over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left,
2006 co.top+(over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top
2012 _convertPositionTo: function(d, pos) {
2015 pos = this.position;
2017 var mod = d === "absolute" ? 1 : -1,
2018 scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent,
2019 scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
2023 pos.top + // The absolute mouse position
2024 this.offset.relative.top * mod + // Only for relative positioned nodes: Relative offset from element to offset parent
2025 this.offset.parent.top * mod - // The offsetParent's offset without borders (offset + border)
2026 ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod)
2029 pos.left + // The absolute mouse position
2030 this.offset.relative.left * mod + // Only for relative positioned nodes: Relative offset from element to offset parent
2031 this.offset.parent.left * mod - // The offsetParent's offset without borders (offset + border)
2032 ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod)
2038 _generatePosition: function(event) {
2042 pageX = event.pageX,
2043 pageY = event.pageY,
2044 scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
2046 // This is another very weird special case that only happens for relative elements:
2047 // 1. If the css position is relative
2048 // 2. and the scroll parent is the document or similar to the offset parent
2049 // we have to refresh the relative offset during the scroll so there are no jumps
2050 if(this.cssPosition === "relative" && !(this.scrollParent[0] !== document && this.scrollParent[0] !== this.offsetParent[0])) {
2051 this.offset.relative = this._getRelativeOffset();
2055 * - Position constraining -
2056 * Constrain the position to a mix of grid, containment.
2059 if(this.originalPosition) { //If we are not dragging yet, we won't check for options
2061 if(this.containment) {
2062 if(event.pageX - this.offset.click.left < this.containment[0]) {
2063 pageX = this.containment[0] + this.offset.click.left;
2065 if(event.pageY - this.offset.click.top < this.containment[1]) {
2066 pageY = this.containment[1] + this.offset.click.top;
2068 if(event.pageX - this.offset.click.left > this.containment[2]) {
2069 pageX = this.containment[2] + this.offset.click.left;
2071 if(event.pageY - this.offset.click.top > this.containment[3]) {
2072 pageY = this.containment[3] + this.offset.click.top;
2077 top = this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1];
2078 pageY = this.containment ? ( (top - this.offset.click.top >= this.containment[1] && top - this.offset.click.top <= this.containment[3]) ? top : ((top - this.offset.click.top >= this.containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;
2080 left = this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0];
2081 pageX = this.containment ? ( (left - this.offset.click.left >= this.containment[0] && left - this.offset.click.left <= this.containment[2]) ? left : ((left - this.offset.click.left >= this.containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
2088 pageY - // The absolute mouse position
2089 this.offset.click.top - // Click offset (relative to the element)
2090 this.offset.relative.top - // Only for relative positioned nodes: Relative offset from element to offset parent
2091 this.offset.parent.top + // The offsetParent's offset without borders (offset + border)
2092 ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ))
2095 pageX - // The absolute mouse position
2096 this.offset.click.left - // Click offset (relative to the element)
2097 this.offset.relative.left - // Only for relative positioned nodes: Relative offset from element to offset parent
2098 this.offset.parent.left + // The offsetParent's offset without borders (offset + border)
2099 ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ))
2105 _rearrange: function(event, i, a, hardRefresh) {
2107 a ? a[0].appendChild(this.placeholder[0]) : i.item[0].parentNode.insertBefore(this.placeholder[0], (this.direction === "down" ? i.item[0] : i.item[0].nextSibling));
2109 //Various things done here to improve the performance:
2110 // 1. we create a setTimeout, that calls refreshPositions
2111 // 2. on the instance, we have a counter variable, that get's higher after every append
2112 // 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same
2113 // 4. this lets only the last addition to the timeout stack through
2114 this.counter = this.counter ? ++this.counter : 1;
2115 var counter = this.counter;
2117 this._delay(function() {
2118 if(counter === this.counter) {
2119 this.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove
2125 _clear: function(event, noPropagation) {
2127 this.reverting = false;
2128 // We delay all events that have to be triggered to after the point where the placeholder has been removed and
2129 // everything else normalized again
2131 delayedTriggers = [];
2133 // We first have to update the dom position of the actual currentItem
2134 // Note: don't do it if the current item is already removed (by a user), or it gets reappended (see #4088)
2135 if(!this._noFinalSort && this.currentItem.parent().length) {
2136 this.placeholder.before(this.currentItem);
2138 this._noFinalSort = null;
2140 if(this.helper[0] === this.currentItem[0]) {
2141 for(i in this._storedCSS) {
2142 if(this._storedCSS[i] === "auto" || this._storedCSS[i] === "static") {
2143 this._storedCSS[i] = "";
2146 this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
2148 this.currentItem.show();
2151 if(this.fromOutside && !noPropagation) {
2152 delayedTriggers.push(function(event) { this._trigger("receive", event, this._uiHash(this.fromOutside)); });
2154 if((this.fromOutside || this.domPosition.prev !== this.currentItem.prev().not(".ui-sortable-helper")[0] || this.domPosition.parent !== this.currentItem.parent()[0]) && !noPropagation) {
2155 delayedTriggers.push(function(event) { this._trigger("update", event, this._uiHash()); }); //Trigger update callback if the DOM position has changed
2158 // Check if the items Container has Changed and trigger appropriate
2160 if (this !== this.currentContainer) {
2161 if(!noPropagation) {
2162 delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); });
2163 delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); }; }).call(this, this.currentContainer));
2164 delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this)); }; }).call(this, this.currentContainer));
2169 //Post events to containers
2170 function delayEvent( type, instance, container ) {
2171 return function( event ) {
2172 container._trigger( type, event, instance._uiHash( instance ) );
2175 for (i = this.containers.length - 1; i >= 0; i--){
2176 if (!noPropagation) {
2177 delayedTriggers.push( delayEvent( "deactivate", this, this.containers[ i ] ) );
2179 if(this.containers[i].containerCache.over) {
2180 delayedTriggers.push( delayEvent( "out", this, this.containers[ i ] ) );
2181 this.containers[i].containerCache.over = 0;
2185 //Do what was originally in plugins
2186 if ( this.storedCursor ) {
2187 this.document.find( "body" ).css( "cursor", this.storedCursor );
2188 this.storedStylesheet.remove();
2190 if(this._storedOpacity) {
2191 this.helper.css("opacity", this._storedOpacity);
2193 if(this._storedZIndex) {
2194 this.helper.css("zIndex", this._storedZIndex === "auto" ? "" : this._storedZIndex);
2197 this.dragging = false;
2198 if(this.cancelHelperRemoval) {
2199 if(!noPropagation) {
2200 this._trigger("beforeStop", event, this._uiHash());
2201 for (i=0; i < delayedTriggers.length; i++) {
2202 delayedTriggers[i].call(this, event);
2203 } //Trigger all delayed events
2204 this._trigger("stop", event, this._uiHash());
2207 this.fromOutside = false;
2211 if(!noPropagation) {
2212 this._trigger("beforeStop", event, this._uiHash());
2215 //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
2216 this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
2218 if(this.helper[0] !== this.currentItem[0]) {
2219 this.helper.remove();
2223 if(!noPropagation) {
2224 for (i=0; i < delayedTriggers.length; i++) {
2225 delayedTriggers[i].call(this, event);
2226 } //Trigger all delayed events
2227 this._trigger("stop", event, this._uiHash());
2230 this.fromOutside = false;
2235 _trigger: function() {
2236 if ($.Widget.prototype._trigger.apply(this, arguments) === false) {
2241 _uiHash: function(_inst) {
2242 var inst = _inst || this;
2244 helper: inst.helper,
2245 placeholder: inst.placeholder || $([]),
2246 position: inst.position,
2247 originalPosition: inst.originalPosition,
2248 offset: inst.positionAbs,
2249 item: inst.currentItem,
2250 sender: _inst ? _inst.element : null