1 /*! jQuery UI - v1.9.2 - 2013-01-02
3 * Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.draggable.js, jquery.ui.droppable.js, jquery.ui.selectable.js, jquery.ui.sortable.js
4 * Copyright (c) 2013 jQuery Foundation and other contributors Licensed MIT */
6 (function( $, undefined ) {
9 runiqueId = /^ui-id-\d+$/;
11 // prevent duplicate loading
12 // this is only a problem because we proxy existing functions
13 // and we don't want to double proxy them
51 focus: function( delay, fn ) {
52 return typeof delay === "number" ?
53 this.each(function() {
55 setTimeout(function() {
62 this._focus.apply( this, arguments );
65 scrollParent: function() {
67 if (($.ui.ie && (/(static|relative)/).test(this.css('position'))) || (/absolute/).test(this.css('position'))) {
68 scrollParent = this.parents().filter(function() {
69 return (/(relative|absolute|fixed)/).test($.css(this,'position')) && (/(auto|scroll)/).test($.css(this,'overflow')+$.css(this,'overflow-y')+$.css(this,'overflow-x'));
72 scrollParent = this.parents().filter(function() {
73 return (/(auto|scroll)/).test($.css(this,'overflow')+$.css(this,'overflow-y')+$.css(this,'overflow-x'));
77 return (/fixed/).test(this.css('position')) || !scrollParent.length ? $(document) : scrollParent;
80 zIndex: function( zIndex ) {
81 if ( zIndex !== undefined ) {
82 return this.css( "zIndex", zIndex );
86 var elem = $( this[ 0 ] ), position, value;
87 while ( elem.length && elem[ 0 ] !== document ) {
88 // Ignore z-index if position is set to a value where z-index is ignored by the browser
89 // This makes behavior of this function consistent across browsers
90 // WebKit always returns auto if the element is positioned
91 position = elem.css( "position" );
92 if ( position === "absolute" || position === "relative" || position === "fixed" ) {
93 // IE returns 0 when zIndex is not specified
94 // other browsers return a string
95 // we ignore the case of nested elements with an explicit value of 0
96 // <div style="z-index: -10;"><div style="z-index: 0;"></div></div>
97 value = parseInt( elem.css( "zIndex" ), 10 );
98 if ( !isNaN( value ) && value !== 0 ) {
102 elem = elem.parent();
109 uniqueId: function() {
110 return this.each(function() {
112 this.id = "ui-id-" + (++uuid);
117 removeUniqueId: function() {
118 return this.each(function() {
119 if ( runiqueId.test( this.id ) ) {
120 $( this ).removeAttr( "id" );
127 function focusable( element, isTabIndexNotNaN ) {
128 var map, mapName, img,
129 nodeName = element.nodeName.toLowerCase();
130 if ( "area" === nodeName ) {
131 map = element.parentNode;
133 if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) {
136 img = $( "img[usemap=#" + mapName + "]" )[0];
137 return !!img && visible( img );
139 return ( /input|select|textarea|button|object/.test( nodeName ) ?
142 element.href || isTabIndexNotNaN :
144 // the element and all of its ancestors must be visible
148 function visible( element ) {
149 return $.expr.filters.visible( element ) &&
150 !$( element ).parents().andSelf().filter(function() {
151 return $.css( this, "visibility" ) === "hidden";
155 $.extend( $.expr[ ":" ], {
156 data: $.expr.createPseudo ?
157 $.expr.createPseudo(function( dataName ) {
158 return function( elem ) {
159 return !!$.data( elem, dataName );
162 // support: jQuery <1.8
163 function( elem, i, match ) {
164 return !!$.data( elem, match[ 3 ] );
167 focusable: function( element ) {
168 return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) );
171 tabbable: function( element ) {
172 var tabIndex = $.attr( element, "tabindex" ),
173 isTabIndexNaN = isNaN( tabIndex );
174 return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN );
180 var body = document.body,
181 div = body.appendChild( div = document.createElement( "div" ) );
183 // access offsetHeight before setting the style to prevent a layout bug
184 // in IE 9 which causes the element to continue to take up space even
185 // after it is removed from the DOM (#8026)
188 $.extend( div.style, {
195 $.support.minHeight = div.offsetHeight === 100;
196 $.support.selectstart = "onselectstart" in div;
198 // set display to none to avoid a layout bug in IE
199 // http://dev.jquery.com/ticket/4014
200 body.removeChild( div ).style.display = "none";
203 // support: jQuery <1.8
204 if ( !$( "<a>" ).outerWidth( 1 ).jquery ) {
205 $.each( [ "Width", "Height" ], function( i, name ) {
206 var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ],
207 type = name.toLowerCase(),
209 innerWidth: $.fn.innerWidth,
210 innerHeight: $.fn.innerHeight,
211 outerWidth: $.fn.outerWidth,
212 outerHeight: $.fn.outerHeight
215 function reduce( elem, size, border, margin ) {
216 $.each( side, function() {
217 size -= parseFloat( $.css( elem, "padding" + this ) ) || 0;
219 size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0;
222 size -= parseFloat( $.css( elem, "margin" + this ) ) || 0;
228 $.fn[ "inner" + name ] = function( size ) {
229 if ( size === undefined ) {
230 return orig[ "inner" + name ].call( this );
233 return this.each(function() {
234 $( this ).css( type, reduce( this, size ) + "px" );
238 $.fn[ "outer" + name] = function( size, margin ) {
239 if ( typeof size !== "number" ) {
240 return orig[ "outer" + name ].call( this, size );
243 return this.each(function() {
244 $( this).css( type, reduce( this, size, true, margin ) + "px" );
250 // support: jQuery 1.6.1, 1.6.2 (http://bugs.jquery.com/ticket/9413)
251 if ( $( "<a>" ).data( "a-b", "a" ).removeData( "a-b" ).data( "a-b" ) ) {
252 $.fn.removeData = (function( removeData ) {
253 return function( key ) {
254 if ( arguments.length ) {
255 return removeData.call( this, $.camelCase( key ) );
257 return removeData.call( this );
260 })( $.fn.removeData );
270 var uaMatch = /msie ([\w.]+)/.exec( navigator.userAgent.toLowerCase() ) || [];
271 $.ui.ie = uaMatch.length ? true : false;
272 $.ui.ie6 = parseFloat( uaMatch[ 1 ], 10 ) === 6;
276 disableSelection: function() {
277 return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) +
278 ".ui-disableSelection", function( event ) {
279 event.preventDefault();
283 enableSelection: function() {
284 return this.unbind( ".ui-disableSelection" );
289 // $.ui.plugin is deprecated. Use the proxy pattern instead.
291 add: function( module, option, set ) {
293 proto = $.ui[ module ].prototype;
295 proto.plugins[ i ] = proto.plugins[ i ] || [];
296 proto.plugins[ i ].push( [ option, set[ i ] ] );
299 call: function( instance, name, args ) {
301 set = instance.plugins[ name ];
302 if ( !set || !instance.element[ 0 ].parentNode || instance.element[ 0 ].parentNode.nodeType === 11 ) {
306 for ( i = 0; i < set.length; i++ ) {
307 if ( instance.options[ set[ i ][ 0 ] ] ) {
308 set[ i ][ 1 ].apply( instance.element, args );
314 contains: $.contains,
316 // only used by resizable
317 hasScroll: function( el, a ) {
319 //If overflow is hidden, the element might have extra content, but the user wants to hide it
320 if ( $( el ).css( "overflow" ) === "hidden") {
324 var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop",
327 if ( el[ scroll ] > 0 ) {
331 // TODO: determine which cases actually cause this to happen
332 // if the element doesn't have the scroll set, see if it's possible to
335 has = ( el[ scroll ] > 0 );
340 // these are odd functions, fix the API or move into individual plugins
341 isOverAxis: function( x, reference, size ) {
342 //Determines when x coordinate is over "b" element axis
343 return ( x > reference ) && ( x < ( reference + size ) );
345 isOver: function( y, x, top, left, height, width ) {
346 //Determines when x, y coordinates is over "b" element
347 return $.ui.isOverAxis( y, top, height ) && $.ui.isOverAxis( x, left, width );
352 (function( $, undefined ) {
355 slice = Array.prototype.slice,
356 _cleanData = $.cleanData;
357 $.cleanData = function( elems ) {
358 for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
360 $( elem ).triggerHandler( "remove" );
361 // http://bugs.jquery.com/ticket/8235
367 $.widget = function( name, base, prototype ) {
368 var fullName, existingConstructor, constructor, basePrototype,
369 namespace = name.split( "." )[ 0 ];
371 name = name.split( "." )[ 1 ];
372 fullName = namespace + "-" + name;
379 // create selector for plugin
380 $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
381 return !!$.data( elem, fullName );
384 $[ namespace ] = $[ namespace ] || {};
385 existingConstructor = $[ namespace ][ name ];
386 constructor = $[ namespace ][ name ] = function( options, element ) {
387 // allow instantiation without "new" keyword
388 if ( !this._createWidget ) {
389 return new constructor( options, element );
392 // allow instantiation without initializing for simple inheritance
393 // must use "new" keyword (the code above always passes args)
394 if ( arguments.length ) {
395 this._createWidget( options, element );
398 // extend with the existing constructor to carry over any static properties
399 $.extend( constructor, existingConstructor, {
400 version: prototype.version,
401 // copy the object used to create the prototype in case we need to
402 // redefine the widget later
403 _proto: $.extend( {}, prototype ),
404 // track widgets that inherit from this widget in case this widget is
405 // redefined after a widget inherits from it
406 _childConstructors: []
409 basePrototype = new base();
410 // we need to make the options hash a property directly on the new instance
411 // otherwise we'll modify the options hash on the prototype that we're
413 basePrototype.options = $.widget.extend( {}, basePrototype.options );
414 $.each( prototype, function( prop, value ) {
415 if ( $.isFunction( value ) ) {
416 prototype[ prop ] = (function() {
417 var _super = function() {
418 return base.prototype[ prop ].apply( this, arguments );
420 _superApply = function( args ) {
421 return base.prototype[ prop ].apply( this, args );
424 var __super = this._super,
425 __superApply = this._superApply,
428 this._super = _super;
429 this._superApply = _superApply;
431 returnValue = value.apply( this, arguments );
433 this._super = __super;
434 this._superApply = __superApply;
441 constructor.prototype = $.widget.extend( basePrototype, {
442 // TODO: remove support for widgetEventPrefix
443 // always use the name + a colon as the prefix, e.g., draggable:start
444 // don't prefix for widgets that aren't DOM-based
445 widgetEventPrefix: existingConstructor ? basePrototype.widgetEventPrefix : name
447 constructor: constructor,
448 namespace: namespace,
450 // TODO remove widgetBaseClass, see #8155
451 widgetBaseClass: fullName,
452 widgetFullName: fullName
455 // If this widget is being redefined then we need to find all widgets that
456 // are inheriting from it and redefine all of them so that they inherit from
457 // the new version of this widget. We're essentially trying to replace one
458 // level in the prototype chain.
459 if ( existingConstructor ) {
460 $.each( existingConstructor._childConstructors, function( i, child ) {
461 var childPrototype = child.prototype;
463 // redefine the child widget using the same prototype that was
464 // originally used, but inherit from the new version of the base
465 $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
467 // remove the list of existing child constructors from the old constructor
468 // so the old child constructors can be garbage collected
469 delete existingConstructor._childConstructors;
471 base._childConstructors.push( constructor );
474 $.widget.bridge( name, constructor );
477 $.widget.extend = function( target ) {
478 var input = slice.call( arguments, 1 ),
480 inputLength = input.length,
483 for ( ; inputIndex < inputLength; inputIndex++ ) {
484 for ( key in input[ inputIndex ] ) {
485 value = input[ inputIndex ][ key ];
486 if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
488 if ( $.isPlainObject( value ) ) {
489 target[ key ] = $.isPlainObject( target[ key ] ) ?
490 $.widget.extend( {}, target[ key ], value ) :
491 // Don't extend strings, arrays, etc. with objects
492 $.widget.extend( {}, value );
493 // Copy everything else by reference
495 target[ key ] = value;
503 $.widget.bridge = function( name, object ) {
504 var fullName = object.prototype.widgetFullName || name;
505 $.fn[ name ] = function( options ) {
506 var isMethodCall = typeof options === "string",
507 args = slice.call( arguments, 1 ),
510 // allow multiple hashes to be passed on init
511 options = !isMethodCall && args.length ?
512 $.widget.extend.apply( null, [ options ].concat(args) ) :
515 if ( isMethodCall ) {
516 this.each(function() {
518 instance = $.data( this, fullName );
520 return $.error( "cannot call methods on " + name + " prior to initialization; " +
521 "attempted to call method '" + options + "'" );
523 if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
524 return $.error( "no such method '" + options + "' for " + name + " widget instance" );
526 methodValue = instance[ options ].apply( instance, args );
527 if ( methodValue !== instance && methodValue !== undefined ) {
528 returnValue = methodValue && methodValue.jquery ?
529 returnValue.pushStack( methodValue.get() ) :
535 this.each(function() {
536 var instance = $.data( this, fullName );
538 instance.option( options || {} )._init();
540 $.data( this, fullName, new object( options, this ) );
549 $.Widget = function( /* options, element */ ) {};
550 $.Widget._childConstructors = [];
552 $.Widget.prototype = {
553 widgetName: "widget",
554 widgetEventPrefix: "",
555 defaultElement: "<div>",
562 _createWidget: function( options, element ) {
563 element = $( element || this.defaultElement || this )[ 0 ];
564 this.element = $( element );
566 this.eventNamespace = "." + this.widgetName + this.uuid;
567 this.options = $.widget.extend( {},
569 this._getCreateOptions(),
573 this.hoverable = $();
574 this.focusable = $();
576 if ( element !== this ) {
578 // TODO remove dual storage
579 $.data( element, this.widgetName, this );
580 $.data( element, this.widgetFullName, this );
581 this._on( true, this.element, {
582 remove: function( event ) {
583 if ( event.target === element ) {
588 this.document = $( element.style ?
589 // element within the document
590 element.ownerDocument :
591 // element is window or document
592 element.document || element );
593 this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
597 this._trigger( "create", null, this._getCreateEventData() );
600 _getCreateOptions: $.noop,
601 _getCreateEventData: $.noop,
605 destroy: function() {
607 // we can probably remove the unbind calls in 2.0
608 // all event bindings should go through this._on()
610 .unbind( this.eventNamespace )
612 // TODO remove dual storage
613 .removeData( this.widgetName )
614 .removeData( this.widgetFullName )
615 // support: jquery <1.6.3
616 // http://bugs.jquery.com/ticket/9413
617 .removeData( $.camelCase( this.widgetFullName ) );
619 .unbind( this.eventNamespace )
620 .removeAttr( "aria-disabled" )
622 this.widgetFullName + "-disabled " +
623 "ui-state-disabled" );
625 // clean up events and states
626 this.bindings.unbind( this.eventNamespace );
627 this.hoverable.removeClass( "ui-state-hover" );
628 this.focusable.removeClass( "ui-state-focus" );
636 option: function( key, value ) {
642 if ( arguments.length === 0 ) {
643 // don't return a reference to the internal hash
644 return $.widget.extend( {}, this.options );
647 if ( typeof key === "string" ) {
648 // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
650 parts = key.split( "." );
652 if ( parts.length ) {
653 curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
654 for ( i = 0; i < parts.length - 1; i++ ) {
655 curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
656 curOption = curOption[ parts[ i ] ];
659 if ( value === undefined ) {
660 return curOption[ key ] === undefined ? null : curOption[ key ];
662 curOption[ key ] = value;
664 if ( value === undefined ) {
665 return this.options[ key ] === undefined ? null : this.options[ key ];
667 options[ key ] = value;
671 this._setOptions( options );
675 _setOptions: function( options ) {
678 for ( key in options ) {
679 this._setOption( key, options[ key ] );
684 _setOption: function( key, value ) {
685 this.options[ key ] = value;
687 if ( key === "disabled" ) {
689 .toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value )
690 .attr( "aria-disabled", value );
691 this.hoverable.removeClass( "ui-state-hover" );
692 this.focusable.removeClass( "ui-state-focus" );
699 return this._setOption( "disabled", false );
701 disable: function() {
702 return this._setOption( "disabled", true );
705 _on: function( suppressDisabledCheck, element, handlers ) {
709 // no suppressDisabledCheck flag, shuffle arguments
710 if ( typeof suppressDisabledCheck !== "boolean" ) {
712 element = suppressDisabledCheck;
713 suppressDisabledCheck = false;
716 // no element argument, shuffle and use this.element
719 element = this.element;
720 delegateElement = this.widget();
722 // accept selectors, DOM elements
723 element = delegateElement = $( element );
724 this.bindings = this.bindings.add( element );
727 $.each( handlers, function( event, handler ) {
728 function handlerProxy() {
729 // allow widgets to customize the disabled handling
730 // - disabled as an array instead of boolean
731 // - disabled class as method for disabling individual parts
732 if ( !suppressDisabledCheck &&
733 ( instance.options.disabled === true ||
734 $( this ).hasClass( "ui-state-disabled" ) ) ) {
737 return ( typeof handler === "string" ? instance[ handler ] : handler )
738 .apply( instance, arguments );
741 // copy the guid so direct unbinding works
742 if ( typeof handler !== "string" ) {
743 handlerProxy.guid = handler.guid =
744 handler.guid || handlerProxy.guid || $.guid++;
747 var match = event.match( /^(\w+)\s*(.*)$/ ),
748 eventName = match[1] + instance.eventNamespace,
751 delegateElement.delegate( selector, eventName, handlerProxy );
753 element.bind( eventName, handlerProxy );
758 _off: function( element, eventName ) {
759 eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace;
760 element.unbind( eventName ).undelegate( eventName );
763 _delay: function( handler, delay ) {
764 function handlerProxy() {
765 return ( typeof handler === "string" ? instance[ handler ] : handler )
766 .apply( instance, arguments );
769 return setTimeout( handlerProxy, delay || 0 );
772 _hoverable: function( element ) {
773 this.hoverable = this.hoverable.add( element );
775 mouseenter: function( event ) {
776 $( event.currentTarget ).addClass( "ui-state-hover" );
778 mouseleave: function( event ) {
779 $( event.currentTarget ).removeClass( "ui-state-hover" );
784 _focusable: function( element ) {
785 this.focusable = this.focusable.add( element );
787 focusin: function( event ) {
788 $( event.currentTarget ).addClass( "ui-state-focus" );
790 focusout: function( event ) {
791 $( event.currentTarget ).removeClass( "ui-state-focus" );
796 _trigger: function( type, event, data ) {
798 callback = this.options[ type ];
801 event = $.Event( event );
802 event.type = ( type === this.widgetEventPrefix ?
804 this.widgetEventPrefix + type ).toLowerCase();
805 // the original event may come from any element
806 // so we need to reset the target on the new event
807 event.target = this.element[ 0 ];
809 // copy original event properties over to the new event
810 orig = event.originalEvent;
812 for ( prop in orig ) {
813 if ( !( prop in event ) ) {
814 event[ prop ] = orig[ prop ];
819 this.element.trigger( event, data );
820 return !( $.isFunction( callback ) &&
821 callback.apply( this.element[0], [ event ].concat( data ) ) === false ||
822 event.isDefaultPrevented() );
826 $.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
827 $.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
828 if ( typeof options === "string" ) {
829 options = { effect: options };
832 effectName = !options ?
834 options === true || typeof options === "number" ?
836 options.effect || defaultEffect;
837 options = options || {};
838 if ( typeof options === "number" ) {
839 options = { duration: options };
841 hasOptions = !$.isEmptyObject( options );
842 options.complete = callback;
843 if ( options.delay ) {
844 element.delay( options.delay );
846 if ( hasOptions && $.effects && ( $.effects.effect[ effectName ] || $.uiBackCompat !== false && $.effects[ effectName ] ) ) {
847 element[ method ]( options );
848 } else if ( effectName !== method && element[ effectName ] ) {
849 element[ effectName ]( options.duration, options.easing, callback );
851 element.queue(function( next ) {
852 $( this )[ method ]();
854 callback.call( element[ 0 ] );
863 if ( $.uiBackCompat !== false ) {
864 $.Widget.prototype._getCreateOptions = function() {
865 return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ];
870 (function( $, undefined ) {
872 var mouseHandled = false;
873 $( document ).mouseup( function( e ) {
874 mouseHandled = false;
877 $.widget("ui.mouse", {
880 cancel: 'input,textarea,button,select,option',
884 _mouseInit: function() {
888 .bind('mousedown.'+this.widgetName, function(event) {
889 return that._mouseDown(event);
891 .bind('click.'+this.widgetName, function(event) {
892 if (true === $.data(event.target, that.widgetName + '.preventClickEvent')) {
893 $.removeData(event.target, that.widgetName + '.preventClickEvent');
894 event.stopImmediatePropagation();
899 this.started = false;
902 // TODO: make sure destroying one instance of mouse doesn't mess with
903 // other instances of mouse
904 _mouseDestroy: function() {
905 this.element.unbind('.'+this.widgetName);
906 if ( this._mouseMoveDelegate ) {
908 .unbind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
909 .unbind('mouseup.'+this.widgetName, this._mouseUpDelegate);
913 _mouseDown: function(event) {
914 // don't let more than one widget handle mouseStart
915 if( mouseHandled ) { return; }
917 // we may have missed mouseup (out of window)
918 (this._mouseStarted && this._mouseUp(event));
920 this._mouseDownEvent = event;
923 btnIsLeft = (event.which === 1),
924 // event.target.nodeName works around a bug in IE 8 with
925 // disabled inputs (#7620)
926 elIsCancel = (typeof this.options.cancel === "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false);
927 if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) {
931 this.mouseDelayMet = !this.options.delay;
932 if (!this.mouseDelayMet) {
933 this._mouseDelayTimer = setTimeout(function() {
934 that.mouseDelayMet = true;
935 }, this.options.delay);
938 if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
939 this._mouseStarted = (this._mouseStart(event) !== false);
940 if (!this._mouseStarted) {
941 event.preventDefault();
946 // Click event may never have fired (Gecko & Opera)
947 if (true === $.data(event.target, this.widgetName + '.preventClickEvent')) {
948 $.removeData(event.target, this.widgetName + '.preventClickEvent');
951 // these delegates are required to keep context
952 this._mouseMoveDelegate = function(event) {
953 return that._mouseMove(event);
955 this._mouseUpDelegate = function(event) {
956 return that._mouseUp(event);
959 .bind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
960 .bind('mouseup.'+this.widgetName, this._mouseUpDelegate);
962 event.preventDefault();
968 _mouseMove: function(event) {
969 // IE mouseup check - mouseup happened when mouse was out of window
970 if ($.ui.ie && !(document.documentMode >= 9) && !event.button) {
971 return this._mouseUp(event);
974 if (this._mouseStarted) {
975 this._mouseDrag(event);
976 return event.preventDefault();
979 if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
981 (this._mouseStart(this._mouseDownEvent, event) !== false);
982 (this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event));
985 return !this._mouseStarted;
988 _mouseUp: function(event) {
990 .unbind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
991 .unbind('mouseup.'+this.widgetName, this._mouseUpDelegate);
993 if (this._mouseStarted) {
994 this._mouseStarted = false;
996 if (event.target === this._mouseDownEvent.target) {
997 $.data(event.target, this.widgetName + '.preventClickEvent', true);
1000 this._mouseStop(event);
1006 _mouseDistanceMet: function(event) {
1008 Math.abs(this._mouseDownEvent.pageX - event.pageX),
1009 Math.abs(this._mouseDownEvent.pageY - event.pageY)
1010 ) >= this.options.distance
1014 _mouseDelayMet: function(event) {
1015 return this.mouseDelayMet;
1018 // These are placeholder methods, to be overriden by extending plugin
1019 _mouseStart: function(event) {},
1020 _mouseDrag: function(event) {},
1021 _mouseStop: function(event) {},
1022 _mouseCapture: function(event) { return true; }
1026 (function( $, undefined ) {
1028 $.widget("ui.draggable", $.ui.mouse, {
1030 widgetEventPrefix: "drag",
1035 connectToSortable: false,
1044 refreshPositions: false,
1046 revertDuration: 500,
1049 scrollSensitivity: 20,
1057 _create: function() {
1059 if (this.options.helper == 'original' && !(/^(?:r|a|f)/).test(this.element.css("position")))
1060 this.element[0].style.position = 'relative';
1062 (this.options.addClasses && this.element.addClass("ui-draggable"));
1063 (this.options.disabled && this.element.addClass("ui-draggable-disabled"));
1069 _destroy: function() {
1070 this.element.removeClass( "ui-draggable ui-draggable-dragging ui-draggable-disabled" );
1071 this._mouseDestroy();
1074 _mouseCapture: function(event) {
1076 var o = this.options;
1078 // among others, prevent a drag on a resizable-handle
1079 if (this.helper || o.disabled || $(event.target).is('.ui-resizable-handle'))
1082 //Quit if we're not on a valid handle
1083 this.handle = this._getHandle(event);
1087 $(o.iframeFix === true ? "iframe" : o.iframeFix).each(function() {
1088 $('<div class="ui-draggable-iframeFix" style="background: #fff;"></div>')
1090 width: this.offsetWidth+"px", height: this.offsetHeight+"px",
1091 position: "absolute", opacity: "0.001", zIndex: 1000
1093 .css($(this).offset())
1101 _mouseStart: function(event) {
1103 var o = this.options;
1105 //Create and append the visible helper
1106 this.helper = this._createHelper(event);
1108 this.helper.addClass("ui-draggable-dragging");
1110 //Cache the helper size
1111 this._cacheHelperProportions();
1113 //If ddmanager is used for droppables, set the global draggable
1115 $.ui.ddmanager.current = this;
1118 * - Position generation -
1119 * This block generates everything position related - it's the core of draggables.
1122 //Cache the margins of the original element
1123 this._cacheMargins();
1125 //Store the helper's css position
1126 this.cssPosition = this.helper.css("position");
1127 this.scrollParent = this.helper.scrollParent();
1129 //The element's absolute position on the page minus margins
1130 this.offset = this.positionAbs = this.element.offset();
1132 top: this.offset.top - this.margins.top,
1133 left: this.offset.left - this.margins.left
1136 $.extend(this.offset, {
1137 click: { //Where the click happened, relative to the element
1138 left: event.pageX - this.offset.left,
1139 top: event.pageY - this.offset.top
1141 parent: this._getParentOffset(),
1142 relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
1145 //Generate the original position
1146 this.originalPosition = this.position = this._generatePosition(event);
1147 this.originalPageX = event.pageX;
1148 this.originalPageY = event.pageY;
1150 //Adjust the mouse offset relative to the helper if 'cursorAt' is supplied
1151 (o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
1153 //Set a containment if given in the options
1155 this._setContainment();
1157 //Trigger event + callbacks
1158 if(this._trigger("start", event) === false) {
1163 //Recache the helper size
1164 this._cacheHelperProportions();
1166 //Prepare the droppable offsets
1167 if ($.ui.ddmanager && !o.dropBehaviour)
1168 $.ui.ddmanager.prepareOffsets(this, event);
1171 this._mouseDrag(event, true); //Execute the drag once - this causes the helper not to be visible before getting its correct position
1173 //If the ddmanager is used for droppables, inform the manager that dragging has started (see #5003)
1174 if ( $.ui.ddmanager ) $.ui.ddmanager.dragStart(this, event);
1179 _mouseDrag: function(event, noPropagation) {
1181 //Compute the helpers position
1182 this.position = this._generatePosition(event);
1183 this.positionAbs = this._convertPositionTo("absolute");
1185 //Call plugins and callbacks and use the resulting position if something is returned
1186 if (!noPropagation) {
1187 var ui = this._uiHash();
1188 if(this._trigger('drag', event, ui) === false) {
1192 this.position = ui.position;
1195 if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px';
1196 if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px';
1197 if($.ui.ddmanager) $.ui.ddmanager.drag(this, event);
1202 _mouseStop: function(event) {
1204 //If we are using droppables, inform the manager about the drop
1205 var dropped = false;
1206 if ($.ui.ddmanager && !this.options.dropBehaviour)
1207 dropped = $.ui.ddmanager.drop(this, event);
1209 //if a drop comes from outside (a sortable)
1211 dropped = this.dropped;
1212 this.dropped = false;
1215 //if the original element is no longer in the DOM don't bother to continue (see #8269)
1216 var element = this.element[0], elementInDom = false;
1217 while ( element && (element = element.parentNode) ) {
1218 if (element == document ) {
1219 elementInDom = true;
1222 if ( !elementInDom && this.options.helper === "original" )
1225 if((this.options.revert == "invalid" && !dropped) || (this.options.revert == "valid" && dropped) || this.options.revert === true || ($.isFunction(this.options.revert) && this.options.revert.call(this.element, dropped))) {
1227 $(this.helper).animate(this.originalPosition, parseInt(this.options.revertDuration, 10), function() {
1228 if(that._trigger("stop", event) !== false) {
1233 if(this._trigger("stop", event) !== false) {
1241 _mouseUp: function(event) {
1242 //Remove frame helpers
1243 $("div.ui-draggable-iframeFix").each(function() {
1244 this.parentNode.removeChild(this);
1247 //If the ddmanager is used for droppables, inform the manager that dragging has stopped (see #5003)
1248 if( $.ui.ddmanager ) $.ui.ddmanager.dragStop(this, event);
1250 return $.ui.mouse.prototype._mouseUp.call(this, event);
1253 cancel: function() {
1255 if(this.helper.is(".ui-draggable-dragging")) {
1265 _getHandle: function(event) {
1267 var handle = !this.options.handle || !$(this.options.handle, this.element).length ? true : false;
1268 $(this.options.handle, this.element)
1272 if(this == event.target) handle = true;
1279 _createHelper: function(event) {
1281 var o = this.options;
1282 var helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event])) : (o.helper == 'clone' ? this.element.clone().removeAttr('id') : this.element);
1284 if(!helper.parents('body').length)
1285 helper.appendTo((o.appendTo == 'parent' ? this.element[0].parentNode : o.appendTo));
1287 if(helper[0] != this.element[0] && !(/(fixed|absolute)/).test(helper.css("position")))
1288 helper.css("position", "absolute");
1294 _adjustOffsetFromHelper: function(obj) {
1295 if (typeof obj == 'string') {
1296 obj = obj.split(' ');
1298 if ($.isArray(obj)) {
1299 obj = {left: +obj[0], top: +obj[1] || 0};
1301 if ('left' in obj) {
1302 this.offset.click.left = obj.left + this.margins.left;
1304 if ('right' in obj) {
1305 this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
1308 this.offset.click.top = obj.top + this.margins.top;
1310 if ('bottom' in obj) {
1311 this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
1315 _getParentOffset: function() {
1317 //Get the offsetParent and cache its position
1318 this.offsetParent = this.helper.offsetParent();
1319 var po = this.offsetParent.offset();
1321 // This is a special case where we need to modify a offset calculated on start, since the following happened:
1322 // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
1323 // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
1324 // the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
1325 if(this.cssPosition == 'absolute' && this.scrollParent[0] != document && $.contains(this.scrollParent[0], this.offsetParent[0])) {
1326 po.left += this.scrollParent.scrollLeft();
1327 po.top += this.scrollParent.scrollTop();
1330 if((this.offsetParent[0] == document.body) //This needs to be actually done for all browsers, since pageX/pageY includes this information
1331 || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() == 'html' && $.ui.ie)) //Ugly IE fix
1332 po = { top: 0, left: 0 };
1335 top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
1336 left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
1341 _getRelativeOffset: function() {
1343 if(this.cssPosition == "relative") {
1344 var p = this.element.position();
1346 top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
1347 left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
1350 return { top: 0, left: 0 };
1355 _cacheMargins: function() {
1357 left: (parseInt(this.element.css("marginLeft"),10) || 0),
1358 top: (parseInt(this.element.css("marginTop"),10) || 0),
1359 right: (parseInt(this.element.css("marginRight"),10) || 0),
1360 bottom: (parseInt(this.element.css("marginBottom"),10) || 0)
1364 _cacheHelperProportions: function() {
1365 this.helperProportions = {
1366 width: this.helper.outerWidth(),
1367 height: this.helper.outerHeight()
1371 _setContainment: function() {
1373 var o = this.options;
1374 if(o.containment == 'parent') o.containment = this.helper[0].parentNode;
1375 if(o.containment == 'document' || o.containment == 'window') this.containment = [
1376 o.containment == 'document' ? 0 : $(window).scrollLeft() - this.offset.relative.left - this.offset.parent.left,
1377 o.containment == 'document' ? 0 : $(window).scrollTop() - this.offset.relative.top - this.offset.parent.top,
1378 (o.containment == 'document' ? 0 : $(window).scrollLeft()) + $(o.containment == 'document' ? document : window).width() - this.helperProportions.width - this.margins.left,
1379 (o.containment == 'document' ? 0 : $(window).scrollTop()) + ($(o.containment == 'document' ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top
1382 if(!(/^(document|window|parent)$/).test(o.containment) && o.containment.constructor != Array) {
1383 var c = $(o.containment);
1384 var ce = c[0]; if(!ce) return;
1385 var co = c.offset();
1386 var over = ($(ce).css("overflow") != 'hidden');
1388 this.containment = [
1389 (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0),
1390 (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0),
1391 (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 - this.margins.right,
1392 (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 - this.margins.bottom
1394 this.relative_container = c;
1396 } else if(o.containment.constructor == Array) {
1397 this.containment = o.containment;
1402 _convertPositionTo: function(d, pos) {
1404 if(!pos) pos = this.position;
1405 var mod = d == "absolute" ? 1 : -1;
1406 var o = this.options, 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);
1410 pos.top // The absolute mouse position
1411 + this.offset.relative.top * mod // Only for relative positioned nodes: Relative offset from element to offset parent
1412 + this.offset.parent.top * mod // The offsetParent's offset without borders (offset + border)
1413 - ( ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod)
1416 pos.left // The absolute mouse position
1417 + this.offset.relative.left * mod // Only for relative positioned nodes: Relative offset from element to offset parent
1418 + this.offset.parent.left * mod // The offsetParent's offset without borders (offset + border)
1419 - ( ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod)
1425 _generatePosition: function(event) {
1427 var o = this.options, 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);
1428 var pageX = event.pageX;
1429 var pageY = event.pageY;
1432 * - Position constraining -
1433 * Constrain the position to a mix of grid, containment.
1436 if(this.originalPosition) { //If we are not dragging yet, we won't check for options
1438 if(this.containment) {
1439 if (this.relative_container){
1440 var co = this.relative_container.offset();
1441 containment = [ this.containment[0] + co.left,
1442 this.containment[1] + co.top,
1443 this.containment[2] + co.left,
1444 this.containment[3] + co.top ];
1447 containment = this.containment;
1450 if(event.pageX - this.offset.click.left < containment[0]) pageX = containment[0] + this.offset.click.left;
1451 if(event.pageY - this.offset.click.top < containment[1]) pageY = containment[1] + this.offset.click.top;
1452 if(event.pageX - this.offset.click.left > containment[2]) pageX = containment[2] + this.offset.click.left;
1453 if(event.pageY - this.offset.click.top > containment[3]) pageY = containment[3] + this.offset.click.top;
1457 //Check for grid elements set to 0 to prevent divide by 0 error causing invalid argument errors in IE (see ticket #6950)
1458 var top = o.grid[1] ? this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1] : this.originalPageY;
1459 pageY = containment ? (!(top - this.offset.click.top < containment[1] || top - this.offset.click.top > containment[3]) ? top : (!(top - this.offset.click.top < containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;
1461 var left = o.grid[0] ? this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0] : this.originalPageX;
1462 pageX = containment ? (!(left - this.offset.click.left < containment[0] || left - this.offset.click.left > containment[2]) ? left : (!(left - this.offset.click.left < containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
1469 pageY // The absolute mouse position
1470 - this.offset.click.top // Click offset (relative to the element)
1471 - this.offset.relative.top // Only for relative positioned nodes: Relative offset from element to offset parent
1472 - this.offset.parent.top // The offsetParent's offset without borders (offset + border)
1473 + ( ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ))
1476 pageX // The absolute mouse position
1477 - this.offset.click.left // Click offset (relative to the element)
1478 - this.offset.relative.left // Only for relative positioned nodes: Relative offset from element to offset parent
1479 - this.offset.parent.left // The offsetParent's offset without borders (offset + border)
1480 + ( ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ))
1486 _clear: function() {
1487 this.helper.removeClass("ui-draggable-dragging");
1488 if(this.helper[0] != this.element[0] && !this.cancelHelperRemoval) this.helper.remove();
1489 //if($.ui.ddmanager) $.ui.ddmanager.current = null;
1491 this.cancelHelperRemoval = false;
1494 // From now on bulk stuff - mainly helpers
1496 _trigger: function(type, event, ui) {
1497 ui = ui || this._uiHash();
1498 $.ui.plugin.call(this, type, [event, ui]);
1499 if(type == "drag") this.positionAbs = this._convertPositionTo("absolute"); //The absolute position has to be recalculated after plugins
1500 return $.Widget.prototype._trigger.call(this, type, event, ui);
1505 _uiHash: function(event) {
1507 helper: this.helper,
1508 position: this.position,
1509 originalPosition: this.originalPosition,
1510 offset: this.positionAbs
1516 $.ui.plugin.add("draggable", "connectToSortable", {
1517 start: function(event, ui) {
1519 var inst = $(this).data("draggable"), o = inst.options,
1520 uiSortable = $.extend({}, ui, { item: inst.element });
1521 inst.sortables = [];
1522 $(o.connectToSortable).each(function() {
1523 var sortable = $.data(this, 'sortable');
1524 if (sortable && !sortable.options.disabled) {
1525 inst.sortables.push({
1527 shouldRevert: sortable.options.revert
1529 sortable.refreshPositions(); // Call the sortable's refreshPositions at drag start to refresh the containerCache since the sortable container cache is used in drag and needs to be up to date (this will ensure it's initialised as well as being kept in step with any changes that might have happened on the page).
1530 sortable._trigger("activate", event, uiSortable);
1535 stop: function(event, ui) {
1537 //If we are still over the sortable, we fake the stop event of the sortable, but also remove helper
1538 var inst = $(this).data("draggable"),
1539 uiSortable = $.extend({}, ui, { item: inst.element });
1541 $.each(inst.sortables, function() {
1542 if(this.instance.isOver) {
1544 this.instance.isOver = 0;
1546 inst.cancelHelperRemoval = true; //Don't remove the helper in the draggable instance
1547 this.instance.cancelHelperRemoval = false; //Remove it in the sortable instance (so sortable plugins like revert still work)
1549 //The sortable revert is supported, and we have to set a temporary dropped variable on the draggable to support revert: 'valid/invalid'
1550 if(this.shouldRevert) this.instance.options.revert = true;
1552 //Trigger the stop of the sortable
1553 this.instance._mouseStop(event);
1555 this.instance.options.helper = this.instance.options._helper;
1557 //If the helper has been the original item, restore properties in the sortable
1558 if(inst.options.helper == 'original')
1559 this.instance.currentItem.css({ top: 'auto', left: 'auto' });
1562 this.instance.cancelHelperRemoval = false; //Remove the helper in the sortable instance
1563 this.instance._trigger("deactivate", event, uiSortable);
1569 drag: function(event, ui) {
1571 var inst = $(this).data("draggable"), that = this;
1573 var checkPos = function(o) {
1574 var dyClick = this.offset.click.top, dxClick = this.offset.click.left;
1575 var helperTop = this.positionAbs.top, helperLeft = this.positionAbs.left;
1576 var itemHeight = o.height, itemWidth = o.width;
1577 var itemTop = o.top, itemLeft = o.left;
1579 return $.ui.isOver(helperTop + dyClick, helperLeft + dxClick, itemTop, itemLeft, itemHeight, itemWidth);
1582 $.each(inst.sortables, function(i) {
1584 var innermostIntersecting = false;
1585 var thisSortable = this;
1586 //Copy over some variables to allow calling the sortable's native _intersectsWith
1587 this.instance.positionAbs = inst.positionAbs;
1588 this.instance.helperProportions = inst.helperProportions;
1589 this.instance.offset.click = inst.offset.click;
1591 if(this.instance._intersectsWith(this.instance.containerCache)) {
1592 innermostIntersecting = true;
1593 $.each(inst.sortables, function () {
1594 this.instance.positionAbs = inst.positionAbs;
1595 this.instance.helperProportions = inst.helperProportions;
1596 this.instance.offset.click = inst.offset.click;
1597 if (this != thisSortable
1598 && this.instance._intersectsWith(this.instance.containerCache)
1599 && $.ui.contains(thisSortable.instance.element[0], this.instance.element[0]))
1600 innermostIntersecting = false;
1601 return innermostIntersecting;
1606 if(innermostIntersecting) {
1607 //If it intersects, we use a little isOver variable and set it once, so our move-in stuff gets fired only once
1608 if(!this.instance.isOver) {
1610 this.instance.isOver = 1;
1611 //Now we fake the start of dragging for the sortable instance,
1612 //by cloning the list group item, appending it to the sortable and using it as inst.currentItem
1613 //We can then fire the start event of the sortable with our passed browser event, and our own helper (so it doesn't create a new one)
1614 this.instance.currentItem = $(that).clone().removeAttr('id').appendTo(this.instance.element).data("sortable-item", true);
1615 this.instance.options._helper = this.instance.options.helper; //Store helper option to later restore it
1616 this.instance.options.helper = function() { return ui.helper[0]; };
1618 event.target = this.instance.currentItem[0];
1619 this.instance._mouseCapture(event, true);
1620 this.instance._mouseStart(event, true, true);
1622 //Because the browser event is way off the new appended portlet, we modify a couple of variables to reflect the changes
1623 this.instance.offset.click.top = inst.offset.click.top;
1624 this.instance.offset.click.left = inst.offset.click.left;
1625 this.instance.offset.parent.left -= inst.offset.parent.left - this.instance.offset.parent.left;
1626 this.instance.offset.parent.top -= inst.offset.parent.top - this.instance.offset.parent.top;
1628 inst._trigger("toSortable", event);
1629 inst.dropped = this.instance.element; //draggable revert needs that
1630 //hack so receive/update callbacks work (mostly)
1631 inst.currentItem = inst.element;
1632 this.instance.fromOutside = inst;
1636 //Provided we did all the previous steps, we can fire the drag event of the sortable on every draggable drag, when it intersects with the sortable
1637 if(this.instance.currentItem) this.instance._mouseDrag(event);
1641 //If it doesn't intersect with the sortable, and it intersected before,
1642 //we fake the drag stop of the sortable, but make sure it doesn't remove the helper by using cancelHelperRemoval
1643 if(this.instance.isOver) {
1645 this.instance.isOver = 0;
1646 this.instance.cancelHelperRemoval = true;
1648 //Prevent reverting on this forced stop
1649 this.instance.options.revert = false;
1651 // The out event needs to be triggered independently
1652 this.instance._trigger('out', event, this.instance._uiHash(this.instance));
1654 this.instance._mouseStop(event, true);
1655 this.instance.options.helper = this.instance.options._helper;
1657 //Now we remove our currentItem, the list group clone again, and the placeholder, and animate the helper back to it's original size
1658 this.instance.currentItem.remove();
1659 if(this.instance.placeholder) this.instance.placeholder.remove();
1661 inst._trigger("fromSortable", event);
1662 inst.dropped = false; //draggable revert needs that
1672 $.ui.plugin.add("draggable", "cursor", {
1673 start: function(event, ui) {
1674 var t = $('body'), o = $(this).data('draggable').options;
1675 if (t.css("cursor")) o._cursor = t.css("cursor");
1676 t.css("cursor", o.cursor);
1678 stop: function(event, ui) {
1679 var o = $(this).data('draggable').options;
1680 if (o._cursor) $('body').css("cursor", o._cursor);
1684 $.ui.plugin.add("draggable", "opacity", {
1685 start: function(event, ui) {
1686 var t = $(ui.helper), o = $(this).data('draggable').options;
1687 if(t.css("opacity")) o._opacity = t.css("opacity");
1688 t.css('opacity', o.opacity);
1690 stop: function(event, ui) {
1691 var o = $(this).data('draggable').options;
1692 if(o._opacity) $(ui.helper).css('opacity', o._opacity);
1696 $.ui.plugin.add("draggable", "scroll", {
1697 start: function(event, ui) {
1698 var i = $(this).data("draggable");
1699 if(i.scrollParent[0] != document && i.scrollParent[0].tagName != 'HTML') i.overflowOffset = i.scrollParent.offset();
1701 drag: function(event, ui) {
1703 var i = $(this).data("draggable"), o = i.options, scrolled = false;
1705 if(i.scrollParent[0] != document && i.scrollParent[0].tagName != 'HTML') {
1707 if(!o.axis || o.axis != 'x') {
1708 if((i.overflowOffset.top + i.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity)
1709 i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop + o.scrollSpeed;
1710 else if(event.pageY - i.overflowOffset.top < o.scrollSensitivity)
1711 i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop - o.scrollSpeed;
1714 if(!o.axis || o.axis != 'y') {
1715 if((i.overflowOffset.left + i.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity)
1716 i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft + o.scrollSpeed;
1717 else if(event.pageX - i.overflowOffset.left < o.scrollSensitivity)
1718 i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft - o.scrollSpeed;
1723 if(!o.axis || o.axis != 'x') {
1724 if(event.pageY - $(document).scrollTop() < o.scrollSensitivity)
1725 scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
1726 else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity)
1727 scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
1730 if(!o.axis || o.axis != 'y') {
1731 if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity)
1732 scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
1733 else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity)
1734 scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
1739 if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour)
1740 $.ui.ddmanager.prepareOffsets(i, event);
1745 $.ui.plugin.add("draggable", "snap", {
1746 start: function(event, ui) {
1748 var i = $(this).data("draggable"), o = i.options;
1749 i.snapElements = [];
1751 $(o.snap.constructor != String ? ( o.snap.items || ':data(draggable)' ) : o.snap).each(function() {
1752 var $t = $(this); var $o = $t.offset();
1753 if(this != i.element[0]) i.snapElements.push({
1755 width: $t.outerWidth(), height: $t.outerHeight(),
1756 top: $o.top, left: $o.left
1761 drag: function(event, ui) {
1763 var inst = $(this).data("draggable"), o = inst.options;
1764 var d = o.snapTolerance;
1766 var x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width,
1767 y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height;
1769 for (var i = inst.snapElements.length - 1; i >= 0; i--){
1771 var l = inst.snapElements[i].left, r = l + inst.snapElements[i].width,
1772 t = inst.snapElements[i].top, b = t + inst.snapElements[i].height;
1774 //Yes, I know, this is insane ;)
1775 if(!((l-d < x1 && x1 < r+d && t-d < y1 && y1 < b+d) || (l-d < x1 && x1 < r+d && t-d < y2 && y2 < b+d) || (l-d < x2 && x2 < r+d && t-d < y1 && y1 < b+d) || (l-d < x2 && x2 < r+d && t-d < y2 && y2 < b+d))) {
1776 if(inst.snapElements[i].snapping) (inst.options.snap.release && inst.options.snap.release.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
1777 inst.snapElements[i].snapping = false;
1781 if(o.snapMode != 'inner') {
1782 var ts = Math.abs(t - y2) <= d;
1783 var bs = Math.abs(b - y1) <= d;
1784 var ls = Math.abs(l - x2) <= d;
1785 var rs = Math.abs(r - x1) <= d;
1786 if(ts) ui.position.top = inst._convertPositionTo("relative", { top: t - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
1787 if(bs) ui.position.top = inst._convertPositionTo("relative", { top: b, left: 0 }).top - inst.margins.top;
1788 if(ls) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l - inst.helperProportions.width }).left - inst.margins.left;
1789 if(rs) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r }).left - inst.margins.left;
1792 var first = (ts || bs || ls || rs);
1794 if(o.snapMode != 'outer') {
1795 var ts = Math.abs(t - y1) <= d;
1796 var bs = Math.abs(b - y2) <= d;
1797 var ls = Math.abs(l - x1) <= d;
1798 var rs = Math.abs(r - x2) <= d;
1799 if(ts) ui.position.top = inst._convertPositionTo("relative", { top: t, left: 0 }).top - inst.margins.top;
1800 if(bs) ui.position.top = inst._convertPositionTo("relative", { top: b - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
1801 if(ls) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l }).left - inst.margins.left;
1802 if(rs) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r - inst.helperProportions.width }).left - inst.margins.left;
1805 if(!inst.snapElements[i].snapping && (ts || bs || ls || rs || first))
1806 (inst.options.snap.snap && inst.options.snap.snap.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
1807 inst.snapElements[i].snapping = (ts || bs || ls || rs || first);
1814 $.ui.plugin.add("draggable", "stack", {
1815 start: function(event, ui) {
1817 var o = $(this).data("draggable").options;
1819 var group = $.makeArray($(o.stack)).sort(function(a,b) {
1820 return (parseInt($(a).css("zIndex"),10) || 0) - (parseInt($(b).css("zIndex"),10) || 0);
1822 if (!group.length) { return; }
1824 var min = parseInt(group[0].style.zIndex) || 0;
1825 $(group).each(function(i) {
1826 this.style.zIndex = min + i;
1829 this[0].style.zIndex = min + group.length;
1834 $.ui.plugin.add("draggable", "zIndex", {
1835 start: function(event, ui) {
1836 var t = $(ui.helper), o = $(this).data("draggable").options;
1837 if(t.css("zIndex")) o._zIndex = t.css("zIndex");
1838 t.css('zIndex', o.zIndex);
1840 stop: function(event, ui) {
1841 var o = $(this).data("draggable").options;
1842 if(o._zIndex) $(ui.helper).css('zIndex', o._zIndex);
1847 (function( $, undefined ) {
1849 $.widget("ui.droppable", {
1851 widgetEventPrefix: "drop",
1859 tolerance: 'intersect'
1861 _create: function() {
1863 var o = this.options, accept = o.accept;
1864 this.isover = 0; this.isout = 1;
1866 this.accept = $.isFunction(accept) ? accept : function(d) {
1867 return d.is(accept);
1870 //Store the droppable's proportions
1871 this.proportions = { width: this.element[0].offsetWidth, height: this.element[0].offsetHeight };
1873 // Add the reference and positions to the manager
1874 $.ui.ddmanager.droppables[o.scope] = $.ui.ddmanager.droppables[o.scope] || [];
1875 $.ui.ddmanager.droppables[o.scope].push(this);
1877 (o.addClasses && this.element.addClass("ui-droppable"));
1881 _destroy: function() {
1882 var drop = $.ui.ddmanager.droppables[this.options.scope];
1883 for ( var i = 0; i < drop.length; i++ )
1884 if ( drop[i] == this )
1887 this.element.removeClass("ui-droppable ui-droppable-disabled");
1890 _setOption: function(key, value) {
1892 if(key == 'accept') {
1893 this.accept = $.isFunction(value) ? value : function(d) {
1897 $.Widget.prototype._setOption.apply(this, arguments);
1900 _activate: function(event) {
1901 var draggable = $.ui.ddmanager.current;
1902 if(this.options.activeClass) this.element.addClass(this.options.activeClass);
1903 (draggable && this._trigger('activate', event, this.ui(draggable)));
1906 _deactivate: function(event) {
1907 var draggable = $.ui.ddmanager.current;
1908 if(this.options.activeClass) this.element.removeClass(this.options.activeClass);
1909 (draggable && this._trigger('deactivate', event, this.ui(draggable)));
1912 _over: function(event) {
1914 var draggable = $.ui.ddmanager.current;
1915 if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return; // Bail if draggable and droppable are same element
1917 if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
1918 if(this.options.hoverClass) this.element.addClass(this.options.hoverClass);
1919 this._trigger('over', event, this.ui(draggable));
1924 _out: function(event) {
1926 var draggable = $.ui.ddmanager.current;
1927 if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return; // Bail if draggable and droppable are same element
1929 if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
1930 if(this.options.hoverClass) this.element.removeClass(this.options.hoverClass);
1931 this._trigger('out', event, this.ui(draggable));
1936 _drop: function(event,custom) {
1938 var draggable = custom || $.ui.ddmanager.current;
1939 if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return false; // Bail if draggable and droppable are same element
1941 var childrenIntersection = false;
1942 this.element.find(":data(droppable)").not(".ui-draggable-dragging").each(function() {
1943 var inst = $.data(this, 'droppable');
1946 && !inst.options.disabled
1947 && inst.options.scope == draggable.options.scope
1948 && inst.accept.call(inst.element[0], (draggable.currentItem || draggable.element))
1949 && $.ui.intersect(draggable, $.extend(inst, { offset: inst.element.offset() }), inst.options.tolerance)
1950 ) { childrenIntersection = true; return false; }
1952 if(childrenIntersection) return false;
1954 if(this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
1955 if(this.options.activeClass) this.element.removeClass(this.options.activeClass);
1956 if(this.options.hoverClass) this.element.removeClass(this.options.hoverClass);
1957 this._trigger('drop', event, this.ui(draggable));
1958 return this.element;
1967 draggable: (c.currentItem || c.element),
1969 position: c.position,
1970 offset: c.positionAbs
1976 $.ui.intersect = function(draggable, droppable, toleranceMode) {
1978 if (!droppable.offset) return false;
1980 var x1 = (draggable.positionAbs || draggable.position.absolute).left, x2 = x1 + draggable.helperProportions.width,
1981 y1 = (draggable.positionAbs || draggable.position.absolute).top, y2 = y1 + draggable.helperProportions.height;
1982 var l = droppable.offset.left, r = l + droppable.proportions.width,
1983 t = droppable.offset.top, b = t + droppable.proportions.height;
1985 switch (toleranceMode) {
1987 return (l <= x1 && x2 <= r
1988 && t <= y1 && y2 <= b);
1991 return (l < x1 + (draggable.helperProportions.width / 2) // Right Half
1992 && x2 - (draggable.helperProportions.width / 2) < r // Left Half
1993 && t < y1 + (draggable.helperProportions.height / 2) // Bottom Half
1994 && y2 - (draggable.helperProportions.height / 2) < b ); // Top Half
1997 var draggableLeft = ((draggable.positionAbs || draggable.position.absolute).left + (draggable.clickOffset || draggable.offset.click).left),
1998 draggableTop = ((draggable.positionAbs || draggable.position.absolute).top + (draggable.clickOffset || draggable.offset.click).top),
1999 isOver = $.ui.isOver(draggableTop, draggableLeft, t, l, droppable.proportions.height, droppable.proportions.width);
2004 (y1 >= t && y1 <= b) || // Top edge touching
2005 (y2 >= t && y2 <= b) || // Bottom edge touching
2006 (y1 < t && y2 > b) // Surrounded vertically
2008 (x1 >= l && x1 <= r) || // Left edge touching
2009 (x2 >= l && x2 <= r) || // Right edge touching
2010 (x1 < l && x2 > r) // Surrounded horizontally
2021 This manager tracks offsets of draggables and droppables
2025 droppables: { 'default': [] },
2026 prepareOffsets: function(t, event) {
2028 var m = $.ui.ddmanager.droppables[t.options.scope] || [];
2029 var type = event ? event.type : null; // workaround for #2317
2030 var list = (t.currentItem || t.element).find(":data(droppable)").andSelf();
2032 droppablesLoop: for (var i = 0; i < m.length; i++) {
2034 if(m[i].options.disabled || (t && !m[i].accept.call(m[i].element[0],(t.currentItem || t.element)))) continue; //No disabled and non-accepted
2035 for (var j=0; j < list.length; j++) { if(list[j] == m[i].element[0]) { m[i].proportions.height = 0; continue droppablesLoop; } }; //Filter out elements in the current dragged item
2036 m[i].visible = m[i].element.css("display") != "none"; if(!m[i].visible) continue; //If the element is not visible, continue
2038 if(type == "mousedown") m[i]._activate.call(m[i], event); //Activate the droppable if used directly from draggables
2040 m[i].offset = m[i].element.offset();
2041 m[i].proportions = { width: m[i].element[0].offsetWidth, height: m[i].element[0].offsetHeight };
2046 drop: function(draggable, event) {
2048 var dropped = false;
2049 $.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() {
2051 if(!this.options) return;
2052 if (!this.options.disabled && this.visible && $.ui.intersect(draggable, this, this.options.tolerance))
2053 dropped = this._drop.call(this, event) || dropped;
2055 if (!this.options.disabled && this.visible && this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
2056 this.isout = 1; this.isover = 0;
2057 this._deactivate.call(this, event);
2064 dragStart: function( draggable, event ) {
2065 //Listen for scrolling so that if the dragging causes scrolling the position of the droppables can be recalculated (see #5003)
2066 draggable.element.parentsUntil( "body" ).bind( "scroll.droppable", function() {
2067 if( !draggable.options.refreshPositions ) $.ui.ddmanager.prepareOffsets( draggable, event );
2070 drag: function(draggable, event) {
2072 //If you have a highly dynamic page, you might try this option. It renders positions every time you move the mouse.
2073 if(draggable.options.refreshPositions) $.ui.ddmanager.prepareOffsets(draggable, event);
2075 //Run through all droppables and check their positions based on specific tolerance options
2076 $.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() {
2078 if(this.options.disabled || this.greedyChild || !this.visible) return;
2079 var intersects = $.ui.intersect(draggable, this, this.options.tolerance);
2081 var c = !intersects && this.isover == 1 ? 'isout' : (intersects && this.isover == 0 ? 'isover' : null);
2085 if (this.options.greedy) {
2086 // find droppable parents with same scope
2087 var scope = this.options.scope;
2088 var parent = this.element.parents(':data(droppable)').filter(function () {
2089 return $.data(this, 'droppable').options.scope === scope;
2092 if (parent.length) {
2093 parentInstance = $.data(parent[0], 'droppable');
2094 parentInstance.greedyChild = (c == 'isover' ? 1 : 0);
2098 // we just moved into a greedy child
2099 if (parentInstance && c == 'isover') {
2100 parentInstance['isover'] = 0;
2101 parentInstance['isout'] = 1;
2102 parentInstance._out.call(parentInstance, event);
2105 this[c] = 1; this[c == 'isout' ? 'isover' : 'isout'] = 0;
2106 this[c == "isover" ? "_over" : "_out"].call(this, event);
2108 // we just moved out of a greedy child
2109 if (parentInstance && c == 'isout') {
2110 parentInstance['isout'] = 0;
2111 parentInstance['isover'] = 1;
2112 parentInstance._over.call(parentInstance, event);
2117 dragStop: function( draggable, event ) {
2118 draggable.element.parentsUntil( "body" ).unbind( "scroll.droppable" );
2119 //Call prepareOffsets one final time since IE does not fire return scroll events when overflow was caused by drag (see #5003)
2120 if( !draggable.options.refreshPositions ) $.ui.ddmanager.prepareOffsets( draggable, event );
2125 (function( $, undefined ) {
2127 $.widget("ui.selectable", $.ui.mouse, {
2136 _create: function() {
2139 this.element.addClass("ui-selectable");
2141 this.dragged = false;
2143 // cache selectee children based on filter
2145 this.refresh = function() {
2146 selectees = $(that.options.filter, that.element[0]);
2147 selectees.addClass("ui-selectee");
2148 selectees.each(function() {
2149 var $this = $(this);
2150 var pos = $this.offset();
2151 $.data(this, "selectable-item", {
2156 right: pos.left + $this.outerWidth(),
2157 bottom: pos.top + $this.outerHeight(),
2158 startselected: false,
2159 selected: $this.hasClass('ui-selected'),
2160 selecting: $this.hasClass('ui-selecting'),
2161 unselecting: $this.hasClass('ui-unselecting')
2167 this.selectees = selectees.addClass("ui-selectee");
2171 this.helper = $("<div class='ui-selectable-helper'></div>");
2174 _destroy: function() {
2176 .removeClass("ui-selectee")
2177 .removeData("selectable-item");
2179 .removeClass("ui-selectable ui-selectable-disabled");
2180 this._mouseDestroy();
2183 _mouseStart: function(event) {
2186 this.opos = [event.pageX, event.pageY];
2188 if (this.options.disabled)
2191 var options = this.options;
2193 this.selectees = $(options.filter, this.element[0]);
2195 this._trigger("start", event);
2197 $(options.appendTo).append(this.helper);
2198 // position helper (lasso)
2200 "left": event.clientX,
2201 "top": event.clientY,
2206 if (options.autoRefresh) {
2210 this.selectees.filter('.ui-selected').each(function() {
2211 var selectee = $.data(this, "selectable-item");
2212 selectee.startselected = true;
2213 if (!event.metaKey && !event.ctrlKey) {
2214 selectee.$element.removeClass('ui-selected');
2215 selectee.selected = false;
2216 selectee.$element.addClass('ui-unselecting');
2217 selectee.unselecting = true;
2218 // selectable UNSELECTING callback
2219 that._trigger("unselecting", event, {
2220 unselecting: selectee.element
2225 $(event.target).parents().andSelf().each(function() {
2226 var selectee = $.data(this, "selectable-item");
2228 var doSelect = (!event.metaKey && !event.ctrlKey) || !selectee.$element.hasClass('ui-selected');
2230 .removeClass(doSelect ? "ui-unselecting" : "ui-selected")
2231 .addClass(doSelect ? "ui-selecting" : "ui-unselecting");
2232 selectee.unselecting = !doSelect;
2233 selectee.selecting = doSelect;
2234 selectee.selected = doSelect;
2235 // selectable (UN)SELECTING callback
2237 that._trigger("selecting", event, {
2238 selecting: selectee.element
2241 that._trigger("unselecting", event, {
2242 unselecting: selectee.element
2251 _mouseDrag: function(event) {
2253 this.dragged = true;
2255 if (this.options.disabled)
2258 var options = this.options;
2260 var x1 = this.opos[0], y1 = this.opos[1], x2 = event.pageX, y2 = event.pageY;
2261 if (x1 > x2) { var tmp = x2; x2 = x1; x1 = tmp; }
2262 if (y1 > y2) { var tmp = y2; y2 = y1; y1 = tmp; }
2263 this.helper.css({left: x1, top: y1, width: x2-x1, height: y2-y1});
2265 this.selectees.each(function() {
2266 var selectee = $.data(this, "selectable-item");
2267 //prevent helper from being selected if appendTo: selectable
2268 if (!selectee || selectee.element == that.element[0])
2271 if (options.tolerance == 'touch') {
2272 hit = ( !(selectee.left > x2 || selectee.right < x1 || selectee.top > y2 || selectee.bottom < y1) );
2273 } else if (options.tolerance == 'fit') {
2274 hit = (selectee.left > x1 && selectee.right < x2 && selectee.top > y1 && selectee.bottom < y2);
2279 if (selectee.selected) {
2280 selectee.$element.removeClass('ui-selected');
2281 selectee.selected = false;
2283 if (selectee.unselecting) {
2284 selectee.$element.removeClass('ui-unselecting');
2285 selectee.unselecting = false;
2287 if (!selectee.selecting) {
2288 selectee.$element.addClass('ui-selecting');
2289 selectee.selecting = true;
2290 // selectable SELECTING callback
2291 that._trigger("selecting", event, {
2292 selecting: selectee.element
2297 if (selectee.selecting) {
2298 if ((event.metaKey || event.ctrlKey) && selectee.startselected) {
2299 selectee.$element.removeClass('ui-selecting');
2300 selectee.selecting = false;
2301 selectee.$element.addClass('ui-selected');
2302 selectee.selected = true;
2304 selectee.$element.removeClass('ui-selecting');
2305 selectee.selecting = false;
2306 if (selectee.startselected) {
2307 selectee.$element.addClass('ui-unselecting');
2308 selectee.unselecting = true;
2310 // selectable UNSELECTING callback
2311 that._trigger("unselecting", event, {
2312 unselecting: selectee.element
2316 if (selectee.selected) {
2317 if (!event.metaKey && !event.ctrlKey && !selectee.startselected) {
2318 selectee.$element.removeClass('ui-selected');
2319 selectee.selected = false;
2321 selectee.$element.addClass('ui-unselecting');
2322 selectee.unselecting = true;
2323 // selectable UNSELECTING callback
2324 that._trigger("unselecting", event, {
2325 unselecting: selectee.element
2335 _mouseStop: function(event) {
2338 this.dragged = false;
2340 var options = this.options;
2342 $('.ui-unselecting', this.element[0]).each(function() {
2343 var selectee = $.data(this, "selectable-item");
2344 selectee.$element.removeClass('ui-unselecting');
2345 selectee.unselecting = false;
2346 selectee.startselected = false;
2347 that._trigger("unselected", event, {
2348 unselected: selectee.element
2351 $('.ui-selecting', this.element[0]).each(function() {
2352 var selectee = $.data(this, "selectable-item");
2353 selectee.$element.removeClass('ui-selecting').addClass('ui-selected');
2354 selectee.selecting = false;
2355 selectee.selected = true;
2356 selectee.startselected = true;
2357 that._trigger("selected", event, {
2358 selected: selectee.element
2361 this._trigger("stop", event);
2363 this.helper.remove();
2371 (function( $, undefined ) {
2373 $.widget("ui.sortable", $.ui.mouse, {
2375 widgetEventPrefix: "sort",
2385 forcePlaceholderSize: false,
2386 forceHelperSize: false,
2395 scrollSensitivity: 20,
2398 tolerance: "intersect",
2401 _create: function() {
2403 var o = this.options;
2404 this.containerCache = {};
2405 this.element.addClass("ui-sortable");
2410 //Let's determine if the items are being displayed horizontally
2411 this.floating = this.items.length ? o.axis === 'x' || (/left|right/).test(this.items[0].item.css('float')) || (/inline|table-cell/).test(this.items[0].item.css('display')) : false;
2413 //Let's determine the parent's offset
2414 this.offset = this.element.offset();
2416 //Initialize mouse events for interaction
2424 _destroy: function() {
2426 .removeClass("ui-sortable ui-sortable-disabled");
2427 this._mouseDestroy();
2429 for ( var i = this.items.length - 1; i >= 0; i-- )
2430 this.items[i].item.removeData(this.widgetName + "-item");
2435 _setOption: function(key, value){
2436 if ( key === "disabled" ) {
2437 this.options[ key ] = value;
2439 this.widget().toggleClass( "ui-sortable-disabled", !!value );
2441 // Don't call widget base _setOption for disable as it adds ui-state-disabled class
2442 $.Widget.prototype._setOption.apply(this, arguments);
2446 _mouseCapture: function(event, overrideHandle) {
2449 if (this.reverting) {
2453 if(this.options.disabled || this.options.type == 'static') return false;
2455 //We have to refresh the items data once first
2456 this._refreshItems(event);
2458 //Find out if the clicked node (or one of its parents) is a actual item in this.items
2459 var currentItem = null, nodes = $(event.target).parents().each(function() {
2460 if($.data(this, that.widgetName + '-item') == that) {
2461 currentItem = $(this);
2465 if($.data(event.target, that.widgetName + '-item') == that) currentItem = $(event.target);
2467 if(!currentItem) return false;
2468 if(this.options.handle && !overrideHandle) {
2469 var validHandle = false;
2471 $(this.options.handle, currentItem).find("*").andSelf().each(function() { if(this == event.target) validHandle = true; });
2472 if(!validHandle) return false;
2475 this.currentItem = currentItem;
2476 this._removeCurrentsFromItems();
2481 _mouseStart: function(event, overrideHandle, noActivation) {
2483 var o = this.options;
2484 this.currentContainer = this;
2486 //We only need to call refreshPositions, because the refreshItems call has been moved to mouseCapture
2487 this.refreshPositions();
2489 //Create and append the visible helper
2490 this.helper = this._createHelper(event);
2492 //Cache the helper size
2493 this._cacheHelperProportions();
2496 * - Position generation -
2497 * This block generates everything position related - it's the core of draggables.
2500 //Cache the margins of the original element
2501 this._cacheMargins();
2503 //Get the next scrolling parent
2504 this.scrollParent = this.helper.scrollParent();
2506 //The element's absolute position on the page minus margins
2507 this.offset = this.currentItem.offset();
2509 top: this.offset.top - this.margins.top,
2510 left: this.offset.left - this.margins.left
2513 $.extend(this.offset, {
2514 click: { //Where the click happened, relative to the element
2515 left: event.pageX - this.offset.left,
2516 top: event.pageY - this.offset.top
2518 parent: this._getParentOffset(),
2519 relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
2522 // Only after we got the offset, we can change the helper's position to absolute
2523 // TODO: Still need to figure out a way to make relative sorting possible
2524 this.helper.css("position", "absolute");
2525 this.cssPosition = this.helper.css("position");
2527 //Generate the original position
2528 this.originalPosition = this._generatePosition(event);
2529 this.originalPageX = event.pageX;
2530 this.originalPageY = event.pageY;
2532 //Adjust the mouse offset relative to the helper if 'cursorAt' is supplied
2533 (o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
2535 //Cache the former DOM position
2536 this.domPosition = { prev: this.currentItem.prev()[0], parent: this.currentItem.parent()[0] };
2538 //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
2539 if(this.helper[0] != this.currentItem[0]) {
2540 this.currentItem.hide();
2543 //Create the placeholder
2544 this._createPlaceholder();
2546 //Set a containment if given in the options
2548 this._setContainment();
2550 if(o.cursor) { // cursor option
2551 if ($('body').css("cursor")) this._storedCursor = $('body').css("cursor");
2552 $('body').css("cursor", o.cursor);
2555 if(o.opacity) { // opacity option
2556 if (this.helper.css("opacity")) this._storedOpacity = this.helper.css("opacity");
2557 this.helper.css("opacity", o.opacity);
2560 if(o.zIndex) { // zIndex option
2561 if (this.helper.css("zIndex")) this._storedZIndex = this.helper.css("zIndex");
2562 this.helper.css("zIndex", o.zIndex);
2566 if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML')
2567 this.overflowOffset = this.scrollParent.offset();
2570 this._trigger("start", event, this._uiHash());
2572 //Recache the helper size
2573 if(!this._preserveHelperProportions)
2574 this._cacheHelperProportions();
2577 //Post 'activate' events to possible containers
2579 for (var i = this.containers.length - 1; i >= 0; i--) { this.containers[i]._trigger("activate", event, this._uiHash(this)); }
2582 //Prepare possible droppables
2584 $.ui.ddmanager.current = this;
2586 if ($.ui.ddmanager && !o.dropBehaviour)
2587 $.ui.ddmanager.prepareOffsets(this, event);
2589 this.dragging = true;
2591 this.helper.addClass("ui-sortable-helper");
2592 this._mouseDrag(event); //Execute the drag once - this causes the helper not to be visible before getting its correct position
2597 _mouseDrag: function(event) {
2599 //Compute the helpers position
2600 this.position = this._generatePosition(event);
2601 this.positionAbs = this._convertPositionTo("absolute");
2603 if (!this.lastPositionAbs) {
2604 this.lastPositionAbs = this.positionAbs;
2608 if(this.options.scroll) {
2609 var o = this.options, scrolled = false;
2610 if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML') {
2612 if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity)
2613 this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed;
2614 else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity)
2615 this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed;
2617 if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity)
2618 this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed;
2619 else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity)
2620 this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed;
2624 if(event.pageY - $(document).scrollTop() < o.scrollSensitivity)
2625 scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
2626 else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity)
2627 scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
2629 if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity)
2630 scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
2631 else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity)
2632 scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
2636 if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour)
2637 $.ui.ddmanager.prepareOffsets(this, event);
2640 //Regenerate the absolute position used for position checks
2641 this.positionAbs = this._convertPositionTo("absolute");
2643 //Set the helper position
2644 if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px';
2645 if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px';
2648 for (var i = this.items.length - 1; i >= 0; i--) {
2650 //Cache variables and intersection, continue if no intersection
2651 var item = this.items[i], itemElement = item.item[0], intersection = this._intersectsWithPointer(item);
2652 if (!intersection) continue;
2654 // Only put the placeholder inside the current Container, skip all
2655 // items form other containers. This works because when moving
2656 // an item from one container to another the
2657 // currentContainer is switched before the placeholder is moved.
2659 // Without this moving items in "sub-sortables" can cause the placeholder to jitter
2660 // beetween the outer and inner container.
2661 if (item.instance !== this.currentContainer) continue;
2663 if (itemElement != this.currentItem[0] //cannot intersect with itself
2664 && this.placeholder[intersection == 1 ? "next" : "prev"]()[0] != itemElement //no useless actions that have been done before
2665 && !$.contains(this.placeholder[0], itemElement) //no action if the item moved is the parent of the item checked
2666 && (this.options.type == 'semi-dynamic' ? !$.contains(this.element[0], itemElement) : true)
2667 //&& itemElement.parentNode == this.placeholder[0].parentNode // only rearrange items within the same container
2670 this.direction = intersection == 1 ? "down" : "up";
2672 if (this.options.tolerance == "pointer" || this._intersectsWithSides(item)) {
2673 this._rearrange(event, item);
2678 this._trigger("change", event, this._uiHash());
2683 //Post events to containers
2684 this._contactContainers(event);
2686 //Interconnect with droppables
2687 if($.ui.ddmanager) $.ui.ddmanager.drag(this, event);
2690 this._trigger('sort', event, this._uiHash());
2692 this.lastPositionAbs = this.positionAbs;
2697 _mouseStop: function(event, noPropagation) {
2701 //If we are using droppables, inform the manager about the drop
2702 if ($.ui.ddmanager && !this.options.dropBehaviour)
2703 $.ui.ddmanager.drop(this, event);
2705 if(this.options.revert) {
2707 var cur = this.placeholder.offset();
2709 this.reverting = true;
2711 $(this.helper).animate({
2712 left: cur.left - this.offset.parent.left - this.margins.left + (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollLeft),
2713 top: cur.top - this.offset.parent.top - this.margins.top + (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollTop)
2714 }, parseInt(this.options.revert, 10) || 500, function() {
2718 this._clear(event, noPropagation);
2725 cancel: function() {
2729 this._mouseUp({ target: null });
2731 if(this.options.helper == "original")
2732 this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
2734 this.currentItem.show();
2736 //Post deactivating events to containers
2737 for (var i = this.containers.length - 1; i >= 0; i--){
2738 this.containers[i]._trigger("deactivate", null, this._uiHash(this));
2739 if(this.containers[i].containerCache.over) {
2740 this.containers[i]._trigger("out", null, this._uiHash(this));
2741 this.containers[i].containerCache.over = 0;
2747 if (this.placeholder) {
2748 //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
2749 if(this.placeholder[0].parentNode) this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
2750 if(this.options.helper != "original" && this.helper && this.helper[0].parentNode) this.helper.remove();
2759 if(this.domPosition.prev) {
2760 $(this.domPosition.prev).after(this.currentItem);
2762 $(this.domPosition.parent).prepend(this.currentItem);
2770 serialize: function(o) {
2772 var items = this._getItemsAsjQuery(o && o.connected);
2773 var str = []; o = o || {};
2775 $(items).each(function() {
2776 var res = ($(o.item || this).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/));
2777 if(res) str.push((o.key || res[1]+'[]')+'='+(o.key && o.expression ? res[1] : res[2]));
2780 if(!str.length && o.key) {
2781 str.push(o.key + '=');
2784 return str.join('&');
2788 toArray: function(o) {
2790 var items = this._getItemsAsjQuery(o && o.connected);
2791 var ret = []; o = o || {};
2793 items.each(function() { ret.push($(o.item || this).attr(o.attribute || 'id') || ''); });
2798 /* Be careful with the following core functions */
2799 _intersectsWith: function(item) {
2801 var x1 = this.positionAbs.left,
2802 x2 = x1 + this.helperProportions.width,
2803 y1 = this.positionAbs.top,
2804 y2 = y1 + this.helperProportions.height;
2809 b = t + item.height;
2811 var dyClick = this.offset.click.top,
2812 dxClick = this.offset.click.left;
2814 var isOverElement = (y1 + dyClick) > t && (y1 + dyClick) < b && (x1 + dxClick) > l && (x1 + dxClick) < r;
2816 if( this.options.tolerance == "pointer"
2817 || this.options.forcePointerForContainers
2818 || (this.options.tolerance != "pointer" && this.helperProportions[this.floating ? 'width' : 'height'] > item[this.floating ? 'width' : 'height'])
2820 return isOverElement;
2823 return (l < x1 + (this.helperProportions.width / 2) // Right Half
2824 && x2 - (this.helperProportions.width / 2) < r // Left Half
2825 && t < y1 + (this.helperProportions.height / 2) // Bottom Half
2826 && y2 - (this.helperProportions.height / 2) < b ); // Top Half
2831 _intersectsWithPointer: function(item) {
2833 var isOverElementHeight = (this.options.axis === 'x') || $.ui.isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height),
2834 isOverElementWidth = (this.options.axis === 'y') || $.ui.isOverAxis(this.positionAbs.left + this.offset.click.left, item.left, item.width),
2835 isOverElement = isOverElementHeight && isOverElementWidth,
2836 verticalDirection = this._getDragVerticalDirection(),
2837 horizontalDirection = this._getDragHorizontalDirection();
2842 return this.floating ?
2843 ( ((horizontalDirection && horizontalDirection == "right") || verticalDirection == "down") ? 2 : 1 )
2844 : ( verticalDirection && (verticalDirection == "down" ? 2 : 1) );
2848 _intersectsWithSides: function(item) {
2850 var isOverBottomHalf = $.ui.isOverAxis(this.positionAbs.top + this.offset.click.top, item.top + (item.height/2), item.height),
2851 isOverRightHalf = $.ui.isOverAxis(this.positionAbs.left + this.offset.click.left, item.left + (item.width/2), item.width),
2852 verticalDirection = this._getDragVerticalDirection(),
2853 horizontalDirection = this._getDragHorizontalDirection();
2855 if (this.floating && horizontalDirection) {
2856 return ((horizontalDirection == "right" && isOverRightHalf) || (horizontalDirection == "left" && !isOverRightHalf));
2858 return verticalDirection && ((verticalDirection == "down" && isOverBottomHalf) || (verticalDirection == "up" && !isOverBottomHalf));
2863 _getDragVerticalDirection: function() {
2864 var delta = this.positionAbs.top - this.lastPositionAbs.top;
2865 return delta != 0 && (delta > 0 ? "down" : "up");
2868 _getDragHorizontalDirection: function() {
2869 var delta = this.positionAbs.left - this.lastPositionAbs.left;
2870 return delta != 0 && (delta > 0 ? "right" : "left");
2873 refresh: function(event) {
2874 this._refreshItems(event);
2875 this.refreshPositions();
2879 _connectWith: function() {
2880 var options = this.options;
2881 return options.connectWith.constructor == String
2882 ? [options.connectWith]
2883 : options.connectWith;
2886 _getItemsAsjQuery: function(connected) {
2890 var connectWith = this._connectWith();
2892 if(connectWith && connected) {
2893 for (var i = connectWith.length - 1; i >= 0; i--){
2894 var cur = $(connectWith[i]);
2895 for (var j = cur.length - 1; j >= 0; j--){
2896 var inst = $.data(cur[j], this.widgetName);
2897 if(inst && inst != this && !inst.options.disabled) {
2898 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]);
2904 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]);
2906 for (var i = queries.length - 1; i >= 0; i--){
2907 queries[i][0].each(function() {
2916 _removeCurrentsFromItems: function() {
2918 var list = this.currentItem.find(":data(" + this.widgetName + "-item)");
2920 this.items = $.grep(this.items, function (item) {
2921 for (var j=0; j < list.length; j++) {
2922 if(list[j] == item.item[0])
2930 _refreshItems: function(event) {
2933 this.containers = [this];
2934 var items = this.items;
2935 var queries = [[$.isFunction(this.options.items) ? this.options.items.call(this.element[0], event, { item: this.currentItem }) : $(this.options.items, this.element), this]];
2936 var connectWith = this._connectWith();
2938 if(connectWith && this.ready) { //Shouldn't be run the first time through due to massive slow-down
2939 for (var i = connectWith.length - 1; i >= 0; i--){
2940 var cur = $(connectWith[i]);
2941 for (var j = cur.length - 1; j >= 0; j--){
2942 var inst = $.data(cur[j], this.widgetName);
2943 if(inst && inst != this && !inst.options.disabled) {
2944 queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element[0], event, { item: this.currentItem }) : $(inst.options.items, inst.element), inst]);
2945 this.containers.push(inst);
2951 for (var i = queries.length - 1; i >= 0; i--) {
2952 var targetData = queries[i][1];
2953 var _queries = queries[i][0];
2955 for (var j=0, queriesLength = _queries.length; j < queriesLength; j++) {
2956 var item = $(_queries[j]);
2958 item.data(this.widgetName + '-item', targetData); // Data for target checking (mouse manager)
2962 instance: targetData,
2963 width: 0, height: 0,
2971 refreshPositions: function(fast) {
2973 //This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change
2974 if(this.offsetParent && this.helper) {
2975 this.offset.parent = this._getParentOffset();
2978 for (var i = this.items.length - 1; i >= 0; i--){
2979 var item = this.items[i];
2981 //We ignore calculating positions of all connected containers when we're not over them
2982 if(item.instance != this.currentContainer && this.currentContainer && item.item[0] != this.currentItem[0])
2985 var t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item;
2988 item.width = t.outerWidth();
2989 item.height = t.outerHeight();
2997 if(this.options.custom && this.options.custom.refreshContainers) {
2998 this.options.custom.refreshContainers.call(this);
3000 for (var i = this.containers.length - 1; i >= 0; i--){
3001 var p = this.containers[i].element.offset();
3002 this.containers[i].containerCache.left = p.left;
3003 this.containers[i].containerCache.top = p.top;
3004 this.containers[i].containerCache.width = this.containers[i].element.outerWidth();
3005 this.containers[i].containerCache.height = this.containers[i].element.outerHeight();
3012 _createPlaceholder: function(that) {
3013 that = that || this;
3014 var o = that.options;
3016 if(!o.placeholder || o.placeholder.constructor == String) {
3017 var className = o.placeholder;
3019 element: function() {
3021 var el = $(document.createElement(that.currentItem[0].nodeName))
3022 .addClass(className || that.currentItem[0].className+" ui-sortable-placeholder")
3023 .removeClass("ui-sortable-helper")[0];
3026 el.style.visibility = "hidden";
3030 update: function(container, p) {
3032 // 1. If a className is set as 'placeholder option, we don't force sizes - the class is responsible for that
3033 // 2. The option 'forcePlaceholderSize can be enabled to force it even if a class name is specified
3034 if(className && !o.forcePlaceholderSize) return;
3036 //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
3037 if(!p.height()) { p.height(that.currentItem.innerHeight() - parseInt(that.currentItem.css('paddingTop')||0, 10) - parseInt(that.currentItem.css('paddingBottom')||0, 10)); };
3038 if(!p.width()) { p.width(that.currentItem.innerWidth() - parseInt(that.currentItem.css('paddingLeft')||0, 10) - parseInt(that.currentItem.css('paddingRight')||0, 10)); };
3043 //Create the placeholder
3044 that.placeholder = $(o.placeholder.element.call(that.element, that.currentItem));
3046 //Append it after the actual current item
3047 that.currentItem.after(that.placeholder);
3049 //Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317)
3050 o.placeholder.update(that, that.placeholder);
3054 _contactContainers: function(event) {
3056 // get innermost container that intersects with item
3057 var innermostContainer = null, innermostIndex = null;
3060 for (var i = this.containers.length - 1; i >= 0; i--){
3062 // never consider a container that's located within the item itself
3063 if($.contains(this.currentItem[0], this.containers[i].element[0]))
3066 if(this._intersectsWith(this.containers[i].containerCache)) {
3068 // if we've already found a container and it's more "inner" than this, then continue
3069 if(innermostContainer && $.contains(this.containers[i].element[0], innermostContainer.element[0]))
3072 innermostContainer = this.containers[i];
3076 // container doesn't intersect. trigger "out" event if necessary
3077 if(this.containers[i].containerCache.over) {
3078 this.containers[i]._trigger("out", event, this._uiHash(this));
3079 this.containers[i].containerCache.over = 0;
3085 // if no intersecting containers found, return
3086 if(!innermostContainer) return;
3088 // move the item into the container if it's not there already
3089 if(this.containers.length === 1) {
3090 this.containers[innermostIndex]._trigger("over", event, this._uiHash(this));
3091 this.containers[innermostIndex].containerCache.over = 1;
3094 //When entering a new container, we will find the item with the least distance and append our item near it
3095 var dist = 10000; var itemWithLeastDistance = null;
3096 var posProperty = this.containers[innermostIndex].floating ? 'left' : 'top';
3097 var sizeProperty = this.containers[innermostIndex].floating ? 'width' : 'height';
3098 var base = this.positionAbs[posProperty] + this.offset.click[posProperty];
3099 for (var j = this.items.length - 1; j >= 0; j--) {
3100 if(!$.contains(this.containers[innermostIndex].element[0], this.items[j].item[0])) continue;
3101 if(this.items[j].item[0] == this.currentItem[0]) continue;
3102 var cur = this.items[j].item.offset()[posProperty];
3103 var nearBottom = false;
3104 if(Math.abs(cur - base) > Math.abs(cur + this.items[j][sizeProperty] - base)){
3106 cur += this.items[j][sizeProperty];
3109 if(Math.abs(cur - base) < dist) {
3110 dist = Math.abs(cur - base); itemWithLeastDistance = this.items[j];
3111 this.direction = nearBottom ? "up": "down";
3115 if(!itemWithLeastDistance && !this.options.dropOnEmpty) //Check if dropOnEmpty is enabled
3118 this.currentContainer = this.containers[innermostIndex];
3119 itemWithLeastDistance ? this._rearrange(event, itemWithLeastDistance, null, true) : this._rearrange(event, null, this.containers[innermostIndex].element, true);
3120 this._trigger("change", event, this._uiHash());
3121 this.containers[innermostIndex]._trigger("change", event, this._uiHash(this));
3123 //Update the placeholder
3124 this.options.placeholder.update(this.currentContainer, this.placeholder);
3126 this.containers[innermostIndex]._trigger("over", event, this._uiHash(this));
3127 this.containers[innermostIndex].containerCache.over = 1;
3133 _createHelper: function(event) {
3135 var o = this.options;
3136 var helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event, this.currentItem])) : (o.helper == 'clone' ? this.currentItem.clone() : this.currentItem);
3138 if(!helper.parents('body').length) //Add the helper to the DOM if that didn't happen already
3139 $(o.appendTo != 'parent' ? o.appendTo : this.currentItem[0].parentNode)[0].appendChild(helper[0]);
3141 if(helper[0] == this.currentItem[0])
3142 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") };
3144 if(helper[0].style.width == '' || o.forceHelperSize) helper.width(this.currentItem.width());
3145 if(helper[0].style.height == '' || o.forceHelperSize) helper.height(this.currentItem.height());
3151 _adjustOffsetFromHelper: function(obj) {
3152 if (typeof obj == 'string') {
3153 obj = obj.split(' ');
3155 if ($.isArray(obj)) {
3156 obj = {left: +obj[0], top: +obj[1] || 0};
3158 if ('left' in obj) {
3159 this.offset.click.left = obj.left + this.margins.left;
3161 if ('right' in obj) {
3162 this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
3165 this.offset.click.top = obj.top + this.margins.top;
3167 if ('bottom' in obj) {
3168 this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
3172 _getParentOffset: function() {
3175 //Get the offsetParent and cache its position
3176 this.offsetParent = this.helper.offsetParent();
3177 var po = this.offsetParent.offset();
3179 // This is a special case where we need to modify a offset calculated on start, since the following happened:
3180 // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
3181 // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
3182 // the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
3183 if(this.cssPosition == 'absolute' && this.scrollParent[0] != document && $.contains(this.scrollParent[0], this.offsetParent[0])) {
3184 po.left += this.scrollParent.scrollLeft();
3185 po.top += this.scrollParent.scrollTop();
3188 if((this.offsetParent[0] == document.body) //This needs to be actually done for all browsers, since pageX/pageY includes this information
3189 || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() == 'html' && $.ui.ie)) //Ugly IE fix
3190 po = { top: 0, left: 0 };
3193 top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
3194 left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
3199 _getRelativeOffset: function() {
3201 if(this.cssPosition == "relative") {
3202 var p = this.currentItem.position();
3204 top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
3205 left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
3208 return { top: 0, left: 0 };
3213 _cacheMargins: function() {
3215 left: (parseInt(this.currentItem.css("marginLeft"),10) || 0),
3216 top: (parseInt(this.currentItem.css("marginTop"),10) || 0)
3220 _cacheHelperProportions: function() {
3221 this.helperProportions = {
3222 width: this.helper.outerWidth(),
3223 height: this.helper.outerHeight()
3227 _setContainment: function() {
3229 var o = this.options;
3230 if(o.containment == 'parent') o.containment = this.helper[0].parentNode;
3231 if(o.containment == 'document' || o.containment == 'window') this.containment = [
3232 0 - this.offset.relative.left - this.offset.parent.left,
3233 0 - this.offset.relative.top - this.offset.parent.top,
3234 $(o.containment == 'document' ? document : window).width() - this.helperProportions.width - this.margins.left,
3235 ($(o.containment == 'document' ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top
3238 if(!(/^(document|window|parent)$/).test(o.containment)) {
3239 var ce = $(o.containment)[0];
3240 var co = $(o.containment).offset();
3241 var over = ($(ce).css("overflow") != 'hidden');
3243 this.containment = [
3244 co.left + (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0) - this.margins.left,
3245 co.top + (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0) - this.margins.top,
3246 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,
3247 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
3253 _convertPositionTo: function(d, pos) {
3255 if(!pos) pos = this.position;
3256 var mod = d == "absolute" ? 1 : -1;
3257 var o = this.options, 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);
3261 pos.top // The absolute mouse position
3262 + this.offset.relative.top * mod // Only for relative positioned nodes: Relative offset from element to offset parent
3263 + this.offset.parent.top * mod // The offsetParent's offset without borders (offset + border)
3264 - ( ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod)
3267 pos.left // The absolute mouse position
3268 + this.offset.relative.left * mod // Only for relative positioned nodes: Relative offset from element to offset parent
3269 + this.offset.parent.left * mod // The offsetParent's offset without borders (offset + border)
3270 - ( ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod)
3276 _generatePosition: function(event) {
3278 var o = this.options, 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);
3280 // This is another very weird special case that only happens for relative elements:
3281 // 1. If the css position is relative
3282 // 2. and the scroll parent is the document or similar to the offset parent
3283 // we have to refresh the relative offset during the scroll so there are no jumps
3284 if(this.cssPosition == 'relative' && !(this.scrollParent[0] != document && this.scrollParent[0] != this.offsetParent[0])) {
3285 this.offset.relative = this._getRelativeOffset();
3288 var pageX = event.pageX;
3289 var pageY = event.pageY;
3292 * - Position constraining -
3293 * Constrain the position to a mix of grid, containment.
3296 if(this.originalPosition) { //If we are not dragging yet, we won't check for options
3298 if(this.containment) {
3299 if(event.pageX - this.offset.click.left < this.containment[0]) pageX = this.containment[0] + this.offset.click.left;
3300 if(event.pageY - this.offset.click.top < this.containment[1]) pageY = this.containment[1] + this.offset.click.top;
3301 if(event.pageX - this.offset.click.left > this.containment[2]) pageX = this.containment[2] + this.offset.click.left;
3302 if(event.pageY - this.offset.click.top > this.containment[3]) pageY = this.containment[3] + this.offset.click.top;
3306 var top = this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1];
3307 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;
3309 var left = this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0];
3310 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;
3317 pageY // The absolute mouse position
3318 - this.offset.click.top // Click offset (relative to the element)
3319 - this.offset.relative.top // Only for relative positioned nodes: Relative offset from element to offset parent
3320 - this.offset.parent.top // The offsetParent's offset without borders (offset + border)
3321 + ( ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ))
3324 pageX // The absolute mouse position
3325 - this.offset.click.left // Click offset (relative to the element)
3326 - this.offset.relative.left // Only for relative positioned nodes: Relative offset from element to offset parent
3327 - this.offset.parent.left // The offsetParent's offset without borders (offset + border)
3328 + ( ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ))
3334 _rearrange: function(event, i, a, hardRefresh) {
3336 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));
3338 //Various things done here to improve the performance:
3339 // 1. we create a setTimeout, that calls refreshPositions
3340 // 2. on the instance, we have a counter variable, that get's higher after every append
3341 // 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same
3342 // 4. this lets only the last addition to the timeout stack through
3343 this.counter = this.counter ? ++this.counter : 1;
3344 var counter = this.counter;
3346 this._delay(function() {
3347 if(counter == this.counter) this.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove
3352 _clear: function(event, noPropagation) {
3354 this.reverting = false;
3355 // We delay all events that have to be triggered to after the point where the placeholder has been removed and
3356 // everything else normalized again
3357 var delayedTriggers = [];
3359 // We first have to update the dom position of the actual currentItem
3360 // Note: don't do it if the current item is already removed (by a user), or it gets reappended (see #4088)
3361 if(!this._noFinalSort && this.currentItem.parent().length) this.placeholder.before(this.currentItem);
3362 this._noFinalSort = null;
3364 if(this.helper[0] == this.currentItem[0]) {
3365 for(var i in this._storedCSS) {
3366 if(this._storedCSS[i] == 'auto' || this._storedCSS[i] == 'static') this._storedCSS[i] = '';
3368 this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
3370 this.currentItem.show();
3373 if(this.fromOutside && !noPropagation) delayedTriggers.push(function(event) { this._trigger("receive", event, this._uiHash(this.fromOutside)); });
3374 if((this.fromOutside || this.domPosition.prev != this.currentItem.prev().not(".ui-sortable-helper")[0] || this.domPosition.parent != this.currentItem.parent()[0]) && !noPropagation) delayedTriggers.push(function(event) { this._trigger("update", event, this._uiHash()); }); //Trigger update callback if the DOM position has changed
3376 // Check if the items Container has Changed and trigger appropriate
3378 if (this !== this.currentContainer) {
3379 if(!noPropagation) {
3380 delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); });
3381 delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); }; }).call(this, this.currentContainer));
3382 delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this)); }; }).call(this, this.currentContainer));
3387 //Post events to containers
3388 for (var i = this.containers.length - 1; i >= 0; i--){
3389 if(!noPropagation) delayedTriggers.push((function(c) { return function(event) { c._trigger("deactivate", event, this._uiHash(this)); }; }).call(this, this.containers[i]));
3390 if(this.containers[i].containerCache.over) {
3391 delayedTriggers.push((function(c) { return function(event) { c._trigger("out", event, this._uiHash(this)); }; }).call(this, this.containers[i]));
3392 this.containers[i].containerCache.over = 0;
3396 //Do what was originally in plugins
3397 if(this._storedCursor) $('body').css("cursor", this._storedCursor); //Reset cursor
3398 if(this._storedOpacity) this.helper.css("opacity", this._storedOpacity); //Reset opacity
3399 if(this._storedZIndex) this.helper.css("zIndex", this._storedZIndex == 'auto' ? '' : this._storedZIndex); //Reset z-index
3401 this.dragging = false;
3402 if(this.cancelHelperRemoval) {
3403 if(!noPropagation) {
3404 this._trigger("beforeStop", event, this._uiHash());
3405 for (var i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); }; //Trigger all delayed events
3406 this._trigger("stop", event, this._uiHash());
3409 this.fromOutside = false;
3413 if(!noPropagation) this._trigger("beforeStop", event, this._uiHash());
3415 //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
3416 this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
3418 if(this.helper[0] != this.currentItem[0]) this.helper.remove(); this.helper = null;
3420 if(!noPropagation) {
3421 for (var i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); }; //Trigger all delayed events
3422 this._trigger("stop", event, this._uiHash());
3425 this.fromOutside = false;
3430 _trigger: function() {
3431 if ($.Widget.prototype._trigger.apply(this, arguments) === false) {
3436 _uiHash: function(_inst) {
3437 var inst = _inst || this;
3439 helper: inst.helper,
3440 placeholder: inst.placeholder || $([]),
3441 position: inst.position,
3442 originalPosition: inst.originalPosition,
3443 offset: inst.positionAbs,
3444 item: inst.currentItem,
3445 sender: _inst ? _inst.element : null