1 /*! jQuery UI - v1.9.1 - 2012-11-16
3 * Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.draggable.js, jquery.ui.droppable.js, jquery.ui.resizable.js, jquery.ui.selectable.js, jquery.ui.sortable.js, jquery.ui.progressbar.js, jquery.ui.tabs.js
4 * Copyright (c) 2012 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" );
126 // support: jQuery <1.8
127 if ( !$( "<a>" ).outerWidth( 1 ).jquery ) {
128 $.each( [ "Width", "Height" ], function( i, name ) {
129 var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ],
130 type = name.toLowerCase(),
132 innerWidth: $.fn.innerWidth,
133 innerHeight: $.fn.innerHeight,
134 outerWidth: $.fn.outerWidth,
135 outerHeight: $.fn.outerHeight
138 function reduce( elem, size, border, margin ) {
139 $.each( side, function() {
140 size -= parseFloat( $.css( elem, "padding" + this ) ) || 0;
142 size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0;
145 size -= parseFloat( $.css( elem, "margin" + this ) ) || 0;
151 $.fn[ "inner" + name ] = function( size ) {
152 if ( size === undefined ) {
153 return orig[ "inner" + name ].call( this );
156 return this.each(function() {
157 $( this ).css( type, reduce( this, size ) + "px" );
161 $.fn[ "outer" + name] = function( size, margin ) {
162 if ( typeof size !== "number" ) {
163 return orig[ "outer" + name ].call( this, size );
166 return this.each(function() {
167 $( this).css( type, reduce( this, size, true, margin ) + "px" );
174 function focusable( element, isTabIndexNotNaN ) {
175 var map, mapName, img,
176 nodeName = element.nodeName.toLowerCase();
177 if ( "area" === nodeName ) {
178 map = element.parentNode;
180 if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) {
183 img = $( "img[usemap=#" + mapName + "]" )[0];
184 return !!img && visible( img );
186 return ( /input|select|textarea|button|object/.test( nodeName ) ?
189 element.href || isTabIndexNotNaN :
191 // the element and all of its ancestors must be visible
195 function visible( element ) {
196 return $.expr.filters.visible( element ) &&
197 !$( element ).parents().andSelf().filter(function() {
198 return $.css( this, "visibility" ) === "hidden";
202 $.extend( $.expr[ ":" ], {
203 data: $.expr.createPseudo ?
204 $.expr.createPseudo(function( dataName ) {
205 return function( elem ) {
206 return !!$.data( elem, dataName );
209 // support: jQuery <1.8
210 function( elem, i, match ) {
211 return !!$.data( elem, match[ 3 ] );
214 focusable: function( element ) {
215 return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) );
218 tabbable: function( element ) {
219 var tabIndex = $.attr( element, "tabindex" ),
220 isTabIndexNaN = isNaN( tabIndex );
221 return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN );
227 var body = document.body,
228 div = body.appendChild( div = document.createElement( "div" ) );
230 // access offsetHeight before setting the style to prevent a layout bug
231 // in IE 9 which causes the element to continue to take up space even
232 // after it is removed from the DOM (#8026)
235 $.extend( div.style, {
242 $.support.minHeight = div.offsetHeight === 100;
243 $.support.selectstart = "onselectstart" in div;
245 // set display to none to avoid a layout bug in IE
246 // http://dev.jquery.com/ticket/4014
247 body.removeChild( div ).style.display = "none";
257 var uaMatch = /msie ([\w.]+)/.exec( navigator.userAgent.toLowerCase() ) || [];
258 $.ui.ie = uaMatch.length ? true : false;
259 $.ui.ie6 = parseFloat( uaMatch[ 1 ], 10 ) === 6;
263 disableSelection: function() {
264 return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) +
265 ".ui-disableSelection", function( event ) {
266 event.preventDefault();
270 enableSelection: function() {
271 return this.unbind( ".ui-disableSelection" );
276 // $.ui.plugin is deprecated. Use the proxy pattern instead.
278 add: function( module, option, set ) {
280 proto = $.ui[ module ].prototype;
282 proto.plugins[ i ] = proto.plugins[ i ] || [];
283 proto.plugins[ i ].push( [ option, set[ i ] ] );
286 call: function( instance, name, args ) {
288 set = instance.plugins[ name ];
289 if ( !set || !instance.element[ 0 ].parentNode || instance.element[ 0 ].parentNode.nodeType === 11 ) {
293 for ( i = 0; i < set.length; i++ ) {
294 if ( instance.options[ set[ i ][ 0 ] ] ) {
295 set[ i ][ 1 ].apply( instance.element, args );
301 contains: $.contains,
303 // only used by resizable
304 hasScroll: function( el, a ) {
306 //If overflow is hidden, the element might have extra content, but the user wants to hide it
307 if ( $( el ).css( "overflow" ) === "hidden") {
311 var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop",
314 if ( el[ scroll ] > 0 ) {
318 // TODO: determine which cases actually cause this to happen
319 // if the element doesn't have the scroll set, see if it's possible to
322 has = ( el[ scroll ] > 0 );
327 // these are odd functions, fix the API or move into individual plugins
328 isOverAxis: function( x, reference, size ) {
329 //Determines when x coordinate is over "b" element axis
330 return ( x > reference ) && ( x < ( reference + size ) );
332 isOver: function( y, x, top, left, height, width ) {
333 //Determines when x, y coordinates is over "b" element
334 return $.ui.isOverAxis( y, top, height ) && $.ui.isOverAxis( x, left, width );
339 (function( $, undefined ) {
342 slice = Array.prototype.slice,
343 _cleanData = $.cleanData;
344 $.cleanData = function( elems ) {
345 for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
347 $( elem ).triggerHandler( "remove" );
348 // http://bugs.jquery.com/ticket/8235
354 $.widget = function( name, base, prototype ) {
355 var fullName, existingConstructor, constructor, basePrototype,
356 namespace = name.split( "." )[ 0 ];
358 name = name.split( "." )[ 1 ];
359 fullName = namespace + "-" + name;
366 // create selector for plugin
367 $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
368 return !!$.data( elem, fullName );
371 $[ namespace ] = $[ namespace ] || {};
372 existingConstructor = $[ namespace ][ name ];
373 constructor = $[ namespace ][ name ] = function( options, element ) {
374 // allow instantiation without "new" keyword
375 if ( !this._createWidget ) {
376 return new constructor( options, element );
379 // allow instantiation without initializing for simple inheritance
380 // must use "new" keyword (the code above always passes args)
381 if ( arguments.length ) {
382 this._createWidget( options, element );
385 // extend with the existing constructor to carry over any static properties
386 $.extend( constructor, existingConstructor, {
387 version: prototype.version,
388 // copy the object used to create the prototype in case we need to
389 // redefine the widget later
390 _proto: $.extend( {}, prototype ),
391 // track widgets that inherit from this widget in case this widget is
392 // redefined after a widget inherits from it
393 _childConstructors: []
396 basePrototype = new base();
397 // we need to make the options hash a property directly on the new instance
398 // otherwise we'll modify the options hash on the prototype that we're
400 basePrototype.options = $.widget.extend( {}, basePrototype.options );
401 $.each( prototype, function( prop, value ) {
402 if ( $.isFunction( value ) ) {
403 prototype[ prop ] = (function() {
404 var _super = function() {
405 return base.prototype[ prop ].apply( this, arguments );
407 _superApply = function( args ) {
408 return base.prototype[ prop ].apply( this, args );
411 var __super = this._super,
412 __superApply = this._superApply,
415 this._super = _super;
416 this._superApply = _superApply;
418 returnValue = value.apply( this, arguments );
420 this._super = __super;
421 this._superApply = __superApply;
428 constructor.prototype = $.widget.extend( basePrototype, {
429 // TODO: remove support for widgetEventPrefix
430 // always use the name + a colon as the prefix, e.g., draggable:start
431 // don't prefix for widgets that aren't DOM-based
432 widgetEventPrefix: basePrototype.widgetEventPrefix || name
434 constructor: constructor,
435 namespace: namespace,
437 // TODO remove widgetBaseClass, see #8155
438 widgetBaseClass: fullName,
439 widgetFullName: fullName
442 // If this widget is being redefined then we need to find all widgets that
443 // are inheriting from it and redefine all of them so that they inherit from
444 // the new version of this widget. We're essentially trying to replace one
445 // level in the prototype chain.
446 if ( existingConstructor ) {
447 $.each( existingConstructor._childConstructors, function( i, child ) {
448 var childPrototype = child.prototype;
450 // redefine the child widget using the same prototype that was
451 // originally used, but inherit from the new version of the base
452 $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
454 // remove the list of existing child constructors from the old constructor
455 // so the old child constructors can be garbage collected
456 delete existingConstructor._childConstructors;
458 base._childConstructors.push( constructor );
461 $.widget.bridge( name, constructor );
464 $.widget.extend = function( target ) {
465 var input = slice.call( arguments, 1 ),
467 inputLength = input.length,
470 for ( ; inputIndex < inputLength; inputIndex++ ) {
471 for ( key in input[ inputIndex ] ) {
472 value = input[ inputIndex ][ key ];
473 if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
475 if ( $.isPlainObject( value ) ) {
476 target[ key ] = $.isPlainObject( target[ key ] ) ?
477 $.widget.extend( {}, target[ key ], value ) :
478 // Don't extend strings, arrays, etc. with objects
479 $.widget.extend( {}, value );
480 // Copy everything else by reference
482 target[ key ] = value;
490 $.widget.bridge = function( name, object ) {
491 var fullName = object.prototype.widgetFullName;
492 $.fn[ name ] = function( options ) {
493 var isMethodCall = typeof options === "string",
494 args = slice.call( arguments, 1 ),
497 // allow multiple hashes to be passed on init
498 options = !isMethodCall && args.length ?
499 $.widget.extend.apply( null, [ options ].concat(args) ) :
502 if ( isMethodCall ) {
503 this.each(function() {
505 instance = $.data( this, fullName );
507 return $.error( "cannot call methods on " + name + " prior to initialization; " +
508 "attempted to call method '" + options + "'" );
510 if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
511 return $.error( "no such method '" + options + "' for " + name + " widget instance" );
513 methodValue = instance[ options ].apply( instance, args );
514 if ( methodValue !== instance && methodValue !== undefined ) {
515 returnValue = methodValue && methodValue.jquery ?
516 returnValue.pushStack( methodValue.get() ) :
522 this.each(function() {
523 var instance = $.data( this, fullName );
525 instance.option( options || {} )._init();
527 new object( options, this );
536 $.Widget = function( /* options, element */ ) {};
537 $.Widget._childConstructors = [];
539 $.Widget.prototype = {
540 widgetName: "widget",
541 widgetEventPrefix: "",
542 defaultElement: "<div>",
549 _createWidget: function( options, element ) {
550 element = $( element || this.defaultElement || this )[ 0 ];
551 this.element = $( element );
553 this.eventNamespace = "." + this.widgetName + this.uuid;
554 this.options = $.widget.extend( {},
556 this._getCreateOptions(),
560 this.hoverable = $();
561 this.focusable = $();
563 if ( element !== this ) {
565 // TODO remove dual storage
566 $.data( element, this.widgetName, this );
567 $.data( element, this.widgetFullName, this );
568 this._on( this.element, {
569 remove: function( event ) {
570 if ( event.target === element ) {
575 this.document = $( element.style ?
576 // element within the document
577 element.ownerDocument :
578 // element is window or document
579 element.document || element );
580 this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
584 this._trigger( "create", null, this._getCreateEventData() );
587 _getCreateOptions: $.noop,
588 _getCreateEventData: $.noop,
592 destroy: function() {
594 // we can probably remove the unbind calls in 2.0
595 // all event bindings should go through this._on()
597 .unbind( this.eventNamespace )
599 // TODO remove dual storage
600 .removeData( this.widgetName )
601 .removeData( this.widgetFullName )
602 // support: jquery <1.6.3
603 // http://bugs.jquery.com/ticket/9413
604 .removeData( $.camelCase( this.widgetFullName ) );
606 .unbind( this.eventNamespace )
607 .removeAttr( "aria-disabled" )
609 this.widgetFullName + "-disabled " +
610 "ui-state-disabled" );
612 // clean up events and states
613 this.bindings.unbind( this.eventNamespace );
614 this.hoverable.removeClass( "ui-state-hover" );
615 this.focusable.removeClass( "ui-state-focus" );
623 option: function( key, value ) {
629 if ( arguments.length === 0 ) {
630 // don't return a reference to the internal hash
631 return $.widget.extend( {}, this.options );
634 if ( typeof key === "string" ) {
635 // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
637 parts = key.split( "." );
639 if ( parts.length ) {
640 curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
641 for ( i = 0; i < parts.length - 1; i++ ) {
642 curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
643 curOption = curOption[ parts[ i ] ];
646 if ( value === undefined ) {
647 return curOption[ key ] === undefined ? null : curOption[ key ];
649 curOption[ key ] = value;
651 if ( value === undefined ) {
652 return this.options[ key ] === undefined ? null : this.options[ key ];
654 options[ key ] = value;
658 this._setOptions( options );
662 _setOptions: function( options ) {
665 for ( key in options ) {
666 this._setOption( key, options[ key ] );
671 _setOption: function( key, value ) {
672 this.options[ key ] = value;
674 if ( key === "disabled" ) {
676 .toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value )
677 .attr( "aria-disabled", value );
678 this.hoverable.removeClass( "ui-state-hover" );
679 this.focusable.removeClass( "ui-state-focus" );
686 return this._setOption( "disabled", false );
688 disable: function() {
689 return this._setOption( "disabled", true );
692 _on: function( element, handlers ) {
695 // no element argument, shuffle and use this.element
698 element = this.element;
699 delegateElement = this.widget();
701 // accept selectors, DOM elements
702 element = delegateElement = $( element );
703 this.bindings = this.bindings.add( element );
706 $.each( handlers, function( event, handler ) {
707 function handlerProxy() {
708 // allow widgets to customize the disabled handling
709 // - disabled as an array instead of boolean
710 // - disabled class as method for disabling individual parts
711 if ( instance.options.disabled === true ||
712 $( this ).hasClass( "ui-state-disabled" ) ) {
715 return ( typeof handler === "string" ? instance[ handler ] : handler )
716 .apply( instance, arguments );
719 // copy the guid so direct unbinding works
720 if ( typeof handler !== "string" ) {
721 handlerProxy.guid = handler.guid =
722 handler.guid || handlerProxy.guid || $.guid++;
725 var match = event.match( /^(\w+)\s*(.*)$/ ),
726 eventName = match[1] + instance.eventNamespace,
729 delegateElement.delegate( selector, eventName, handlerProxy );
731 element.bind( eventName, handlerProxy );
736 _off: function( element, eventName ) {
737 eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace;
738 element.unbind( eventName ).undelegate( eventName );
741 _delay: function( handler, delay ) {
742 function handlerProxy() {
743 return ( typeof handler === "string" ? instance[ handler ] : handler )
744 .apply( instance, arguments );
747 return setTimeout( handlerProxy, delay || 0 );
750 _hoverable: function( element ) {
751 this.hoverable = this.hoverable.add( element );
753 mouseenter: function( event ) {
754 $( event.currentTarget ).addClass( "ui-state-hover" );
756 mouseleave: function( event ) {
757 $( event.currentTarget ).removeClass( "ui-state-hover" );
762 _focusable: function( element ) {
763 this.focusable = this.focusable.add( element );
765 focusin: function( event ) {
766 $( event.currentTarget ).addClass( "ui-state-focus" );
768 focusout: function( event ) {
769 $( event.currentTarget ).removeClass( "ui-state-focus" );
774 _trigger: function( type, event, data ) {
776 callback = this.options[ type ];
779 event = $.Event( event );
780 event.type = ( type === this.widgetEventPrefix ?
782 this.widgetEventPrefix + type ).toLowerCase();
783 // the original event may come from any element
784 // so we need to reset the target on the new event
785 event.target = this.element[ 0 ];
787 // copy original event properties over to the new event
788 orig = event.originalEvent;
790 for ( prop in orig ) {
791 if ( !( prop in event ) ) {
792 event[ prop ] = orig[ prop ];
797 this.element.trigger( event, data );
798 return !( $.isFunction( callback ) &&
799 callback.apply( this.element[0], [ event ].concat( data ) ) === false ||
800 event.isDefaultPrevented() );
804 $.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
805 $.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
806 if ( typeof options === "string" ) {
807 options = { effect: options };
810 effectName = !options ?
812 options === true || typeof options === "number" ?
814 options.effect || defaultEffect;
815 options = options || {};
816 if ( typeof options === "number" ) {
817 options = { duration: options };
819 hasOptions = !$.isEmptyObject( options );
820 options.complete = callback;
821 if ( options.delay ) {
822 element.delay( options.delay );
824 if ( hasOptions && $.effects && ( $.effects.effect[ effectName ] || $.uiBackCompat !== false && $.effects[ effectName ] ) ) {
825 element[ method ]( options );
826 } else if ( effectName !== method && element[ effectName ] ) {
827 element[ effectName ]( options.duration, options.easing, callback );
829 element.queue(function( next ) {
830 $( this )[ method ]();
832 callback.call( element[ 0 ] );
841 if ( $.uiBackCompat !== false ) {
842 $.Widget.prototype._getCreateOptions = function() {
843 return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ];
848 (function( $, undefined ) {
850 var mouseHandled = false;
851 $( document ).mouseup( function( e ) {
852 mouseHandled = false;
855 $.widget("ui.mouse", {
858 cancel: 'input,textarea,button,select,option',
862 _mouseInit: function() {
866 .bind('mousedown.'+this.widgetName, function(event) {
867 return that._mouseDown(event);
869 .bind('click.'+this.widgetName, function(event) {
870 if (true === $.data(event.target, that.widgetName + '.preventClickEvent')) {
871 $.removeData(event.target, that.widgetName + '.preventClickEvent');
872 event.stopImmediatePropagation();
877 this.started = false;
880 // TODO: make sure destroying one instance of mouse doesn't mess with
881 // other instances of mouse
882 _mouseDestroy: function() {
883 this.element.unbind('.'+this.widgetName);
884 if ( this._mouseMoveDelegate ) {
886 .unbind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
887 .unbind('mouseup.'+this.widgetName, this._mouseUpDelegate);
891 _mouseDown: function(event) {
892 // don't let more than one widget handle mouseStart
893 if( mouseHandled ) { return; }
895 // we may have missed mouseup (out of window)
896 (this._mouseStarted && this._mouseUp(event));
898 this._mouseDownEvent = event;
901 btnIsLeft = (event.which === 1),
902 // event.target.nodeName works around a bug in IE 8 with
903 // disabled inputs (#7620)
904 elIsCancel = (typeof this.options.cancel === "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false);
905 if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) {
909 this.mouseDelayMet = !this.options.delay;
910 if (!this.mouseDelayMet) {
911 this._mouseDelayTimer = setTimeout(function() {
912 that.mouseDelayMet = true;
913 }, this.options.delay);
916 if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
917 this._mouseStarted = (this._mouseStart(event) !== false);
918 if (!this._mouseStarted) {
919 event.preventDefault();
924 // Click event may never have fired (Gecko & Opera)
925 if (true === $.data(event.target, this.widgetName + '.preventClickEvent')) {
926 $.removeData(event.target, this.widgetName + '.preventClickEvent');
929 // these delegates are required to keep context
930 this._mouseMoveDelegate = function(event) {
931 return that._mouseMove(event);
933 this._mouseUpDelegate = function(event) {
934 return that._mouseUp(event);
937 .bind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
938 .bind('mouseup.'+this.widgetName, this._mouseUpDelegate);
940 event.preventDefault();
946 _mouseMove: function(event) {
947 // IE mouseup check - mouseup happened when mouse was out of window
948 if ($.ui.ie && !(document.documentMode >= 9) && !event.button) {
949 return this._mouseUp(event);
952 if (this._mouseStarted) {
953 this._mouseDrag(event);
954 return event.preventDefault();
957 if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
959 (this._mouseStart(this._mouseDownEvent, event) !== false);
960 (this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event));
963 return !this._mouseStarted;
966 _mouseUp: function(event) {
968 .unbind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
969 .unbind('mouseup.'+this.widgetName, this._mouseUpDelegate);
971 if (this._mouseStarted) {
972 this._mouseStarted = false;
974 if (event.target === this._mouseDownEvent.target) {
975 $.data(event.target, this.widgetName + '.preventClickEvent', true);
978 this._mouseStop(event);
984 _mouseDistanceMet: function(event) {
986 Math.abs(this._mouseDownEvent.pageX - event.pageX),
987 Math.abs(this._mouseDownEvent.pageY - event.pageY)
988 ) >= this.options.distance
992 _mouseDelayMet: function(event) {
993 return this.mouseDelayMet;
996 // These are placeholder methods, to be overriden by extending plugin
997 _mouseStart: function(event) {},
998 _mouseDrag: function(event) {},
999 _mouseStop: function(event) {},
1000 _mouseCapture: function(event) { return true; }
1004 (function( $, undefined ) {
1006 $.widget("ui.draggable", $.ui.mouse, {
1008 widgetEventPrefix: "drag",
1013 connectToSortable: false,
1022 refreshPositions: false,
1024 revertDuration: 500,
1027 scrollSensitivity: 20,
1035 _create: function() {
1037 if (this.options.helper == 'original' && !(/^(?:r|a|f)/).test(this.element.css("position")))
1038 this.element[0].style.position = 'relative';
1040 (this.options.addClasses && this.element.addClass("ui-draggable"));
1041 (this.options.disabled && this.element.addClass("ui-draggable-disabled"));
1047 _destroy: function() {
1048 this.element.removeClass( "ui-draggable ui-draggable-dragging ui-draggable-disabled" );
1049 this._mouseDestroy();
1052 _mouseCapture: function(event) {
1054 var o = this.options;
1056 // among others, prevent a drag on a resizable-handle
1057 if (this.helper || o.disabled || $(event.target).is('.ui-resizable-handle'))
1060 //Quit if we're not on a valid handle
1061 this.handle = this._getHandle(event);
1065 $(o.iframeFix === true ? "iframe" : o.iframeFix).each(function() {
1066 $('<div class="ui-draggable-iframeFix" style="background: #fff;"></div>')
1068 width: this.offsetWidth+"px", height: this.offsetHeight+"px",
1069 position: "absolute", opacity: "0.001", zIndex: 1000
1071 .css($(this).offset())
1079 _mouseStart: function(event) {
1081 var o = this.options;
1083 //Create and append the visible helper
1084 this.helper = this._createHelper(event);
1086 this.helper.addClass("ui-draggable-dragging");
1088 //Cache the helper size
1089 this._cacheHelperProportions();
1091 //If ddmanager is used for droppables, set the global draggable
1093 $.ui.ddmanager.current = this;
1096 * - Position generation -
1097 * This block generates everything position related - it's the core of draggables.
1100 //Cache the margins of the original element
1101 this._cacheMargins();
1103 //Store the helper's css position
1104 this.cssPosition = this.helper.css("position");
1105 this.scrollParent = this.helper.scrollParent();
1107 //The element's absolute position on the page minus margins
1108 this.offset = this.positionAbs = this.element.offset();
1110 top: this.offset.top - this.margins.top,
1111 left: this.offset.left - this.margins.left
1114 $.extend(this.offset, {
1115 click: { //Where the click happened, relative to the element
1116 left: event.pageX - this.offset.left,
1117 top: event.pageY - this.offset.top
1119 parent: this._getParentOffset(),
1120 relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
1123 //Generate the original position
1124 this.originalPosition = this.position = this._generatePosition(event);
1125 this.originalPageX = event.pageX;
1126 this.originalPageY = event.pageY;
1128 //Adjust the mouse offset relative to the helper if 'cursorAt' is supplied
1129 (o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
1131 //Set a containment if given in the options
1133 this._setContainment();
1135 //Trigger event + callbacks
1136 if(this._trigger("start", event) === false) {
1141 //Recache the helper size
1142 this._cacheHelperProportions();
1144 //Prepare the droppable offsets
1145 if ($.ui.ddmanager && !o.dropBehaviour)
1146 $.ui.ddmanager.prepareOffsets(this, event);
1149 this._mouseDrag(event, true); //Execute the drag once - this causes the helper not to be visible before getting its correct position
1151 //If the ddmanager is used for droppables, inform the manager that dragging has started (see #5003)
1152 if ( $.ui.ddmanager ) $.ui.ddmanager.dragStart(this, event);
1157 _mouseDrag: function(event, noPropagation) {
1159 //Compute the helpers position
1160 this.position = this._generatePosition(event);
1161 this.positionAbs = this._convertPositionTo("absolute");
1163 //Call plugins and callbacks and use the resulting position if something is returned
1164 if (!noPropagation) {
1165 var ui = this._uiHash();
1166 if(this._trigger('drag', event, ui) === false) {
1170 this.position = ui.position;
1173 if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px';
1174 if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px';
1175 if($.ui.ddmanager) $.ui.ddmanager.drag(this, event);
1180 _mouseStop: function(event) {
1182 //If we are using droppables, inform the manager about the drop
1183 var dropped = false;
1184 if ($.ui.ddmanager && !this.options.dropBehaviour)
1185 dropped = $.ui.ddmanager.drop(this, event);
1187 //if a drop comes from outside (a sortable)
1189 dropped = this.dropped;
1190 this.dropped = false;
1193 //if the original element is no longer in the DOM don't bother to continue (see #8269)
1194 var element = this.element[0], elementInDom = false;
1195 while ( element && (element = element.parentNode) ) {
1196 if (element == document ) {
1197 elementInDom = true;
1200 if ( !elementInDom && this.options.helper === "original" )
1203 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))) {
1205 $(this.helper).animate(this.originalPosition, parseInt(this.options.revertDuration, 10), function() {
1206 if(that._trigger("stop", event) !== false) {
1211 if(this._trigger("stop", event) !== false) {
1219 _mouseUp: function(event) {
1220 //Remove frame helpers
1221 $("div.ui-draggable-iframeFix").each(function() {
1222 this.parentNode.removeChild(this);
1225 //If the ddmanager is used for droppables, inform the manager that dragging has stopped (see #5003)
1226 if( $.ui.ddmanager ) $.ui.ddmanager.dragStop(this, event);
1228 return $.ui.mouse.prototype._mouseUp.call(this, event);
1231 cancel: function() {
1233 if(this.helper.is(".ui-draggable-dragging")) {
1243 _getHandle: function(event) {
1245 var handle = !this.options.handle || !$(this.options.handle, this.element).length ? true : false;
1246 $(this.options.handle, this.element)
1250 if(this == event.target) handle = true;
1257 _createHelper: function(event) {
1259 var o = this.options;
1260 var helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event])) : (o.helper == 'clone' ? this.element.clone().removeAttr('id') : this.element);
1262 if(!helper.parents('body').length)
1263 helper.appendTo((o.appendTo == 'parent' ? this.element[0].parentNode : o.appendTo));
1265 if(helper[0] != this.element[0] && !(/(fixed|absolute)/).test(helper.css("position")))
1266 helper.css("position", "absolute");
1272 _adjustOffsetFromHelper: function(obj) {
1273 if (typeof obj == 'string') {
1274 obj = obj.split(' ');
1276 if ($.isArray(obj)) {
1277 obj = {left: +obj[0], top: +obj[1] || 0};
1279 if ('left' in obj) {
1280 this.offset.click.left = obj.left + this.margins.left;
1282 if ('right' in obj) {
1283 this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
1286 this.offset.click.top = obj.top + this.margins.top;
1288 if ('bottom' in obj) {
1289 this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
1293 _getParentOffset: function() {
1295 //Get the offsetParent and cache its position
1296 this.offsetParent = this.helper.offsetParent();
1297 var po = this.offsetParent.offset();
1299 // This is a special case where we need to modify a offset calculated on start, since the following happened:
1300 // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
1301 // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
1302 // the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
1303 if(this.cssPosition == 'absolute' && this.scrollParent[0] != document && $.contains(this.scrollParent[0], this.offsetParent[0])) {
1304 po.left += this.scrollParent.scrollLeft();
1305 po.top += this.scrollParent.scrollTop();
1308 if((this.offsetParent[0] == document.body) //This needs to be actually done for all browsers, since pageX/pageY includes this information
1309 || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() == 'html' && $.ui.ie)) //Ugly IE fix
1310 po = { top: 0, left: 0 };
1313 top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
1314 left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
1319 _getRelativeOffset: function() {
1321 if(this.cssPosition == "relative") {
1322 var p = this.element.position();
1324 top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
1325 left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
1328 return { top: 0, left: 0 };
1333 _cacheMargins: function() {
1335 left: (parseInt(this.element.css("marginLeft"),10) || 0),
1336 top: (parseInt(this.element.css("marginTop"),10) || 0),
1337 right: (parseInt(this.element.css("marginRight"),10) || 0),
1338 bottom: (parseInt(this.element.css("marginBottom"),10) || 0)
1342 _cacheHelperProportions: function() {
1343 this.helperProportions = {
1344 width: this.helper.outerWidth(),
1345 height: this.helper.outerHeight()
1349 _setContainment: function() {
1351 var o = this.options;
1352 if(o.containment == 'parent') o.containment = this.helper[0].parentNode;
1353 if(o.containment == 'document' || o.containment == 'window') this.containment = [
1354 o.containment == 'document' ? 0 : $(window).scrollLeft() - this.offset.relative.left - this.offset.parent.left,
1355 o.containment == 'document' ? 0 : $(window).scrollTop() - this.offset.relative.top - this.offset.parent.top,
1356 (o.containment == 'document' ? 0 : $(window).scrollLeft()) + $(o.containment == 'document' ? document : window).width() - this.helperProportions.width - this.margins.left,
1357 (o.containment == 'document' ? 0 : $(window).scrollTop()) + ($(o.containment == 'document' ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top
1360 if(!(/^(document|window|parent)$/).test(o.containment) && o.containment.constructor != Array) {
1361 var c = $(o.containment);
1362 var ce = c[0]; if(!ce) return;
1363 var co = c.offset();
1364 var over = ($(ce).css("overflow") != 'hidden');
1366 this.containment = [
1367 (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0),
1368 (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0),
1369 (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,
1370 (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
1372 this.relative_container = c;
1374 } else if(o.containment.constructor == Array) {
1375 this.containment = o.containment;
1380 _convertPositionTo: function(d, pos) {
1382 if(!pos) pos = this.position;
1383 var mod = d == "absolute" ? 1 : -1;
1384 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);
1388 pos.top // The absolute mouse position
1389 + this.offset.relative.top * mod // Only for relative positioned nodes: Relative offset from element to offset parent
1390 + this.offset.parent.top * mod // The offsetParent's offset without borders (offset + border)
1391 - ( ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod)
1394 pos.left // The absolute mouse position
1395 + this.offset.relative.left * mod // Only for relative positioned nodes: Relative offset from element to offset parent
1396 + this.offset.parent.left * mod // The offsetParent's offset without borders (offset + border)
1397 - ( ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod)
1403 _generatePosition: function(event) {
1405 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);
1406 var pageX = event.pageX;
1407 var pageY = event.pageY;
1410 * - Position constraining -
1411 * Constrain the position to a mix of grid, containment.
1414 if(this.originalPosition) { //If we are not dragging yet, we won't check for options
1416 if(this.containment) {
1417 if (this.relative_container){
1418 var co = this.relative_container.offset();
1419 containment = [ this.containment[0] + co.left,
1420 this.containment[1] + co.top,
1421 this.containment[2] + co.left,
1422 this.containment[3] + co.top ];
1425 containment = this.containment;
1428 if(event.pageX - this.offset.click.left < containment[0]) pageX = containment[0] + this.offset.click.left;
1429 if(event.pageY - this.offset.click.top < containment[1]) pageY = containment[1] + this.offset.click.top;
1430 if(event.pageX - this.offset.click.left > containment[2]) pageX = containment[2] + this.offset.click.left;
1431 if(event.pageY - this.offset.click.top > containment[3]) pageY = containment[3] + this.offset.click.top;
1435 //Check for grid elements set to 0 to prevent divide by 0 error causing invalid argument errors in IE (see ticket #6950)
1436 var top = o.grid[1] ? this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1] : this.originalPageY;
1437 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;
1439 var left = o.grid[0] ? this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0] : this.originalPageX;
1440 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;
1447 pageY // The absolute mouse position
1448 - this.offset.click.top // Click offset (relative to the element)
1449 - this.offset.relative.top // Only for relative positioned nodes: Relative offset from element to offset parent
1450 - this.offset.parent.top // The offsetParent's offset without borders (offset + border)
1451 + ( ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ))
1454 pageX // The absolute mouse position
1455 - this.offset.click.left // Click offset (relative to the element)
1456 - this.offset.relative.left // Only for relative positioned nodes: Relative offset from element to offset parent
1457 - this.offset.parent.left // The offsetParent's offset without borders (offset + border)
1458 + ( ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ))
1464 _clear: function() {
1465 this.helper.removeClass("ui-draggable-dragging");
1466 if(this.helper[0] != this.element[0] && !this.cancelHelperRemoval) this.helper.remove();
1467 //if($.ui.ddmanager) $.ui.ddmanager.current = null;
1469 this.cancelHelperRemoval = false;
1472 // From now on bulk stuff - mainly helpers
1474 _trigger: function(type, event, ui) {
1475 ui = ui || this._uiHash();
1476 $.ui.plugin.call(this, type, [event, ui]);
1477 if(type == "drag") this.positionAbs = this._convertPositionTo("absolute"); //The absolute position has to be recalculated after plugins
1478 return $.Widget.prototype._trigger.call(this, type, event, ui);
1483 _uiHash: function(event) {
1485 helper: this.helper,
1486 position: this.position,
1487 originalPosition: this.originalPosition,
1488 offset: this.positionAbs
1494 $.ui.plugin.add("draggable", "connectToSortable", {
1495 start: function(event, ui) {
1497 var inst = $(this).data("draggable"), o = inst.options,
1498 uiSortable = $.extend({}, ui, { item: inst.element });
1499 inst.sortables = [];
1500 $(o.connectToSortable).each(function() {
1501 var sortable = $.data(this, 'sortable');
1502 if (sortable && !sortable.options.disabled) {
1503 inst.sortables.push({
1505 shouldRevert: sortable.options.revert
1507 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).
1508 sortable._trigger("activate", event, uiSortable);
1513 stop: function(event, ui) {
1515 //If we are still over the sortable, we fake the stop event of the sortable, but also remove helper
1516 var inst = $(this).data("draggable"),
1517 uiSortable = $.extend({}, ui, { item: inst.element });
1519 $.each(inst.sortables, function() {
1520 if(this.instance.isOver) {
1522 this.instance.isOver = 0;
1524 inst.cancelHelperRemoval = true; //Don't remove the helper in the draggable instance
1525 this.instance.cancelHelperRemoval = false; //Remove it in the sortable instance (so sortable plugins like revert still work)
1527 //The sortable revert is supported, and we have to set a temporary dropped variable on the draggable to support revert: 'valid/invalid'
1528 if(this.shouldRevert) this.instance.options.revert = true;
1530 //Trigger the stop of the sortable
1531 this.instance._mouseStop(event);
1533 this.instance.options.helper = this.instance.options._helper;
1535 //If the helper has been the original item, restore properties in the sortable
1536 if(inst.options.helper == 'original')
1537 this.instance.currentItem.css({ top: 'auto', left: 'auto' });
1540 this.instance.cancelHelperRemoval = false; //Remove the helper in the sortable instance
1541 this.instance._trigger("deactivate", event, uiSortable);
1547 drag: function(event, ui) {
1549 var inst = $(this).data("draggable"), that = this;
1551 var checkPos = function(o) {
1552 var dyClick = this.offset.click.top, dxClick = this.offset.click.left;
1553 var helperTop = this.positionAbs.top, helperLeft = this.positionAbs.left;
1554 var itemHeight = o.height, itemWidth = o.width;
1555 var itemTop = o.top, itemLeft = o.left;
1557 return $.ui.isOver(helperTop + dyClick, helperLeft + dxClick, itemTop, itemLeft, itemHeight, itemWidth);
1560 $.each(inst.sortables, function(i) {
1562 var innermostIntersecting = false;
1563 var thisSortable = this;
1564 //Copy over some variables to allow calling the sortable's native _intersectsWith
1565 this.instance.positionAbs = inst.positionAbs;
1566 this.instance.helperProportions = inst.helperProportions;
1567 this.instance.offset.click = inst.offset.click;
1569 if(this.instance._intersectsWith(this.instance.containerCache)) {
1570 innermostIntersecting = true;
1571 $.each(inst.sortables, function () {
1572 this.instance.positionAbs = inst.positionAbs;
1573 this.instance.helperProportions = inst.helperProportions;
1574 this.instance.offset.click = inst.offset.click;
1575 if (this != thisSortable
1576 && this.instance._intersectsWith(this.instance.containerCache)
1577 && $.ui.contains(thisSortable.instance.element[0], this.instance.element[0]))
1578 innermostIntersecting = false;
1579 return innermostIntersecting;
1584 if(innermostIntersecting) {
1585 //If it intersects, we use a little isOver variable and set it once, so our move-in stuff gets fired only once
1586 if(!this.instance.isOver) {
1588 this.instance.isOver = 1;
1589 //Now we fake the start of dragging for the sortable instance,
1590 //by cloning the list group item, appending it to the sortable and using it as inst.currentItem
1591 //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)
1592 this.instance.currentItem = $(that).clone().removeAttr('id').appendTo(this.instance.element).data("sortable-item", true);
1593 this.instance.options._helper = this.instance.options.helper; //Store helper option to later restore it
1594 this.instance.options.helper = function() { return ui.helper[0]; };
1596 event.target = this.instance.currentItem[0];
1597 this.instance._mouseCapture(event, true);
1598 this.instance._mouseStart(event, true, true);
1600 //Because the browser event is way off the new appended portlet, we modify a couple of variables to reflect the changes
1601 this.instance.offset.click.top = inst.offset.click.top;
1602 this.instance.offset.click.left = inst.offset.click.left;
1603 this.instance.offset.parent.left -= inst.offset.parent.left - this.instance.offset.parent.left;
1604 this.instance.offset.parent.top -= inst.offset.parent.top - this.instance.offset.parent.top;
1606 inst._trigger("toSortable", event);
1607 inst.dropped = this.instance.element; //draggable revert needs that
1608 //hack so receive/update callbacks work (mostly)
1609 inst.currentItem = inst.element;
1610 this.instance.fromOutside = inst;
1614 //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
1615 if(this.instance.currentItem) this.instance._mouseDrag(event);
1619 //If it doesn't intersect with the sortable, and it intersected before,
1620 //we fake the drag stop of the sortable, but make sure it doesn't remove the helper by using cancelHelperRemoval
1621 if(this.instance.isOver) {
1623 this.instance.isOver = 0;
1624 this.instance.cancelHelperRemoval = true;
1626 //Prevent reverting on this forced stop
1627 this.instance.options.revert = false;
1629 // The out event needs to be triggered independently
1630 this.instance._trigger('out', event, this.instance._uiHash(this.instance));
1632 this.instance._mouseStop(event, true);
1633 this.instance.options.helper = this.instance.options._helper;
1635 //Now we remove our currentItem, the list group clone again, and the placeholder, and animate the helper back to it's original size
1636 this.instance.currentItem.remove();
1637 if(this.instance.placeholder) this.instance.placeholder.remove();
1639 inst._trigger("fromSortable", event);
1640 inst.dropped = false; //draggable revert needs that
1650 $.ui.plugin.add("draggable", "cursor", {
1651 start: function(event, ui) {
1652 var t = $('body'), o = $(this).data('draggable').options;
1653 if (t.css("cursor")) o._cursor = t.css("cursor");
1654 t.css("cursor", o.cursor);
1656 stop: function(event, ui) {
1657 var o = $(this).data('draggable').options;
1658 if (o._cursor) $('body').css("cursor", o._cursor);
1662 $.ui.plugin.add("draggable", "opacity", {
1663 start: function(event, ui) {
1664 var t = $(ui.helper), o = $(this).data('draggable').options;
1665 if(t.css("opacity")) o._opacity = t.css("opacity");
1666 t.css('opacity', o.opacity);
1668 stop: function(event, ui) {
1669 var o = $(this).data('draggable').options;
1670 if(o._opacity) $(ui.helper).css('opacity', o._opacity);
1674 $.ui.plugin.add("draggable", "scroll", {
1675 start: function(event, ui) {
1676 var i = $(this).data("draggable");
1677 if(i.scrollParent[0] != document && i.scrollParent[0].tagName != 'HTML') i.overflowOffset = i.scrollParent.offset();
1679 drag: function(event, ui) {
1681 var i = $(this).data("draggable"), o = i.options, scrolled = false;
1683 if(i.scrollParent[0] != document && i.scrollParent[0].tagName != 'HTML') {
1685 if(!o.axis || o.axis != 'x') {
1686 if((i.overflowOffset.top + i.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity)
1687 i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop + o.scrollSpeed;
1688 else if(event.pageY - i.overflowOffset.top < o.scrollSensitivity)
1689 i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop - o.scrollSpeed;
1692 if(!o.axis || o.axis != 'y') {
1693 if((i.overflowOffset.left + i.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity)
1694 i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft + o.scrollSpeed;
1695 else if(event.pageX - i.overflowOffset.left < o.scrollSensitivity)
1696 i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft - o.scrollSpeed;
1701 if(!o.axis || o.axis != 'x') {
1702 if(event.pageY - $(document).scrollTop() < o.scrollSensitivity)
1703 scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
1704 else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity)
1705 scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
1708 if(!o.axis || o.axis != 'y') {
1709 if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity)
1710 scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
1711 else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity)
1712 scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
1717 if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour)
1718 $.ui.ddmanager.prepareOffsets(i, event);
1723 $.ui.plugin.add("draggable", "snap", {
1724 start: function(event, ui) {
1726 var i = $(this).data("draggable"), o = i.options;
1727 i.snapElements = [];
1729 $(o.snap.constructor != String ? ( o.snap.items || ':data(draggable)' ) : o.snap).each(function() {
1730 var $t = $(this); var $o = $t.offset();
1731 if(this != i.element[0]) i.snapElements.push({
1733 width: $t.outerWidth(), height: $t.outerHeight(),
1734 top: $o.top, left: $o.left
1739 drag: function(event, ui) {
1741 var inst = $(this).data("draggable"), o = inst.options;
1742 var d = o.snapTolerance;
1744 var x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width,
1745 y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height;
1747 for (var i = inst.snapElements.length - 1; i >= 0; i--){
1749 var l = inst.snapElements[i].left, r = l + inst.snapElements[i].width,
1750 t = inst.snapElements[i].top, b = t + inst.snapElements[i].height;
1752 //Yes, I know, this is insane ;)
1753 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))) {
1754 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 })));
1755 inst.snapElements[i].snapping = false;
1759 if(o.snapMode != 'inner') {
1760 var ts = Math.abs(t - y2) <= d;
1761 var bs = Math.abs(b - y1) <= d;
1762 var ls = Math.abs(l - x2) <= d;
1763 var rs = Math.abs(r - x1) <= d;
1764 if(ts) ui.position.top = inst._convertPositionTo("relative", { top: t - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
1765 if(bs) ui.position.top = inst._convertPositionTo("relative", { top: b, left: 0 }).top - inst.margins.top;
1766 if(ls) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l - inst.helperProportions.width }).left - inst.margins.left;
1767 if(rs) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r }).left - inst.margins.left;
1770 var first = (ts || bs || ls || rs);
1772 if(o.snapMode != 'outer') {
1773 var ts = Math.abs(t - y1) <= d;
1774 var bs = Math.abs(b - y2) <= d;
1775 var ls = Math.abs(l - x1) <= d;
1776 var rs = Math.abs(r - x2) <= d;
1777 if(ts) ui.position.top = inst._convertPositionTo("relative", { top: t, left: 0 }).top - inst.margins.top;
1778 if(bs) ui.position.top = inst._convertPositionTo("relative", { top: b - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
1779 if(ls) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l }).left - inst.margins.left;
1780 if(rs) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r - inst.helperProportions.width }).left - inst.margins.left;
1783 if(!inst.snapElements[i].snapping && (ts || bs || ls || rs || first))
1784 (inst.options.snap.snap && inst.options.snap.snap.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
1785 inst.snapElements[i].snapping = (ts || bs || ls || rs || first);
1792 $.ui.plugin.add("draggable", "stack", {
1793 start: function(event, ui) {
1795 var o = $(this).data("draggable").options;
1797 var group = $.makeArray($(o.stack)).sort(function(a,b) {
1798 return (parseInt($(a).css("zIndex"),10) || 0) - (parseInt($(b).css("zIndex"),10) || 0);
1800 if (!group.length) { return; }
1802 var min = parseInt(group[0].style.zIndex) || 0;
1803 $(group).each(function(i) {
1804 this.style.zIndex = min + i;
1807 this[0].style.zIndex = min + group.length;
1812 $.ui.plugin.add("draggable", "zIndex", {
1813 start: function(event, ui) {
1814 var t = $(ui.helper), o = $(this).data("draggable").options;
1815 if(t.css("zIndex")) o._zIndex = t.css("zIndex");
1816 t.css('zIndex', o.zIndex);
1818 stop: function(event, ui) {
1819 var o = $(this).data("draggable").options;
1820 if(o._zIndex) $(ui.helper).css('zIndex', o._zIndex);
1825 (function( $, undefined ) {
1827 $.widget("ui.droppable", {
1829 widgetEventPrefix: "drop",
1837 tolerance: 'intersect'
1839 _create: function() {
1841 var o = this.options, accept = o.accept;
1842 this.isover = 0; this.isout = 1;
1844 this.accept = $.isFunction(accept) ? accept : function(d) {
1845 return d.is(accept);
1848 //Store the droppable's proportions
1849 this.proportions = { width: this.element[0].offsetWidth, height: this.element[0].offsetHeight };
1851 // Add the reference and positions to the manager
1852 $.ui.ddmanager.droppables[o.scope] = $.ui.ddmanager.droppables[o.scope] || [];
1853 $.ui.ddmanager.droppables[o.scope].push(this);
1855 (o.addClasses && this.element.addClass("ui-droppable"));
1859 _destroy: function() {
1860 var drop = $.ui.ddmanager.droppables[this.options.scope];
1861 for ( var i = 0; i < drop.length; i++ )
1862 if ( drop[i] == this )
1865 this.element.removeClass("ui-droppable ui-droppable-disabled");
1868 _setOption: function(key, value) {
1870 if(key == 'accept') {
1871 this.accept = $.isFunction(value) ? value : function(d) {
1875 $.Widget.prototype._setOption.apply(this, arguments);
1878 _activate: function(event) {
1879 var draggable = $.ui.ddmanager.current;
1880 if(this.options.activeClass) this.element.addClass(this.options.activeClass);
1881 (draggable && this._trigger('activate', event, this.ui(draggable)));
1884 _deactivate: function(event) {
1885 var draggable = $.ui.ddmanager.current;
1886 if(this.options.activeClass) this.element.removeClass(this.options.activeClass);
1887 (draggable && this._trigger('deactivate', event, this.ui(draggable)));
1890 _over: function(event) {
1892 var draggable = $.ui.ddmanager.current;
1893 if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return; // Bail if draggable and droppable are same element
1895 if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
1896 if(this.options.hoverClass) this.element.addClass(this.options.hoverClass);
1897 this._trigger('over', event, this.ui(draggable));
1902 _out: function(event) {
1904 var draggable = $.ui.ddmanager.current;
1905 if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return; // Bail if draggable and droppable are same element
1907 if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
1908 if(this.options.hoverClass) this.element.removeClass(this.options.hoverClass);
1909 this._trigger('out', event, this.ui(draggable));
1914 _drop: function(event,custom) {
1916 var draggable = custom || $.ui.ddmanager.current;
1917 if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return false; // Bail if draggable and droppable are same element
1919 var childrenIntersection = false;
1920 this.element.find(":data(droppable)").not(".ui-draggable-dragging").each(function() {
1921 var inst = $.data(this, 'droppable');
1924 && !inst.options.disabled
1925 && inst.options.scope == draggable.options.scope
1926 && inst.accept.call(inst.element[0], (draggable.currentItem || draggable.element))
1927 && $.ui.intersect(draggable, $.extend(inst, { offset: inst.element.offset() }), inst.options.tolerance)
1928 ) { childrenIntersection = true; return false; }
1930 if(childrenIntersection) return false;
1932 if(this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
1933 if(this.options.activeClass) this.element.removeClass(this.options.activeClass);
1934 if(this.options.hoverClass) this.element.removeClass(this.options.hoverClass);
1935 this._trigger('drop', event, this.ui(draggable));
1936 return this.element;
1945 draggable: (c.currentItem || c.element),
1947 position: c.position,
1948 offset: c.positionAbs
1954 $.ui.intersect = function(draggable, droppable, toleranceMode) {
1956 if (!droppable.offset) return false;
1958 var x1 = (draggable.positionAbs || draggable.position.absolute).left, x2 = x1 + draggable.helperProportions.width,
1959 y1 = (draggable.positionAbs || draggable.position.absolute).top, y2 = y1 + draggable.helperProportions.height;
1960 var l = droppable.offset.left, r = l + droppable.proportions.width,
1961 t = droppable.offset.top, b = t + droppable.proportions.height;
1963 switch (toleranceMode) {
1965 return (l <= x1 && x2 <= r
1966 && t <= y1 && y2 <= b);
1969 return (l < x1 + (draggable.helperProportions.width / 2) // Right Half
1970 && x2 - (draggable.helperProportions.width / 2) < r // Left Half
1971 && t < y1 + (draggable.helperProportions.height / 2) // Bottom Half
1972 && y2 - (draggable.helperProportions.height / 2) < b ); // Top Half
1975 var draggableLeft = ((draggable.positionAbs || draggable.position.absolute).left + (draggable.clickOffset || draggable.offset.click).left),
1976 draggableTop = ((draggable.positionAbs || draggable.position.absolute).top + (draggable.clickOffset || draggable.offset.click).top),
1977 isOver = $.ui.isOver(draggableTop, draggableLeft, t, l, droppable.proportions.height, droppable.proportions.width);
1982 (y1 >= t && y1 <= b) || // Top edge touching
1983 (y2 >= t && y2 <= b) || // Bottom edge touching
1984 (y1 < t && y2 > b) // Surrounded vertically
1986 (x1 >= l && x1 <= r) || // Left edge touching
1987 (x2 >= l && x2 <= r) || // Right edge touching
1988 (x1 < l && x2 > r) // Surrounded horizontally
1999 This manager tracks offsets of draggables and droppables
2003 droppables: { 'default': [] },
2004 prepareOffsets: function(t, event) {
2006 var m = $.ui.ddmanager.droppables[t.options.scope] || [];
2007 var type = event ? event.type : null; // workaround for #2317
2008 var list = (t.currentItem || t.element).find(":data(droppable)").andSelf();
2010 droppablesLoop: for (var i = 0; i < m.length; i++) {
2012 if(m[i].options.disabled || (t && !m[i].accept.call(m[i].element[0],(t.currentItem || t.element)))) continue; //No disabled and non-accepted
2013 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
2014 m[i].visible = m[i].element.css("display") != "none"; if(!m[i].visible) continue; //If the element is not visible, continue
2016 if(type == "mousedown") m[i]._activate.call(m[i], event); //Activate the droppable if used directly from draggables
2018 m[i].offset = m[i].element.offset();
2019 m[i].proportions = { width: m[i].element[0].offsetWidth, height: m[i].element[0].offsetHeight };
2024 drop: function(draggable, event) {
2026 var dropped = false;
2027 $.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() {
2029 if(!this.options) return;
2030 if (!this.options.disabled && this.visible && $.ui.intersect(draggable, this, this.options.tolerance))
2031 dropped = this._drop.call(this, event) || dropped;
2033 if (!this.options.disabled && this.visible && this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
2034 this.isout = 1; this.isover = 0;
2035 this._deactivate.call(this, event);
2042 dragStart: function( draggable, event ) {
2043 //Listen for scrolling so that if the dragging causes scrolling the position of the droppables can be recalculated (see #5003)
2044 draggable.element.parentsUntil( "body" ).bind( "scroll.droppable", function() {
2045 if( !draggable.options.refreshPositions ) $.ui.ddmanager.prepareOffsets( draggable, event );
2048 drag: function(draggable, event) {
2050 //If you have a highly dynamic page, you might try this option. It renders positions every time you move the mouse.
2051 if(draggable.options.refreshPositions) $.ui.ddmanager.prepareOffsets(draggable, event);
2053 //Run through all droppables and check their positions based on specific tolerance options
2054 $.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() {
2056 if(this.options.disabled || this.greedyChild || !this.visible) return;
2057 var intersects = $.ui.intersect(draggable, this, this.options.tolerance);
2059 var c = !intersects && this.isover == 1 ? 'isout' : (intersects && this.isover == 0 ? 'isover' : null);
2063 if (this.options.greedy) {
2064 // find droppable parents with same scope
2065 var scope = this.options.scope;
2066 var parent = this.element.parents(':data(droppable)').filter(function () {
2067 return $.data(this, 'droppable').options.scope === scope;
2070 if (parent.length) {
2071 parentInstance = $.data(parent[0], 'droppable');
2072 parentInstance.greedyChild = (c == 'isover' ? 1 : 0);
2076 // we just moved into a greedy child
2077 if (parentInstance && c == 'isover') {
2078 parentInstance['isover'] = 0;
2079 parentInstance['isout'] = 1;
2080 parentInstance._out.call(parentInstance, event);
2083 this[c] = 1; this[c == 'isout' ? 'isover' : 'isout'] = 0;
2084 this[c == "isover" ? "_over" : "_out"].call(this, event);
2086 // we just moved out of a greedy child
2087 if (parentInstance && c == 'isout') {
2088 parentInstance['isout'] = 0;
2089 parentInstance['isover'] = 1;
2090 parentInstance._over.call(parentInstance, event);
2095 dragStop: function( draggable, event ) {
2096 draggable.element.parentsUntil( "body" ).unbind( "scroll.droppable" );
2097 //Call prepareOffsets one final time since IE does not fire return scroll events when overflow was caused by drag (see #5003)
2098 if( !draggable.options.refreshPositions ) $.ui.ddmanager.prepareOffsets( draggable, event );
2103 (function( $, undefined ) {
2105 $.widget("ui.resizable", $.ui.mouse, {
2107 widgetEventPrefix: "resize",
2111 animateDuration: "slow",
2112 animateEasing: "swing",
2126 _create: function() {
2128 var that = this, o = this.options;
2129 this.element.addClass("ui-resizable");
2132 _aspectRatio: !!(o.aspectRatio),
2133 aspectRatio: o.aspectRatio,
2134 originalElement: this.element,
2135 _proportionallyResizeElements: [],
2136 _helper: o.helper || o.ghost || o.animate ? o.helper || 'ui-resizable-helper' : null
2139 //Wrap the element if it cannot hold child nodes
2140 if(this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)) {
2142 //Create a wrapper element and set the wrapper to the new current internal element
2144 $('<div class="ui-wrapper" style="overflow: hidden;"></div>').css({
2145 position: this.element.css('position'),
2146 width: this.element.outerWidth(),
2147 height: this.element.outerHeight(),
2148 top: this.element.css('top'),
2149 left: this.element.css('left')
2153 //Overwrite the original this.element
2154 this.element = this.element.parent().data(
2155 "resizable", this.element.data('resizable')
2158 this.elementIsWrapper = true;
2160 //Move margins to the wrapper
2161 this.element.css({ marginLeft: this.originalElement.css("marginLeft"), marginTop: this.originalElement.css("marginTop"), marginRight: this.originalElement.css("marginRight"), marginBottom: this.originalElement.css("marginBottom") });
2162 this.originalElement.css({ marginLeft: 0, marginTop: 0, marginRight: 0, marginBottom: 0});
2164 //Prevent Safari textarea resize
2165 this.originalResizeStyle = this.originalElement.css('resize');
2166 this.originalElement.css('resize', 'none');
2168 //Push the actual element to our proportionallyResize internal array
2169 this._proportionallyResizeElements.push(this.originalElement.css({ position: 'static', zoom: 1, display: 'block' }));
2171 // avoid IE jump (hard set the margin)
2172 this.originalElement.css({ margin: this.originalElement.css('margin') });
2174 // fix handlers offset
2175 this._proportionallyResize();
2179 this.handles = o.handles || (!$('.ui-resizable-handle', this.element).length ? "e,s,se" : { n: '.ui-resizable-n', e: '.ui-resizable-e', s: '.ui-resizable-s', w: '.ui-resizable-w', se: '.ui-resizable-se', sw: '.ui-resizable-sw', ne: '.ui-resizable-ne', nw: '.ui-resizable-nw' });
2180 if(this.handles.constructor == String) {
2182 if(this.handles == 'all') this.handles = 'n,e,s,w,se,sw,ne,nw';
2183 var n = this.handles.split(","); this.handles = {};
2185 for(var i = 0; i < n.length; i++) {
2187 var handle = $.trim(n[i]), hname = 'ui-resizable-'+handle;
2188 var axis = $('<div class="ui-resizable-handle ' + hname + '"></div>');
2190 // Apply zIndex to all handles - see #7960
2191 axis.css({ zIndex: o.zIndex });
2193 //TODO : What's going on here?
2194 if ('se' == handle) {
2195 axis.addClass('ui-icon ui-icon-gripsmall-diagonal-se');
2198 //Insert into internal handles object and append to element
2199 this.handles[handle] = '.ui-resizable-'+handle;
2200 this.element.append(axis);
2205 this._renderAxis = function(target) {
2207 target = target || this.element;
2209 for(var i in this.handles) {
2211 if(this.handles[i].constructor == String)
2212 this.handles[i] = $(this.handles[i], this.element).show();
2214 //Apply pad to wrapper element, needed to fix axis position (textarea, inputs, scrolls)
2215 if (this.elementIsWrapper && this.originalElement[0].nodeName.match(/textarea|input|select|button/i)) {
2217 var axis = $(this.handles[i], this.element), padWrapper = 0;
2219 //Checking the correct pad and border
2220 padWrapper = /sw|ne|nw|se|n|s/.test(i) ? axis.outerHeight() : axis.outerWidth();
2222 //The padding type i have to apply...
2223 var padPos = [ 'padding',
2224 /ne|nw|n/.test(i) ? 'Top' :
2225 /se|sw|s/.test(i) ? 'Bottom' :
2226 /^e$/.test(i) ? 'Right' : 'Left' ].join("");
2228 target.css(padPos, padWrapper);
2230 this._proportionallyResize();
2234 //TODO: What's that good for? There's not anything to be executed left
2235 if(!$(this.handles[i]).length)
2241 //TODO: make renderAxis a prototype function
2242 this._renderAxis(this.element);
2244 this._handles = $('.ui-resizable-handle', this.element)
2245 .disableSelection();
2247 //Matching axis name
2248 this._handles.mouseover(function() {
2249 if (!that.resizing) {
2251 var axis = this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);
2252 //Axis, default = se
2253 that.axis = axis && axis[1] ? axis[1] : 'se';
2257 //If we want to auto hide the elements
2259 this._handles.hide();
2261 .addClass("ui-resizable-autohide")
2262 .mouseenter(function() {
2263 if (o.disabled) return;
2264 $(this).removeClass("ui-resizable-autohide");
2265 that._handles.show();
2267 .mouseleave(function(){
2268 if (o.disabled) return;
2269 if (!that.resizing) {
2270 $(this).addClass("ui-resizable-autohide");
2271 that._handles.hide();
2276 //Initialize the mouse interaction
2281 _destroy: function() {
2283 this._mouseDestroy();
2285 var _destroy = function(exp) {
2286 $(exp).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing")
2287 .removeData("resizable").removeData("ui-resizable").unbind(".resizable").find('.ui-resizable-handle').remove();
2290 //TODO: Unwrap at same DOM position
2291 if (this.elementIsWrapper) {
2292 _destroy(this.element);
2293 var wrapper = this.element;
2294 this.originalElement.css({
2295 position: wrapper.css('position'),
2296 width: wrapper.outerWidth(),
2297 height: wrapper.outerHeight(),
2298 top: wrapper.css('top'),
2299 left: wrapper.css('left')
2300 }).insertAfter( wrapper );
2304 this.originalElement.css('resize', this.originalResizeStyle);
2305 _destroy(this.originalElement);
2310 _mouseCapture: function(event) {
2312 for (var i in this.handles) {
2313 if ($(this.handles[i])[0] == event.target) {
2318 return !this.options.disabled && handle;
2321 _mouseStart: function(event) {
2323 var o = this.options, iniPos = this.element.position(), el = this.element;
2325 this.resizing = true;
2326 this.documentScroll = { top: $(document).scrollTop(), left: $(document).scrollLeft() };
2328 // bugfix for http://dev.jquery.com/ticket/1749
2329 if (el.is('.ui-draggable') || (/absolute/).test(el.css('position'))) {
2330 el.css({ position: 'absolute', top: iniPos.top, left: iniPos.left });
2333 this._renderProxy();
2335 var curleft = num(this.helper.css('left')), curtop = num(this.helper.css('top'));
2337 if (o.containment) {
2338 curleft += $(o.containment).scrollLeft() || 0;
2339 curtop += $(o.containment).scrollTop() || 0;
2342 //Store needed variables
2343 this.offset = this.helper.offset();
2344 this.position = { left: curleft, top: curtop };
2345 this.size = this._helper ? { width: el.outerWidth(), height: el.outerHeight() } : { width: el.width(), height: el.height() };
2346 this.originalSize = this._helper ? { width: el.outerWidth(), height: el.outerHeight() } : { width: el.width(), height: el.height() };
2347 this.originalPosition = { left: curleft, top: curtop };
2348 this.sizeDiff = { width: el.outerWidth() - el.width(), height: el.outerHeight() - el.height() };
2349 this.originalMousePosition = { left: event.pageX, top: event.pageY };
2352 this.aspectRatio = (typeof o.aspectRatio == 'number') ? o.aspectRatio : ((this.originalSize.width / this.originalSize.height) || 1);
2354 var cursor = $('.ui-resizable-' + this.axis).css('cursor');
2355 $('body').css('cursor', cursor == 'auto' ? this.axis + '-resize' : cursor);
2357 el.addClass("ui-resizable-resizing");
2358 this._propagate("start", event);
2362 _mouseDrag: function(event) {
2364 //Increase performance, avoid regex
2365 var el = this.helper, o = this.options, props = {},
2366 that = this, smp = this.originalMousePosition, a = this.axis;
2368 var dx = (event.pageX-smp.left)||0, dy = (event.pageY-smp.top)||0;
2369 var trigger = this._change[a];
2370 if (!trigger) return false;
2372 // Calculate the attrs that will be change
2373 var data = trigger.apply(this, [event, dx, dy]);
2375 // Put this in the mouseDrag handler since the user can start pressing shift while resizing
2376 this._updateVirtualBoundaries(event.shiftKey);
2377 if (this._aspectRatio || event.shiftKey)
2378 data = this._updateRatio(data, event);
2380 data = this._respectSize(data, event);
2382 // plugins callbacks need to be called first
2383 this._propagate("resize", event);
2386 top: this.position.top + "px", left: this.position.left + "px",
2387 width: this.size.width + "px", height: this.size.height + "px"
2390 if (!this._helper && this._proportionallyResizeElements.length)
2391 this._proportionallyResize();
2393 this._updateCache(data);
2395 // calling the user callback at the end
2396 this._trigger('resize', event, this.ui());
2401 _mouseStop: function(event) {
2403 this.resizing = false;
2404 var o = this.options, that = this;
2407 var pr = this._proportionallyResizeElements, ista = pr.length && (/textarea/i).test(pr[0].nodeName),
2408 soffseth = ista && $.ui.hasScroll(pr[0], 'left') /* TODO - jump height */ ? 0 : that.sizeDiff.height,
2409 soffsetw = ista ? 0 : that.sizeDiff.width;
2411 var s = { width: (that.helper.width() - soffsetw), height: (that.helper.height() - soffseth) },
2412 left = (parseInt(that.element.css('left'), 10) + (that.position.left - that.originalPosition.left)) || null,
2413 top = (parseInt(that.element.css('top'), 10) + (that.position.top - that.originalPosition.top)) || null;
2416 this.element.css($.extend(s, { top: top, left: left }));
2418 that.helper.height(that.size.height);
2419 that.helper.width(that.size.width);
2421 if (this._helper && !o.animate) this._proportionallyResize();
2424 $('body').css('cursor', 'auto');
2426 this.element.removeClass("ui-resizable-resizing");
2428 this._propagate("stop", event);
2430 if (this._helper) this.helper.remove();
2435 _updateVirtualBoundaries: function(forceAspectRatio) {
2436 var o = this.options, pMinWidth, pMaxWidth, pMinHeight, pMaxHeight, b;
2439 minWidth: isNumber(o.minWidth) ? o.minWidth : 0,
2440 maxWidth: isNumber(o.maxWidth) ? o.maxWidth : Infinity,
2441 minHeight: isNumber(o.minHeight) ? o.minHeight : 0,
2442 maxHeight: isNumber(o.maxHeight) ? o.maxHeight : Infinity
2445 if(this._aspectRatio || forceAspectRatio) {
2446 // We want to create an enclosing box whose aspect ration is the requested one
2447 // First, compute the "projected" size for each dimension based on the aspect ratio and other dimension
2448 pMinWidth = b.minHeight * this.aspectRatio;
2449 pMinHeight = b.minWidth / this.aspectRatio;
2450 pMaxWidth = b.maxHeight * this.aspectRatio;
2451 pMaxHeight = b.maxWidth / this.aspectRatio;
2453 if(pMinWidth > b.minWidth) b.minWidth = pMinWidth;
2454 if(pMinHeight > b.minHeight) b.minHeight = pMinHeight;
2455 if(pMaxWidth < b.maxWidth) b.maxWidth = pMaxWidth;
2456 if(pMaxHeight < b.maxHeight) b.maxHeight = pMaxHeight;
2458 this._vBoundaries = b;
2461 _updateCache: function(data) {
2462 var o = this.options;
2463 this.offset = this.helper.offset();
2464 if (isNumber(data.left)) this.position.left = data.left;
2465 if (isNumber(data.top)) this.position.top = data.top;
2466 if (isNumber(data.height)) this.size.height = data.height;
2467 if (isNumber(data.width)) this.size.width = data.width;
2470 _updateRatio: function(data, event) {
2472 var o = this.options, cpos = this.position, csize = this.size, a = this.axis;
2474 if (isNumber(data.height)) data.width = (data.height * this.aspectRatio);
2475 else if (isNumber(data.width)) data.height = (data.width / this.aspectRatio);
2478 data.left = cpos.left + (csize.width - data.width);
2482 data.top = cpos.top + (csize.height - data.height);
2483 data.left = cpos.left + (csize.width - data.width);
2489 _respectSize: function(data, event) {
2491 var el = this.helper, o = this._vBoundaries, pRatio = this._aspectRatio || event.shiftKey, a = this.axis,
2492 ismaxw = isNumber(data.width) && o.maxWidth && (o.maxWidth < data.width), ismaxh = isNumber(data.height) && o.maxHeight && (o.maxHeight < data.height),
2493 isminw = isNumber(data.width) && o.minWidth && (o.minWidth > data.width), isminh = isNumber(data.height) && o.minHeight && (o.minHeight > data.height);
2495 if (isminw) data.width = o.minWidth;
2496 if (isminh) data.height = o.minHeight;
2497 if (ismaxw) data.width = o.maxWidth;
2498 if (ismaxh) data.height = o.maxHeight;
2500 var dw = this.originalPosition.left + this.originalSize.width, dh = this.position.top + this.size.height;
2501 var cw = /sw|nw|w/.test(a), ch = /nw|ne|n/.test(a);
2503 if (isminw && cw) data.left = dw - o.minWidth;
2504 if (ismaxw && cw) data.left = dw - o.maxWidth;
2505 if (isminh && ch) data.top = dh - o.minHeight;
2506 if (ismaxh && ch) data.top = dh - o.maxHeight;
2508 // fixing jump error on top/left - bug #2330
2509 var isNotwh = !data.width && !data.height;
2510 if (isNotwh && !data.left && data.top) data.top = null;
2511 else if (isNotwh && !data.top && data.left) data.left = null;
2516 _proportionallyResize: function() {
2518 var o = this.options;
2519 if (!this._proportionallyResizeElements.length) return;
2520 var element = this.helper || this.element;
2522 for (var i=0; i < this._proportionallyResizeElements.length; i++) {
2524 var prel = this._proportionallyResizeElements[i];
2526 if (!this.borderDif) {
2527 var b = [prel.css('borderTopWidth'), prel.css('borderRightWidth'), prel.css('borderBottomWidth'), prel.css('borderLeftWidth')],
2528 p = [prel.css('paddingTop'), prel.css('paddingRight'), prel.css('paddingBottom'), prel.css('paddingLeft')];
2530 this.borderDif = $.map(b, function(v, i) {
2531 var border = parseInt(v,10)||0, padding = parseInt(p[i],10)||0;
2532 return border + padding;
2537 height: (element.height() - this.borderDif[0] - this.borderDif[2]) || 0,
2538 width: (element.width() - this.borderDif[1] - this.borderDif[3]) || 0
2545 _renderProxy: function() {
2547 var el = this.element, o = this.options;
2548 this.elementOffset = el.offset();
2552 this.helper = this.helper || $('<div style="overflow:hidden;"></div>');
2554 // fix ie6 offset TODO: This seems broken
2555 var ie6offset = ($.ui.ie6 ? 1 : 0),
2556 pxyoffset = ( $.ui.ie6 ? 2 : -1 );
2558 this.helper.addClass(this._helper).css({
2559 width: this.element.outerWidth() + pxyoffset,
2560 height: this.element.outerHeight() + pxyoffset,
2561 position: 'absolute',
2562 left: this.elementOffset.left - ie6offset +'px',
2563 top: this.elementOffset.top - ie6offset +'px',
2564 zIndex: ++o.zIndex //TODO: Don't modify option
2569 .disableSelection();
2572 this.helper = this.element;
2578 e: function(event, dx, dy) {
2579 return { width: this.originalSize.width + dx };
2581 w: function(event, dx, dy) {
2582 var o = this.options, cs = this.originalSize, sp = this.originalPosition;
2583 return { left: sp.left + dx, width: cs.width - dx };
2585 n: function(event, dx, dy) {
2586 var o = this.options, cs = this.originalSize, sp = this.originalPosition;
2587 return { top: sp.top + dy, height: cs.height - dy };
2589 s: function(event, dx, dy) {
2590 return { height: this.originalSize.height + dy };
2592 se: function(event, dx, dy) {
2593 return $.extend(this._change.s.apply(this, arguments), this._change.e.apply(this, [event, dx, dy]));
2595 sw: function(event, dx, dy) {
2596 return $.extend(this._change.s.apply(this, arguments), this._change.w.apply(this, [event, dx, dy]));
2598 ne: function(event, dx, dy) {
2599 return $.extend(this._change.n.apply(this, arguments), this._change.e.apply(this, [event, dx, dy]));
2601 nw: function(event, dx, dy) {
2602 return $.extend(this._change.n.apply(this, arguments), this._change.w.apply(this, [event, dx, dy]));
2606 _propagate: function(n, event) {
2607 $.ui.plugin.call(this, n, [event, this.ui()]);
2608 (n != "resize" && this._trigger(n, event, this.ui()));
2615 originalElement: this.originalElement,
2616 element: this.element,
2617 helper: this.helper,
2618 position: this.position,
2620 originalSize: this.originalSize,
2621 originalPosition: this.originalPosition
2628 * Resizable Extensions
2631 $.ui.plugin.add("resizable", "alsoResize", {
2633 start: function (event, ui) {
2634 var that = $(this).data("resizable"), o = that.options;
2636 var _store = function (exp) {
2637 $(exp).each(function() {
2639 el.data("resizable-alsoresize", {
2640 width: parseInt(el.width(), 10), height: parseInt(el.height(), 10),
2641 left: parseInt(el.css('left'), 10), top: parseInt(el.css('top'), 10)
2646 if (typeof(o.alsoResize) == 'object' && !o.alsoResize.parentNode) {
2647 if (o.alsoResize.length) { o.alsoResize = o.alsoResize[0]; _store(o.alsoResize); }
2648 else { $.each(o.alsoResize, function (exp) { _store(exp); }); }
2650 _store(o.alsoResize);
2654 resize: function (event, ui) {
2655 var that = $(this).data("resizable"), o = that.options, os = that.originalSize, op = that.originalPosition;
2658 height: (that.size.height - os.height) || 0, width: (that.size.width - os.width) || 0,
2659 top: (that.position.top - op.top) || 0, left: (that.position.left - op.left) || 0
2662 _alsoResize = function (exp, c) {
2663 $(exp).each(function() {
2664 var el = $(this), start = $(this).data("resizable-alsoresize"), style = {},
2665 css = c && c.length ? c : el.parents(ui.originalElement[0]).length ? ['width', 'height'] : ['width', 'height', 'top', 'left'];
2667 $.each(css, function (i, prop) {
2668 var sum = (start[prop]||0) + (delta[prop]||0);
2669 if (sum && sum >= 0)
2670 style[prop] = sum || null;
2677 if (typeof(o.alsoResize) == 'object' && !o.alsoResize.nodeType) {
2678 $.each(o.alsoResize, function (exp, c) { _alsoResize(exp, c); });
2680 _alsoResize(o.alsoResize);
2684 stop: function (event, ui) {
2685 $(this).removeData("resizable-alsoresize");
2689 $.ui.plugin.add("resizable", "animate", {
2691 stop: function(event, ui) {
2692 var that = $(this).data("resizable"), o = that.options;
2694 var pr = that._proportionallyResizeElements, ista = pr.length && (/textarea/i).test(pr[0].nodeName),
2695 soffseth = ista && $.ui.hasScroll(pr[0], 'left') /* TODO - jump height */ ? 0 : that.sizeDiff.height,
2696 soffsetw = ista ? 0 : that.sizeDiff.width;
2698 var style = { width: (that.size.width - soffsetw), height: (that.size.height - soffseth) },
2699 left = (parseInt(that.element.css('left'), 10) + (that.position.left - that.originalPosition.left)) || null,
2700 top = (parseInt(that.element.css('top'), 10) + (that.position.top - that.originalPosition.top)) || null;
2702 that.element.animate(
2703 $.extend(style, top && left ? { top: top, left: left } : {}), {
2704 duration: o.animateDuration,
2705 easing: o.animateEasing,
2709 width: parseInt(that.element.css('width'), 10),
2710 height: parseInt(that.element.css('height'), 10),
2711 top: parseInt(that.element.css('top'), 10),
2712 left: parseInt(that.element.css('left'), 10)
2715 if (pr && pr.length) $(pr[0]).css({ width: data.width, height: data.height });
2717 // propagating resize, and updating values for each animation step
2718 that._updateCache(data);
2719 that._propagate("resize", event);
2728 $.ui.plugin.add("resizable", "containment", {
2730 start: function(event, ui) {
2731 var that = $(this).data("resizable"), o = that.options, el = that.element;
2732 var oc = o.containment, ce = (oc instanceof $) ? oc.get(0) : (/parent/.test(oc)) ? el.parent().get(0) : oc;
2735 that.containerElement = $(ce);
2737 if (/document/.test(oc) || oc == document) {
2738 that.containerOffset = { left: 0, top: 0 };
2739 that.containerPosition = { left: 0, top: 0 };
2742 element: $(document), left: 0, top: 0,
2743 width: $(document).width(), height: $(document).height() || document.body.parentNode.scrollHeight
2747 // i'm a node, so compute top, left, right, bottom
2749 var element = $(ce), p = [];
2750 $([ "Top", "Right", "Left", "Bottom" ]).each(function(i, name) { p[i] = num(element.css("padding" + name)); });
2752 that.containerOffset = element.offset();
2753 that.containerPosition = element.position();
2754 that.containerSize = { height: (element.innerHeight() - p[3]), width: (element.innerWidth() - p[1]) };
2756 var co = that.containerOffset, ch = that.containerSize.height, cw = that.containerSize.width,
2757 width = ($.ui.hasScroll(ce, "left") ? ce.scrollWidth : cw ), height = ($.ui.hasScroll(ce) ? ce.scrollHeight : ch);
2760 element: ce, left: co.left, top: co.top, width: width, height: height
2765 resize: function(event, ui) {
2766 var that = $(this).data("resizable"), o = that.options,
2767 ps = that.containerSize, co = that.containerOffset, cs = that.size, cp = that.position,
2768 pRatio = that._aspectRatio || event.shiftKey, cop = { top:0, left:0 }, ce = that.containerElement;
2770 if (ce[0] != document && (/static/).test(ce.css('position'))) cop = co;
2772 if (cp.left < (that._helper ? co.left : 0)) {
2773 that.size.width = that.size.width + (that._helper ? (that.position.left - co.left) : (that.position.left - cop.left));
2774 if (pRatio) that.size.height = that.size.width / that.aspectRatio;
2775 that.position.left = o.helper ? co.left : 0;
2778 if (cp.top < (that._helper ? co.top : 0)) {
2779 that.size.height = that.size.height + (that._helper ? (that.position.top - co.top) : that.position.top);
2780 if (pRatio) that.size.width = that.size.height * that.aspectRatio;
2781 that.position.top = that._helper ? co.top : 0;
2784 that.offset.left = that.parentData.left+that.position.left;
2785 that.offset.top = that.parentData.top+that.position.top;
2787 var woset = Math.abs( (that._helper ? that.offset.left - cop.left : (that.offset.left - cop.left)) + that.sizeDiff.width ),
2788 hoset = Math.abs( (that._helper ? that.offset.top - cop.top : (that.offset.top - co.top)) + that.sizeDiff.height );
2790 var isParent = that.containerElement.get(0) == that.element.parent().get(0),
2791 isOffsetRelative = /relative|absolute/.test(that.containerElement.css('position'));
2793 if(isParent && isOffsetRelative) woset -= that.parentData.left;
2795 if (woset + that.size.width >= that.parentData.width) {
2796 that.size.width = that.parentData.width - woset;
2797 if (pRatio) that.size.height = that.size.width / that.aspectRatio;
2800 if (hoset + that.size.height >= that.parentData.height) {
2801 that.size.height = that.parentData.height - hoset;
2802 if (pRatio) that.size.width = that.size.height * that.aspectRatio;
2806 stop: function(event, ui){
2807 var that = $(this).data("resizable"), o = that.options, cp = that.position,
2808 co = that.containerOffset, cop = that.containerPosition, ce = that.containerElement;
2810 var helper = $(that.helper), ho = helper.offset(), w = helper.outerWidth() - that.sizeDiff.width, h = helper.outerHeight() - that.sizeDiff.height;
2812 if (that._helper && !o.animate && (/relative/).test(ce.css('position')))
2813 $(this).css({ left: ho.left - cop.left - co.left, width: w, height: h });
2815 if (that._helper && !o.animate && (/static/).test(ce.css('position')))
2816 $(this).css({ left: ho.left - cop.left - co.left, width: w, height: h });
2821 $.ui.plugin.add("resizable", "ghost", {
2823 start: function(event, ui) {
2825 var that = $(this).data("resizable"), o = that.options, cs = that.size;
2827 that.ghost = that.originalElement.clone();
2829 .css({ opacity: .25, display: 'block', position: 'relative', height: cs.height, width: cs.width, margin: 0, left: 0, top: 0 })
2830 .addClass('ui-resizable-ghost')
2831 .addClass(typeof o.ghost == 'string' ? o.ghost : '');
2833 that.ghost.appendTo(that.helper);
2837 resize: function(event, ui){
2838 var that = $(this).data("resizable"), o = that.options;
2839 if (that.ghost) that.ghost.css({ position: 'relative', height: that.size.height, width: that.size.width });
2842 stop: function(event, ui){
2843 var that = $(this).data("resizable"), o = that.options;
2844 if (that.ghost && that.helper) that.helper.get(0).removeChild(that.ghost.get(0));
2849 $.ui.plugin.add("resizable", "grid", {
2851 resize: function(event, ui) {
2852 var that = $(this).data("resizable"), o = that.options, cs = that.size, os = that.originalSize, op = that.originalPosition, a = that.axis, ratio = o._aspectRatio || event.shiftKey;
2853 o.grid = typeof o.grid == "number" ? [o.grid, o.grid] : o.grid;
2854 var ox = Math.round((cs.width - os.width) / (o.grid[0]||1)) * (o.grid[0]||1), oy = Math.round((cs.height - os.height) / (o.grid[1]||1)) * (o.grid[1]||1);
2856 if (/^(se|s|e)$/.test(a)) {
2857 that.size.width = os.width + ox;
2858 that.size.height = os.height + oy;
2860 else if (/^(ne)$/.test(a)) {
2861 that.size.width = os.width + ox;
2862 that.size.height = os.height + oy;
2863 that.position.top = op.top - oy;
2865 else if (/^(sw)$/.test(a)) {
2866 that.size.width = os.width + ox;
2867 that.size.height = os.height + oy;
2868 that.position.left = op.left - ox;
2871 that.size.width = os.width + ox;
2872 that.size.height = os.height + oy;
2873 that.position.top = op.top - oy;
2874 that.position.left = op.left - ox;
2880 var num = function(v) {
2881 return parseInt(v, 10) || 0;
2884 var isNumber = function(value) {
2885 return !isNaN(parseInt(value, 10));
2889 (function( $, undefined ) {
2891 $.widget("ui.selectable", $.ui.mouse, {
2900 _create: function() {
2903 this.element.addClass("ui-selectable");
2905 this.dragged = false;
2907 // cache selectee children based on filter
2909 this.refresh = function() {
2910 selectees = $(that.options.filter, that.element[0]);
2911 selectees.addClass("ui-selectee");
2912 selectees.each(function() {
2913 var $this = $(this);
2914 var pos = $this.offset();
2915 $.data(this, "selectable-item", {
2920 right: pos.left + $this.outerWidth(),
2921 bottom: pos.top + $this.outerHeight(),
2922 startselected: false,
2923 selected: $this.hasClass('ui-selected'),
2924 selecting: $this.hasClass('ui-selecting'),
2925 unselecting: $this.hasClass('ui-unselecting')
2931 this.selectees = selectees.addClass("ui-selectee");
2935 this.helper = $("<div class='ui-selectable-helper'></div>");
2938 _destroy: function() {
2940 .removeClass("ui-selectee")
2941 .removeData("selectable-item");
2943 .removeClass("ui-selectable ui-selectable-disabled");
2944 this._mouseDestroy();
2947 _mouseStart: function(event) {
2950 this.opos = [event.pageX, event.pageY];
2952 if (this.options.disabled)
2955 var options = this.options;
2957 this.selectees = $(options.filter, this.element[0]);
2959 this._trigger("start", event);
2961 $(options.appendTo).append(this.helper);
2962 // position helper (lasso)
2964 "left": event.clientX,
2965 "top": event.clientY,
2970 if (options.autoRefresh) {
2974 this.selectees.filter('.ui-selected').each(function() {
2975 var selectee = $.data(this, "selectable-item");
2976 selectee.startselected = true;
2977 if (!event.metaKey && !event.ctrlKey) {
2978 selectee.$element.removeClass('ui-selected');
2979 selectee.selected = false;
2980 selectee.$element.addClass('ui-unselecting');
2981 selectee.unselecting = true;
2982 // selectable UNSELECTING callback
2983 that._trigger("unselecting", event, {
2984 unselecting: selectee.element
2989 $(event.target).parents().andSelf().each(function() {
2990 var selectee = $.data(this, "selectable-item");
2992 var doSelect = (!event.metaKey && !event.ctrlKey) || !selectee.$element.hasClass('ui-selected');
2994 .removeClass(doSelect ? "ui-unselecting" : "ui-selected")
2995 .addClass(doSelect ? "ui-selecting" : "ui-unselecting");
2996 selectee.unselecting = !doSelect;
2997 selectee.selecting = doSelect;
2998 selectee.selected = doSelect;
2999 // selectable (UN)SELECTING callback
3001 that._trigger("selecting", event, {
3002 selecting: selectee.element
3005 that._trigger("unselecting", event, {
3006 unselecting: selectee.element
3015 _mouseDrag: function(event) {
3017 this.dragged = true;
3019 if (this.options.disabled)
3022 var options = this.options;
3024 var x1 = this.opos[0], y1 = this.opos[1], x2 = event.pageX, y2 = event.pageY;
3025 if (x1 > x2) { var tmp = x2; x2 = x1; x1 = tmp; }
3026 if (y1 > y2) { var tmp = y2; y2 = y1; y1 = tmp; }
3027 this.helper.css({left: x1, top: y1, width: x2-x1, height: y2-y1});
3029 this.selectees.each(function() {
3030 var selectee = $.data(this, "selectable-item");
3031 //prevent helper from being selected if appendTo: selectable
3032 if (!selectee || selectee.element == that.element[0])
3035 if (options.tolerance == 'touch') {
3036 hit = ( !(selectee.left > x2 || selectee.right < x1 || selectee.top > y2 || selectee.bottom < y1) );
3037 } else if (options.tolerance == 'fit') {
3038 hit = (selectee.left > x1 && selectee.right < x2 && selectee.top > y1 && selectee.bottom < y2);
3043 if (selectee.selected) {
3044 selectee.$element.removeClass('ui-selected');
3045 selectee.selected = false;
3047 if (selectee.unselecting) {
3048 selectee.$element.removeClass('ui-unselecting');
3049 selectee.unselecting = false;
3051 if (!selectee.selecting) {
3052 selectee.$element.addClass('ui-selecting');
3053 selectee.selecting = true;
3054 // selectable SELECTING callback
3055 that._trigger("selecting", event, {
3056 selecting: selectee.element
3061 if (selectee.selecting) {
3062 if ((event.metaKey || event.ctrlKey) && selectee.startselected) {
3063 selectee.$element.removeClass('ui-selecting');
3064 selectee.selecting = false;
3065 selectee.$element.addClass('ui-selected');
3066 selectee.selected = true;
3068 selectee.$element.removeClass('ui-selecting');
3069 selectee.selecting = false;
3070 if (selectee.startselected) {
3071 selectee.$element.addClass('ui-unselecting');
3072 selectee.unselecting = true;
3074 // selectable UNSELECTING callback
3075 that._trigger("unselecting", event, {
3076 unselecting: selectee.element
3080 if (selectee.selected) {
3081 if (!event.metaKey && !event.ctrlKey && !selectee.startselected) {
3082 selectee.$element.removeClass('ui-selected');
3083 selectee.selected = false;
3085 selectee.$element.addClass('ui-unselecting');
3086 selectee.unselecting = true;
3087 // selectable UNSELECTING callback
3088 that._trigger("unselecting", event, {
3089 unselecting: selectee.element
3099 _mouseStop: function(event) {
3102 this.dragged = false;
3104 var options = this.options;
3106 $('.ui-unselecting', this.element[0]).each(function() {
3107 var selectee = $.data(this, "selectable-item");
3108 selectee.$element.removeClass('ui-unselecting');
3109 selectee.unselecting = false;
3110 selectee.startselected = false;
3111 that._trigger("unselected", event, {
3112 unselected: selectee.element
3115 $('.ui-selecting', this.element[0]).each(function() {
3116 var selectee = $.data(this, "selectable-item");
3117 selectee.$element.removeClass('ui-selecting').addClass('ui-selected');
3118 selectee.selecting = false;
3119 selectee.selected = true;
3120 selectee.startselected = true;
3121 that._trigger("selected", event, {
3122 selected: selectee.element
3125 this._trigger("stop", event);
3127 this.helper.remove();
3135 (function( $, undefined ) {
3137 $.widget("ui.sortable", $.ui.mouse, {
3139 widgetEventPrefix: "sort",
3149 forcePlaceholderSize: false,
3150 forceHelperSize: false,
3159 scrollSensitivity: 20,
3162 tolerance: "intersect",
3165 _create: function() {
3167 var o = this.options;
3168 this.containerCache = {};
3169 this.element.addClass("ui-sortable");
3174 //Let's determine if the items are being displayed horizontally
3175 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;
3177 //Let's determine the parent's offset
3178 this.offset = this.element.offset();
3180 //Initialize mouse events for interaction
3188 _destroy: function() {
3190 .removeClass("ui-sortable ui-sortable-disabled");
3191 this._mouseDestroy();
3193 for ( var i = this.items.length - 1; i >= 0; i-- )
3194 this.items[i].item.removeData(this.widgetName + "-item");
3199 _setOption: function(key, value){
3200 if ( key === "disabled" ) {
3201 this.options[ key ] = value;
3203 this.widget().toggleClass( "ui-sortable-disabled", !!value );
3205 // Don't call widget base _setOption for disable as it adds ui-state-disabled class
3206 $.Widget.prototype._setOption.apply(this, arguments);
3210 _mouseCapture: function(event, overrideHandle) {
3213 if (this.reverting) {
3217 if(this.options.disabled || this.options.type == 'static') return false;
3219 //We have to refresh the items data once first
3220 this._refreshItems(event);
3222 //Find out if the clicked node (or one of its parents) is a actual item in this.items
3223 var currentItem = null, nodes = $(event.target).parents().each(function() {
3224 if($.data(this, that.widgetName + '-item') == that) {
3225 currentItem = $(this);
3229 if($.data(event.target, that.widgetName + '-item') == that) currentItem = $(event.target);
3231 if(!currentItem) return false;
3232 if(this.options.handle && !overrideHandle) {
3233 var validHandle = false;
3235 $(this.options.handle, currentItem).find("*").andSelf().each(function() { if(this == event.target) validHandle = true; });
3236 if(!validHandle) return false;
3239 this.currentItem = currentItem;
3240 this._removeCurrentsFromItems();
3245 _mouseStart: function(event, overrideHandle, noActivation) {
3247 var o = this.options;
3248 this.currentContainer = this;
3250 //We only need to call refreshPositions, because the refreshItems call has been moved to mouseCapture
3251 this.refreshPositions();
3253 //Create and append the visible helper
3254 this.helper = this._createHelper(event);
3256 //Cache the helper size
3257 this._cacheHelperProportions();
3260 * - Position generation -
3261 * This block generates everything position related - it's the core of draggables.
3264 //Cache the margins of the original element
3265 this._cacheMargins();
3267 //Get the next scrolling parent
3268 this.scrollParent = this.helper.scrollParent();
3270 //The element's absolute position on the page minus margins
3271 this.offset = this.currentItem.offset();
3273 top: this.offset.top - this.margins.top,
3274 left: this.offset.left - this.margins.left
3277 $.extend(this.offset, {
3278 click: { //Where the click happened, relative to the element
3279 left: event.pageX - this.offset.left,
3280 top: event.pageY - this.offset.top
3282 parent: this._getParentOffset(),
3283 relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
3286 // Only after we got the offset, we can change the helper's position to absolute
3287 // TODO: Still need to figure out a way to make relative sorting possible
3288 this.helper.css("position", "absolute");
3289 this.cssPosition = this.helper.css("position");
3291 //Generate the original position
3292 this.originalPosition = this._generatePosition(event);
3293 this.originalPageX = event.pageX;
3294 this.originalPageY = event.pageY;
3296 //Adjust the mouse offset relative to the helper if 'cursorAt' is supplied
3297 (o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
3299 //Cache the former DOM position
3300 this.domPosition = { prev: this.currentItem.prev()[0], parent: this.currentItem.parent()[0] };
3302 //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
3303 if(this.helper[0] != this.currentItem[0]) {
3304 this.currentItem.hide();
3307 //Create the placeholder
3308 this._createPlaceholder();
3310 //Set a containment if given in the options
3312 this._setContainment();
3314 if(o.cursor) { // cursor option
3315 if ($('body').css("cursor")) this._storedCursor = $('body').css("cursor");
3316 $('body').css("cursor", o.cursor);
3319 if(o.opacity) { // opacity option
3320 if (this.helper.css("opacity")) this._storedOpacity = this.helper.css("opacity");
3321 this.helper.css("opacity", o.opacity);
3324 if(o.zIndex) { // zIndex option
3325 if (this.helper.css("zIndex")) this._storedZIndex = this.helper.css("zIndex");
3326 this.helper.css("zIndex", o.zIndex);
3330 if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML')
3331 this.overflowOffset = this.scrollParent.offset();
3334 this._trigger("start", event, this._uiHash());
3336 //Recache the helper size
3337 if(!this._preserveHelperProportions)
3338 this._cacheHelperProportions();
3341 //Post 'activate' events to possible containers
3343 for (var i = this.containers.length - 1; i >= 0; i--) { this.containers[i]._trigger("activate", event, this._uiHash(this)); }
3346 //Prepare possible droppables
3348 $.ui.ddmanager.current = this;
3350 if ($.ui.ddmanager && !o.dropBehaviour)
3351 $.ui.ddmanager.prepareOffsets(this, event);
3353 this.dragging = true;
3355 this.helper.addClass("ui-sortable-helper");
3356 this._mouseDrag(event); //Execute the drag once - this causes the helper not to be visible before getting its correct position
3361 _mouseDrag: function(event) {
3363 //Compute the helpers position
3364 this.position = this._generatePosition(event);
3365 this.positionAbs = this._convertPositionTo("absolute");
3367 if (!this.lastPositionAbs) {
3368 this.lastPositionAbs = this.positionAbs;
3372 if(this.options.scroll) {
3373 var o = this.options, scrolled = false;
3374 if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML') {
3376 if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity)
3377 this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed;
3378 else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity)
3379 this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed;
3381 if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity)
3382 this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed;
3383 else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity)
3384 this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed;
3388 if(event.pageY - $(document).scrollTop() < o.scrollSensitivity)
3389 scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
3390 else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity)
3391 scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
3393 if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity)
3394 scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
3395 else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity)
3396 scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
3400 if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour)
3401 $.ui.ddmanager.prepareOffsets(this, event);
3404 //Regenerate the absolute position used for position checks
3405 this.positionAbs = this._convertPositionTo("absolute");
3407 //Set the helper position
3408 if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px';
3409 if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px';
3412 for (var i = this.items.length - 1; i >= 0; i--) {
3414 //Cache variables and intersection, continue if no intersection
3415 var item = this.items[i], itemElement = item.item[0], intersection = this._intersectsWithPointer(item);
3416 if (!intersection) continue;
3418 // Only put the placeholder inside the current Container, skip all
3419 // items form other containers. This works because when moving
3420 // an item from one container to another the
3421 // currentContainer is switched before the placeholder is moved.
3423 // Without this moving items in "sub-sortables" can cause the placeholder to jitter
3424 // beetween the outer and inner container.
3425 if (item.instance !== this.currentContainer) continue;
3427 if (itemElement != this.currentItem[0] //cannot intersect with itself
3428 && this.placeholder[intersection == 1 ? "next" : "prev"]()[0] != itemElement //no useless actions that have been done before
3429 && !$.contains(this.placeholder[0], itemElement) //no action if the item moved is the parent of the item checked
3430 && (this.options.type == 'semi-dynamic' ? !$.contains(this.element[0], itemElement) : true)
3431 //&& itemElement.parentNode == this.placeholder[0].parentNode // only rearrange items within the same container
3434 this.direction = intersection == 1 ? "down" : "up";
3436 if (this.options.tolerance == "pointer" || this._intersectsWithSides(item)) {
3437 this._rearrange(event, item);
3442 this._trigger("change", event, this._uiHash());
3447 //Post events to containers
3448 this._contactContainers(event);
3450 //Interconnect with droppables
3451 if($.ui.ddmanager) $.ui.ddmanager.drag(this, event);
3454 this._trigger('sort', event, this._uiHash());
3456 this.lastPositionAbs = this.positionAbs;
3461 _mouseStop: function(event, noPropagation) {
3465 //If we are using droppables, inform the manager about the drop
3466 if ($.ui.ddmanager && !this.options.dropBehaviour)
3467 $.ui.ddmanager.drop(this, event);
3469 if(this.options.revert) {
3471 var cur = this.placeholder.offset();
3473 this.reverting = true;
3475 $(this.helper).animate({
3476 left: cur.left - this.offset.parent.left - this.margins.left + (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollLeft),
3477 top: cur.top - this.offset.parent.top - this.margins.top + (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollTop)
3478 }, parseInt(this.options.revert, 10) || 500, function() {
3482 this._clear(event, noPropagation);
3489 cancel: function() {
3493 this._mouseUp({ target: null });
3495 if(this.options.helper == "original")
3496 this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
3498 this.currentItem.show();
3500 //Post deactivating events to containers
3501 for (var i = this.containers.length - 1; i >= 0; i--){
3502 this.containers[i]._trigger("deactivate", null, this._uiHash(this));
3503 if(this.containers[i].containerCache.over) {
3504 this.containers[i]._trigger("out", null, this._uiHash(this));
3505 this.containers[i].containerCache.over = 0;
3511 if (this.placeholder) {
3512 //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
3513 if(this.placeholder[0].parentNode) this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
3514 if(this.options.helper != "original" && this.helper && this.helper[0].parentNode) this.helper.remove();
3523 if(this.domPosition.prev) {
3524 $(this.domPosition.prev).after(this.currentItem);
3526 $(this.domPosition.parent).prepend(this.currentItem);
3534 serialize: function(o) {
3536 var items = this._getItemsAsjQuery(o && o.connected);
3537 var str = []; o = o || {};
3539 $(items).each(function() {
3540 var res = ($(o.item || this).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/));
3541 if(res) str.push((o.key || res[1]+'[]')+'='+(o.key && o.expression ? res[1] : res[2]));
3544 if(!str.length && o.key) {
3545 str.push(o.key + '=');
3548 return str.join('&');
3552 toArray: function(o) {
3554 var items = this._getItemsAsjQuery(o && o.connected);
3555 var ret = []; o = o || {};
3557 items.each(function() { ret.push($(o.item || this).attr(o.attribute || 'id') || ''); });
3562 /* Be careful with the following core functions */
3563 _intersectsWith: function(item) {
3565 var x1 = this.positionAbs.left,
3566 x2 = x1 + this.helperProportions.width,
3567 y1 = this.positionAbs.top,
3568 y2 = y1 + this.helperProportions.height;
3573 b = t + item.height;
3575 var dyClick = this.offset.click.top,
3576 dxClick = this.offset.click.left;
3578 var isOverElement = (y1 + dyClick) > t && (y1 + dyClick) < b && (x1 + dxClick) > l && (x1 + dxClick) < r;
3580 if( this.options.tolerance == "pointer"
3581 || this.options.forcePointerForContainers
3582 || (this.options.tolerance != "pointer" && this.helperProportions[this.floating ? 'width' : 'height'] > item[this.floating ? 'width' : 'height'])
3584 return isOverElement;
3587 return (l < x1 + (this.helperProportions.width / 2) // Right Half
3588 && x2 - (this.helperProportions.width / 2) < r // Left Half
3589 && t < y1 + (this.helperProportions.height / 2) // Bottom Half
3590 && y2 - (this.helperProportions.height / 2) < b ); // Top Half
3595 _intersectsWithPointer: function(item) {
3597 var isOverElementHeight = (this.options.axis === 'x') || $.ui.isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height),
3598 isOverElementWidth = (this.options.axis === 'y') || $.ui.isOverAxis(this.positionAbs.left + this.offset.click.left, item.left, item.width),
3599 isOverElement = isOverElementHeight && isOverElementWidth,
3600 verticalDirection = this._getDragVerticalDirection(),
3601 horizontalDirection = this._getDragHorizontalDirection();
3606 return this.floating ?
3607 ( ((horizontalDirection && horizontalDirection == "right") || verticalDirection == "down") ? 2 : 1 )
3608 : ( verticalDirection && (verticalDirection == "down" ? 2 : 1) );
3612 _intersectsWithSides: function(item) {
3614 var isOverBottomHalf = $.ui.isOverAxis(this.positionAbs.top + this.offset.click.top, item.top + (item.height/2), item.height),
3615 isOverRightHalf = $.ui.isOverAxis(this.positionAbs.left + this.offset.click.left, item.left + (item.width/2), item.width),
3616 verticalDirection = this._getDragVerticalDirection(),
3617 horizontalDirection = this._getDragHorizontalDirection();
3619 if (this.floating && horizontalDirection) {
3620 return ((horizontalDirection == "right" && isOverRightHalf) || (horizontalDirection == "left" && !isOverRightHalf));
3622 return verticalDirection && ((verticalDirection == "down" && isOverBottomHalf) || (verticalDirection == "up" && !isOverBottomHalf));
3627 _getDragVerticalDirection: function() {
3628 var delta = this.positionAbs.top - this.lastPositionAbs.top;
3629 return delta != 0 && (delta > 0 ? "down" : "up");
3632 _getDragHorizontalDirection: function() {
3633 var delta = this.positionAbs.left - this.lastPositionAbs.left;
3634 return delta != 0 && (delta > 0 ? "right" : "left");
3637 refresh: function(event) {
3638 this._refreshItems(event);
3639 this.refreshPositions();
3643 _connectWith: function() {
3644 var options = this.options;
3645 return options.connectWith.constructor == String
3646 ? [options.connectWith]
3647 : options.connectWith;
3650 _getItemsAsjQuery: function(connected) {
3654 var connectWith = this._connectWith();
3656 if(connectWith && connected) {
3657 for (var i = connectWith.length - 1; i >= 0; i--){
3658 var cur = $(connectWith[i]);
3659 for (var j = cur.length - 1; j >= 0; j--){
3660 var inst = $.data(cur[j], this.widgetName);
3661 if(inst && inst != this && !inst.options.disabled) {
3662 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]);
3668 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]);
3670 for (var i = queries.length - 1; i >= 0; i--){
3671 queries[i][0].each(function() {
3680 _removeCurrentsFromItems: function() {
3682 var list = this.currentItem.find(":data(" + this.widgetName + "-item)");
3684 this.items = $.grep(this.items, function (item) {
3685 for (var j=0; j < list.length; j++) {
3686 if(list[j] == item.item[0])
3694 _refreshItems: function(event) {
3697 this.containers = [this];
3698 var items = this.items;
3699 var queries = [[$.isFunction(this.options.items) ? this.options.items.call(this.element[0], event, { item: this.currentItem }) : $(this.options.items, this.element), this]];
3700 var connectWith = this._connectWith();
3702 if(connectWith && this.ready) { //Shouldn't be run the first time through due to massive slow-down
3703 for (var i = connectWith.length - 1; i >= 0; i--){
3704 var cur = $(connectWith[i]);
3705 for (var j = cur.length - 1; j >= 0; j--){
3706 var inst = $.data(cur[j], this.widgetName);
3707 if(inst && inst != this && !inst.options.disabled) {
3708 queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element[0], event, { item: this.currentItem }) : $(inst.options.items, inst.element), inst]);
3709 this.containers.push(inst);
3715 for (var i = queries.length - 1; i >= 0; i--) {
3716 var targetData = queries[i][1];
3717 var _queries = queries[i][0];
3719 for (var j=0, queriesLength = _queries.length; j < queriesLength; j++) {
3720 var item = $(_queries[j]);
3722 item.data(this.widgetName + '-item', targetData); // Data for target checking (mouse manager)
3726 instance: targetData,
3727 width: 0, height: 0,
3735 refreshPositions: function(fast) {
3737 //This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change
3738 if(this.offsetParent && this.helper) {
3739 this.offset.parent = this._getParentOffset();
3742 for (var i = this.items.length - 1; i >= 0; i--){
3743 var item = this.items[i];
3745 //We ignore calculating positions of all connected containers when we're not over them
3746 if(item.instance != this.currentContainer && this.currentContainer && item.item[0] != this.currentItem[0])
3749 var t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item;
3752 item.width = t.outerWidth();
3753 item.height = t.outerHeight();
3761 if(this.options.custom && this.options.custom.refreshContainers) {
3762 this.options.custom.refreshContainers.call(this);
3764 for (var i = this.containers.length - 1; i >= 0; i--){
3765 var p = this.containers[i].element.offset();
3766 this.containers[i].containerCache.left = p.left;
3767 this.containers[i].containerCache.top = p.top;
3768 this.containers[i].containerCache.width = this.containers[i].element.outerWidth();
3769 this.containers[i].containerCache.height = this.containers[i].element.outerHeight();
3776 _createPlaceholder: function(that) {
3777 that = that || this;
3778 var o = that.options;
3780 if(!o.placeholder || o.placeholder.constructor == String) {
3781 var className = o.placeholder;
3783 element: function() {
3785 var el = $(document.createElement(that.currentItem[0].nodeName))
3786 .addClass(className || that.currentItem[0].className+" ui-sortable-placeholder")
3787 .removeClass("ui-sortable-helper")[0];
3790 el.style.visibility = "hidden";
3794 update: function(container, p) {
3796 // 1. If a className is set as 'placeholder option, we don't force sizes - the class is responsible for that
3797 // 2. The option 'forcePlaceholderSize can be enabled to force it even if a class name is specified
3798 if(className && !o.forcePlaceholderSize) return;
3800 //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
3801 if(!p.height()) { p.height(that.currentItem.innerHeight() - parseInt(that.currentItem.css('paddingTop')||0, 10) - parseInt(that.currentItem.css('paddingBottom')||0, 10)); };
3802 if(!p.width()) { p.width(that.currentItem.innerWidth() - parseInt(that.currentItem.css('paddingLeft')||0, 10) - parseInt(that.currentItem.css('paddingRight')||0, 10)); };
3807 //Create the placeholder
3808 that.placeholder = $(o.placeholder.element.call(that.element, that.currentItem));
3810 //Append it after the actual current item
3811 that.currentItem.after(that.placeholder);
3813 //Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317)
3814 o.placeholder.update(that, that.placeholder);
3818 _contactContainers: function(event) {
3820 // get innermost container that intersects with item
3821 var innermostContainer = null, innermostIndex = null;
3824 for (var i = this.containers.length - 1; i >= 0; i--){
3826 // never consider a container that's located within the item itself
3827 if($.contains(this.currentItem[0], this.containers[i].element[0]))
3830 if(this._intersectsWith(this.containers[i].containerCache)) {
3832 // if we've already found a container and it's more "inner" than this, then continue
3833 if(innermostContainer && $.contains(this.containers[i].element[0], innermostContainer.element[0]))
3836 innermostContainer = this.containers[i];
3840 // container doesn't intersect. trigger "out" event if necessary
3841 if(this.containers[i].containerCache.over) {
3842 this.containers[i]._trigger("out", event, this._uiHash(this));
3843 this.containers[i].containerCache.over = 0;
3849 // if no intersecting containers found, return
3850 if(!innermostContainer) return;
3852 // move the item into the container if it's not there already
3853 if(this.containers.length === 1) {
3854 this.containers[innermostIndex]._trigger("over", event, this._uiHash(this));
3855 this.containers[innermostIndex].containerCache.over = 1;
3858 //When entering a new container, we will find the item with the least distance and append our item near it
3859 var dist = 10000; var itemWithLeastDistance = null;
3860 var posProperty = this.containers[innermostIndex].floating ? 'left' : 'top';
3861 var sizeProperty = this.containers[innermostIndex].floating ? 'width' : 'height';
3862 var base = this.positionAbs[posProperty] + this.offset.click[posProperty];
3863 for (var j = this.items.length - 1; j >= 0; j--) {
3864 if(!$.contains(this.containers[innermostIndex].element[0], this.items[j].item[0])) continue;
3865 if(this.items[j].item[0] == this.currentItem[0]) continue;
3866 var cur = this.items[j].item.offset()[posProperty];
3867 var nearBottom = false;
3868 if(Math.abs(cur - base) > Math.abs(cur + this.items[j][sizeProperty] - base)){
3870 cur += this.items[j][sizeProperty];
3873 if(Math.abs(cur - base) < dist) {
3874 dist = Math.abs(cur - base); itemWithLeastDistance = this.items[j];
3875 this.direction = nearBottom ? "up": "down";
3879 if(!itemWithLeastDistance && !this.options.dropOnEmpty) //Check if dropOnEmpty is enabled
3882 this.currentContainer = this.containers[innermostIndex];
3883 itemWithLeastDistance ? this._rearrange(event, itemWithLeastDistance, null, true) : this._rearrange(event, null, this.containers[innermostIndex].element, true);
3884 this._trigger("change", event, this._uiHash());
3885 this.containers[innermostIndex]._trigger("change", event, this._uiHash(this));
3887 //Update the placeholder
3888 this.options.placeholder.update(this.currentContainer, this.placeholder);
3890 this.containers[innermostIndex]._trigger("over", event, this._uiHash(this));
3891 this.containers[innermostIndex].containerCache.over = 1;
3897 _createHelper: function(event) {
3899 var o = this.options;
3900 var helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event, this.currentItem])) : (o.helper == 'clone' ? this.currentItem.clone() : this.currentItem);
3902 if(!helper.parents('body').length) //Add the helper to the DOM if that didn't happen already
3903 $(o.appendTo != 'parent' ? o.appendTo : this.currentItem[0].parentNode)[0].appendChild(helper[0]);
3905 if(helper[0] == this.currentItem[0])
3906 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") };
3908 if(helper[0].style.width == '' || o.forceHelperSize) helper.width(this.currentItem.width());
3909 if(helper[0].style.height == '' || o.forceHelperSize) helper.height(this.currentItem.height());
3915 _adjustOffsetFromHelper: function(obj) {
3916 if (typeof obj == 'string') {
3917 obj = obj.split(' ');
3919 if ($.isArray(obj)) {
3920 obj = {left: +obj[0], top: +obj[1] || 0};
3922 if ('left' in obj) {
3923 this.offset.click.left = obj.left + this.margins.left;
3925 if ('right' in obj) {
3926 this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
3929 this.offset.click.top = obj.top + this.margins.top;
3931 if ('bottom' in obj) {
3932 this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
3936 _getParentOffset: function() {
3939 //Get the offsetParent and cache its position
3940 this.offsetParent = this.helper.offsetParent();
3941 var po = this.offsetParent.offset();
3943 // This is a special case where we need to modify a offset calculated on start, since the following happened:
3944 // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
3945 // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
3946 // the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
3947 if(this.cssPosition == 'absolute' && this.scrollParent[0] != document && $.contains(this.scrollParent[0], this.offsetParent[0])) {
3948 po.left += this.scrollParent.scrollLeft();
3949 po.top += this.scrollParent.scrollTop();
3952 if((this.offsetParent[0] == document.body) //This needs to be actually done for all browsers, since pageX/pageY includes this information
3953 || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() == 'html' && $.ui.ie)) //Ugly IE fix
3954 po = { top: 0, left: 0 };
3957 top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
3958 left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
3963 _getRelativeOffset: function() {
3965 if(this.cssPosition == "relative") {
3966 var p = this.currentItem.position();
3968 top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
3969 left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
3972 return { top: 0, left: 0 };
3977 _cacheMargins: function() {
3979 left: (parseInt(this.currentItem.css("marginLeft"),10) || 0),
3980 top: (parseInt(this.currentItem.css("marginTop"),10) || 0)
3984 _cacheHelperProportions: function() {
3985 this.helperProportions = {
3986 width: this.helper.outerWidth(),
3987 height: this.helper.outerHeight()
3991 _setContainment: function() {
3993 var o = this.options;
3994 if(o.containment == 'parent') o.containment = this.helper[0].parentNode;
3995 if(o.containment == 'document' || o.containment == 'window') this.containment = [
3996 0 - this.offset.relative.left - this.offset.parent.left,
3997 0 - this.offset.relative.top - this.offset.parent.top,
3998 $(o.containment == 'document' ? document : window).width() - this.helperProportions.width - this.margins.left,
3999 ($(o.containment == 'document' ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top
4002 if(!(/^(document|window|parent)$/).test(o.containment)) {
4003 var ce = $(o.containment)[0];
4004 var co = $(o.containment).offset();
4005 var over = ($(ce).css("overflow") != 'hidden');
4007 this.containment = [
4008 co.left + (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0) - this.margins.left,
4009 co.top + (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0) - this.margins.top,
4010 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,
4011 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
4017 _convertPositionTo: function(d, pos) {
4019 if(!pos) pos = this.position;
4020 var mod = d == "absolute" ? 1 : -1;
4021 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);
4025 pos.top // The absolute mouse position
4026 + this.offset.relative.top * mod // Only for relative positioned nodes: Relative offset from element to offset parent
4027 + this.offset.parent.top * mod // The offsetParent's offset without borders (offset + border)
4028 - ( ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod)
4031 pos.left // The absolute mouse position
4032 + this.offset.relative.left * mod // Only for relative positioned nodes: Relative offset from element to offset parent
4033 + this.offset.parent.left * mod // The offsetParent's offset without borders (offset + border)
4034 - ( ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod)
4040 _generatePosition: function(event) {
4042 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);
4044 // This is another very weird special case that only happens for relative elements:
4045 // 1. If the css position is relative
4046 // 2. and the scroll parent is the document or similar to the offset parent
4047 // we have to refresh the relative offset during the scroll so there are no jumps
4048 if(this.cssPosition == 'relative' && !(this.scrollParent[0] != document && this.scrollParent[0] != this.offsetParent[0])) {
4049 this.offset.relative = this._getRelativeOffset();
4052 var pageX = event.pageX;
4053 var pageY = event.pageY;
4056 * - Position constraining -
4057 * Constrain the position to a mix of grid, containment.
4060 if(this.originalPosition) { //If we are not dragging yet, we won't check for options
4062 if(this.containment) {
4063 if(event.pageX - this.offset.click.left < this.containment[0]) pageX = this.containment[0] + this.offset.click.left;
4064 if(event.pageY - this.offset.click.top < this.containment[1]) pageY = this.containment[1] + this.offset.click.top;
4065 if(event.pageX - this.offset.click.left > this.containment[2]) pageX = this.containment[2] + this.offset.click.left;
4066 if(event.pageY - this.offset.click.top > this.containment[3]) pageY = this.containment[3] + this.offset.click.top;
4070 var top = this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1];
4071 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;
4073 var left = this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0];
4074 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;
4081 pageY // The absolute mouse position
4082 - this.offset.click.top // Click offset (relative to the element)
4083 - this.offset.relative.top // Only for relative positioned nodes: Relative offset from element to offset parent
4084 - this.offset.parent.top // The offsetParent's offset without borders (offset + border)
4085 + ( ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ))
4088 pageX // The absolute mouse position
4089 - this.offset.click.left // Click offset (relative to the element)
4090 - this.offset.relative.left // Only for relative positioned nodes: Relative offset from element to offset parent
4091 - this.offset.parent.left // The offsetParent's offset without borders (offset + border)
4092 + ( ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ))
4098 _rearrange: function(event, i, a, hardRefresh) {
4100 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));
4102 //Various things done here to improve the performance:
4103 // 1. we create a setTimeout, that calls refreshPositions
4104 // 2. on the instance, we have a counter variable, that get's higher after every append
4105 // 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same
4106 // 4. this lets only the last addition to the timeout stack through
4107 this.counter = this.counter ? ++this.counter : 1;
4108 var counter = this.counter;
4110 this._delay(function() {
4111 if(counter == this.counter) this.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove
4116 _clear: function(event, noPropagation) {
4118 this.reverting = false;
4119 // We delay all events that have to be triggered to after the point where the placeholder has been removed and
4120 // everything else normalized again
4121 var delayedTriggers = [];
4123 // We first have to update the dom position of the actual currentItem
4124 // Note: don't do it if the current item is already removed (by a user), or it gets reappended (see #4088)
4125 if(!this._noFinalSort && this.currentItem.parent().length) this.placeholder.before(this.currentItem);
4126 this._noFinalSort = null;
4128 if(this.helper[0] == this.currentItem[0]) {
4129 for(var i in this._storedCSS) {
4130 if(this._storedCSS[i] == 'auto' || this._storedCSS[i] == 'static') this._storedCSS[i] = '';
4132 this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
4134 this.currentItem.show();
4137 if(this.fromOutside && !noPropagation) delayedTriggers.push(function(event) { this._trigger("receive", event, this._uiHash(this.fromOutside)); });
4138 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
4140 // Check if the items Container has Changed and trigger appropriate
4142 if (this !== this.currentContainer) {
4143 if(!noPropagation) {
4144 delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); });
4145 delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); }; }).call(this, this.currentContainer));
4146 delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this)); }; }).call(this, this.currentContainer));
4151 //Post events to containers
4152 for (var i = this.containers.length - 1; i >= 0; i--){
4153 if(!noPropagation) delayedTriggers.push((function(c) { return function(event) { c._trigger("deactivate", event, this._uiHash(this)); }; }).call(this, this.containers[i]));
4154 if(this.containers[i].containerCache.over) {
4155 delayedTriggers.push((function(c) { return function(event) { c._trigger("out", event, this._uiHash(this)); }; }).call(this, this.containers[i]));
4156 this.containers[i].containerCache.over = 0;
4160 //Do what was originally in plugins
4161 if(this._storedCursor) $('body').css("cursor", this._storedCursor); //Reset cursor
4162 if(this._storedOpacity) this.helper.css("opacity", this._storedOpacity); //Reset opacity
4163 if(this._storedZIndex) this.helper.css("zIndex", this._storedZIndex == 'auto' ? '' : this._storedZIndex); //Reset z-index
4165 this.dragging = false;
4166 if(this.cancelHelperRemoval) {
4167 if(!noPropagation) {
4168 this._trigger("beforeStop", event, this._uiHash());
4169 for (var i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); }; //Trigger all delayed events
4170 this._trigger("stop", event, this._uiHash());
4173 this.fromOutside = false;
4177 if(!noPropagation) this._trigger("beforeStop", event, this._uiHash());
4179 //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
4180 this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
4182 if(this.helper[0] != this.currentItem[0]) this.helper.remove(); this.helper = null;
4184 if(!noPropagation) {
4185 for (var i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); }; //Trigger all delayed events
4186 this._trigger("stop", event, this._uiHash());
4189 this.fromOutside = false;
4194 _trigger: function() {
4195 if ($.Widget.prototype._trigger.apply(this, arguments) === false) {
4200 _uiHash: function(_inst) {
4201 var inst = _inst || this;
4203 helper: inst.helper,
4204 placeholder: inst.placeholder || $([]),
4205 position: inst.position,
4206 originalPosition: inst.originalPosition,
4207 offset: inst.positionAbs,
4208 item: inst.currentItem,
4209 sender: _inst ? _inst.element : null
4216 (function( $, undefined ) {
4218 $.widget( "ui.progressbar", {
4227 _create: function() {
4229 .addClass( "ui-progressbar ui-widget ui-widget-content ui-corner-all" )
4231 role: "progressbar",
4232 "aria-valuemin": this.min,
4233 "aria-valuemax": this.options.max,
4234 "aria-valuenow": this._value()
4237 this.valueDiv = $( "<div class='ui-progressbar-value ui-widget-header ui-corner-left'></div>" )
4238 .appendTo( this.element );
4240 this.oldValue = this._value();
4241 this._refreshValue();
4244 _destroy: function() {
4246 .removeClass( "ui-progressbar ui-widget ui-widget-content ui-corner-all" )
4247 .removeAttr( "role" )
4248 .removeAttr( "aria-valuemin" )
4249 .removeAttr( "aria-valuemax" )
4250 .removeAttr( "aria-valuenow" );
4252 this.valueDiv.remove();
4255 value: function( newValue ) {
4256 if ( newValue === undefined ) {
4257 return this._value();
4260 this._setOption( "value", newValue );
4264 _setOption: function( key, value ) {
4265 if ( key === "value" ) {
4266 this.options.value = value;
4267 this._refreshValue();
4268 if ( this._value() === this.options.max ) {
4269 this._trigger( "complete" );
4273 this._super( key, value );
4276 _value: function() {
4277 var val = this.options.value;
4278 // normalize invalid value
4279 if ( typeof val !== "number" ) {
4282 return Math.min( this.options.max, Math.max( this.min, val ) );
4285 _percentage: function() {
4286 return 100 * this._value() / this.options.max;
4289 _refreshValue: function() {
4290 var value = this.value(),
4291 percentage = this._percentage();
4293 if ( this.oldValue !== value ) {
4294 this.oldValue = value;
4295 this._trigger( "change" );
4299 .toggle( value > this.min )
4300 .toggleClass( "ui-corner-right", value === this.options.max )
4301 .width( percentage.toFixed(0) + "%" );
4302 this.element.attr( "aria-valuenow", value );
4307 (function( $, undefined ) {
4312 function getNextTabId() {
4316 function isLocal( anchor ) {
4317 return anchor.hash.length > 1 &&
4318 anchor.href.replace( rhash, "" ) === location.href.replace( rhash, "" );
4321 $.widget( "ui.tabs", {
4328 heightStyle: "content",
4334 beforeActivate: null,
4339 _create: function() {
4341 options = this.options,
4342 active = options.active,
4343 locationHash = location.hash.substring( 1 );
4345 this.running = false;
4348 .addClass( "ui-tabs ui-widget ui-widget-content ui-corner-all" )
4349 .toggleClass( "ui-tabs-collapsible", options.collapsible )
4350 // Prevent users from focusing disabled tabs via click
4351 .delegate( ".ui-tabs-nav > li", "mousedown" + this.eventNamespace, function( event ) {
4352 if ( $( this ).is( ".ui-state-disabled" ) ) {
4353 event.preventDefault();
4357 // Preventing the default action in mousedown doesn't prevent IE
4358 // from focusing the element, so if the anchor gets focused, blur.
4359 // We don't have to worry about focusing the previously focused
4360 // element since clicking on a non-focusable element should focus
4362 .delegate( ".ui-tabs-anchor", "focus" + this.eventNamespace, function() {
4363 if ( $( this ).closest( "li" ).is( ".ui-state-disabled" ) ) {
4368 this._processTabs();
4370 if ( active === null ) {
4371 // check the fragment identifier in the URL
4372 if ( locationHash ) {
4373 this.tabs.each(function( i, tab ) {
4374 if ( $( tab ).attr( "aria-controls" ) === locationHash ) {
4381 // check for a tab marked active via a class
4382 if ( active === null ) {
4383 active = this.tabs.index( this.tabs.filter( ".ui-tabs-active" ) );
4386 // no active tab, set to false
4387 if ( active === null || active === -1 ) {
4388 active = this.tabs.length ? 0 : false;
4392 // handle numbers: negative, out of range
4393 if ( active !== false ) {
4394 active = this.tabs.index( this.tabs.eq( active ) );
4395 if ( active === -1 ) {
4396 active = options.collapsible ? false : 0;
4399 options.active = active;
4401 // don't allow collapsible: false and active: false
4402 if ( !options.collapsible && options.active === false && this.anchors.length ) {
4406 // Take disabling tabs via class attribute from HTML
4407 // into account and update option properly.
4408 if ( $.isArray( options.disabled ) ) {
4409 options.disabled = $.unique( options.disabled.concat(
4410 $.map( this.tabs.filter( ".ui-state-disabled" ), function( li ) {
4411 return that.tabs.index( li );
4416 // check for length avoids error when initializing empty list
4417 if ( this.options.active !== false && this.anchors.length ) {
4418 this.active = this._findActive( this.options.active );
4425 if ( this.active.length ) {
4426 this.load( options.active );
4430 _getCreateEventData: function() {
4433 panel: !this.active.length ? $() : this._getPanelForTab( this.active )
4437 _tabKeydown: function( event ) {
4438 var focusedTab = $( this.document[0].activeElement ).closest( "li" ),
4439 selectedIndex = this.tabs.index( focusedTab ),
4440 goingForward = true;
4442 if ( this._handlePageNav( event ) ) {
4446 switch ( event.keyCode ) {
4447 case $.ui.keyCode.RIGHT:
4448 case $.ui.keyCode.DOWN:
4451 case $.ui.keyCode.UP:
4452 case $.ui.keyCode.LEFT:
4453 goingForward = false;
4456 case $.ui.keyCode.END:
4457 selectedIndex = this.anchors.length - 1;
4459 case $.ui.keyCode.HOME:
4462 case $.ui.keyCode.SPACE:
4463 // Activate only, no collapsing
4464 event.preventDefault();
4465 clearTimeout( this.activating );
4466 this._activate( selectedIndex );
4468 case $.ui.keyCode.ENTER:
4469 // Toggle (cancel delayed activation, allow collapsing)
4470 event.preventDefault();
4471 clearTimeout( this.activating );
4472 // Determine if we should collapse or activate
4473 this._activate( selectedIndex === this.options.active ? false : selectedIndex );
4479 // Focus the appropriate tab, based on which key was pressed
4480 event.preventDefault();
4481 clearTimeout( this.activating );
4482 selectedIndex = this._focusNextTab( selectedIndex, goingForward );
4484 // Navigating with control key will prevent automatic activation
4485 if ( !event.ctrlKey ) {
4486 // Update aria-selected immediately so that AT think the tab is already selected.
4487 // Otherwise AT may confuse the user by stating that they need to activate the tab,
4488 // but the tab will already be activated by the time the announcement finishes.
4489 focusedTab.attr( "aria-selected", "false" );
4490 this.tabs.eq( selectedIndex ).attr( "aria-selected", "true" );
4492 this.activating = this._delay(function() {
4493 this.option( "active", selectedIndex );
4498 _panelKeydown: function( event ) {
4499 if ( this._handlePageNav( event ) ) {
4503 // Ctrl+up moves focus to the current tab
4504 if ( event.ctrlKey && event.keyCode === $.ui.keyCode.UP ) {
4505 event.preventDefault();
4506 this.active.focus();
4510 // Alt+page up/down moves focus to the previous/next tab (and activates)
4511 _handlePageNav: function( event ) {
4512 if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_UP ) {
4513 this._activate( this._focusNextTab( this.options.active - 1, false ) );
4516 if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_DOWN ) {
4517 this._activate( this._focusNextTab( this.options.active + 1, true ) );
4522 _findNextTab: function( index, goingForward ) {
4523 var lastTabIndex = this.tabs.length - 1;
4525 function constrain() {
4526 if ( index > lastTabIndex ) {
4530 index = lastTabIndex;
4535 while ( $.inArray( constrain(), this.options.disabled ) !== -1 ) {
4536 index = goingForward ? index + 1 : index - 1;
4542 _focusNextTab: function( index, goingForward ) {
4543 index = this._findNextTab( index, goingForward );
4544 this.tabs.eq( index ).focus();
4548 _setOption: function( key, value ) {
4549 if ( key === "active" ) {
4550 // _activate() will handle invalid values and update this.options
4551 this._activate( value );
4555 if ( key === "disabled" ) {
4556 // don't use the widget factory's disabled handling
4557 this._setupDisabled( value );
4561 this._super( key, value);
4563 if ( key === "collapsible" ) {
4564 this.element.toggleClass( "ui-tabs-collapsible", value );
4565 // Setting collapsible: false while collapsed; open first panel
4566 if ( !value && this.options.active === false ) {
4567 this._activate( 0 );
4571 if ( key === "event" ) {
4572 this._setupEvents( value );
4575 if ( key === "heightStyle" ) {
4576 this._setupHeightStyle( value );
4580 _tabId: function( tab ) {
4581 return tab.attr( "aria-controls" ) || "ui-tabs-" + getNextTabId();
4584 _sanitizeSelector: function( hash ) {
4585 return hash ? hash.replace( /[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g, "\\$&" ) : "";
4588 refresh: function() {
4589 var options = this.options,
4590 lis = this.tablist.children( ":has(a[href])" );
4592 // get disabled tabs from class attribute from HTML
4593 // this will get converted to a boolean if needed in _refresh()
4594 options.disabled = $.map( lis.filter( ".ui-state-disabled" ), function( tab ) {
4595 return lis.index( tab );
4598 this._processTabs();
4600 // was collapsed or no tabs
4601 if ( options.active === false || !this.anchors.length ) {
4602 options.active = false;
4604 // was active, but active tab is gone
4605 } else if ( this.active.length && !$.contains( this.tablist[ 0 ], this.active[ 0 ] ) ) {
4606 // all remaining tabs are disabled
4607 if ( this.tabs.length === options.disabled.length ) {
4608 options.active = false;
4610 // activate previous tab
4612 this._activate( this._findNextTab( Math.max( 0, options.active - 1 ), false ) );
4614 // was active, active tab still exists
4616 // make sure active index is correct
4617 options.active = this.tabs.index( this.active );
4623 _refresh: function() {
4624 this._setupDisabled( this.options.disabled );
4625 this._setupEvents( this.options.event );
4626 this._setupHeightStyle( this.options.heightStyle );
4628 this.tabs.not( this.active ).attr({
4629 "aria-selected": "false",
4632 this.panels.not( this._getPanelForTab( this.active ) )
4635 "aria-expanded": "false",
4636 "aria-hidden": "true"
4639 // Make sure one tab is in the tab order
4640 if ( !this.active.length ) {
4641 this.tabs.eq( 0 ).attr( "tabIndex", 0 );
4644 .addClass( "ui-tabs-active ui-state-active" )
4646 "aria-selected": "true",
4649 this._getPanelForTab( this.active )
4652 "aria-expanded": "true",
4653 "aria-hidden": "false"
4658 _processTabs: function() {
4661 this.tablist = this._getList()
4662 .addClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" )
4663 .attr( "role", "tablist" );
4665 this.tabs = this.tablist.find( "> li:has(a[href])" )
4666 .addClass( "ui-state-default ui-corner-top" )
4672 this.anchors = this.tabs.map(function() {
4673 return $( "a", this )[ 0 ];
4675 .addClass( "ui-tabs-anchor" )
4677 role: "presentation",
4683 this.anchors.each(function( i, anchor ) {
4684 var selector, panel, panelId,
4685 anchorId = $( anchor ).uniqueId().attr( "id" ),
4686 tab = $( anchor ).closest( "li" ),
4687 originalAriaControls = tab.attr( "aria-controls" );
4690 if ( isLocal( anchor ) ) {
4691 selector = anchor.hash;
4692 panel = that.element.find( that._sanitizeSelector( selector ) );
4695 panelId = that._tabId( tab );
4696 selector = "#" + panelId;
4697 panel = that.element.find( selector );
4698 if ( !panel.length ) {
4699 panel = that._createPanel( panelId );
4700 panel.insertAfter( that.panels[ i - 1 ] || that.tablist );
4702 panel.attr( "aria-live", "polite" );
4705 if ( panel.length) {
4706 that.panels = that.panels.add( panel );
4708 if ( originalAriaControls ) {
4709 tab.data( "ui-tabs-aria-controls", originalAriaControls );
4712 "aria-controls": selector.substring( 1 ),
4713 "aria-labelledby": anchorId
4715 panel.attr( "aria-labelledby", anchorId );
4719 .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )
4720 .attr( "role", "tabpanel" );
4723 // allow overriding how to find the list for rare usage scenarios (#7715)
4724 _getList: function() {
4725 return this.element.find( "ol,ul" ).eq( 0 );
4728 _createPanel: function( id ) {
4731 .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )
4732 .data( "ui-tabs-destroy", true );
4735 _setupDisabled: function( disabled ) {
4736 if ( $.isArray( disabled ) ) {
4737 if ( !disabled.length ) {
4739 } else if ( disabled.length === this.anchors.length ) {
4745 for ( var i = 0, li; ( li = this.tabs[ i ] ); i++ ) {
4746 if ( disabled === true || $.inArray( i, disabled ) !== -1 ) {
4748 .addClass( "ui-state-disabled" )
4749 .attr( "aria-disabled", "true" );
4752 .removeClass( "ui-state-disabled" )
4753 .removeAttr( "aria-disabled" );
4757 this.options.disabled = disabled;
4760 _setupEvents: function( event ) {
4762 click: function( event ) {
4763 event.preventDefault();
4767 $.each( event.split(" "), function( index, eventName ) {
4768 events[ eventName ] = "_eventHandler";
4772 this._off( this.anchors.add( this.tabs ).add( this.panels ) );
4773 this._on( this.anchors, events );
4774 this._on( this.tabs, { keydown: "_tabKeydown" } );
4775 this._on( this.panels, { keydown: "_panelKeydown" } );
4777 this._focusable( this.tabs );
4778 this._hoverable( this.tabs );
4781 _setupHeightStyle: function( heightStyle ) {
4782 var maxHeight, overflow,
4783 parent = this.element.parent();
4785 if ( heightStyle === "fill" ) {
4786 // IE 6 treats height like minHeight, so we need to turn off overflow
4787 // in order to get a reliable height
4788 // we use the minHeight support test because we assume that only
4789 // browsers that don't support minHeight will treat height as minHeight
4790 if ( !$.support.minHeight ) {
4791 overflow = parent.css( "overflow" );
4792 parent.css( "overflow", "hidden");
4794 maxHeight = parent.height();
4795 this.element.siblings( ":visible" ).each(function() {
4796 var elem = $( this ),
4797 position = elem.css( "position" );
4799 if ( position === "absolute" || position === "fixed" ) {
4802 maxHeight -= elem.outerHeight( true );
4805 parent.css( "overflow", overflow );
4808 this.element.children().not( this.panels ).each(function() {
4809 maxHeight -= $( this ).outerHeight( true );
4812 this.panels.each(function() {
4813 $( this ).height( Math.max( 0, maxHeight -
4814 $( this ).innerHeight() + $( this ).height() ) );
4816 .css( "overflow", "auto" );
4817 } else if ( heightStyle === "auto" ) {
4819 this.panels.each(function() {
4820 maxHeight = Math.max( maxHeight, $( this ).height( "" ).height() );
4821 }).height( maxHeight );
4825 _eventHandler: function( event ) {
4826 var options = this.options,
4827 active = this.active,
4828 anchor = $( event.currentTarget ),
4829 tab = anchor.closest( "li" ),
4830 clickedIsActive = tab[ 0 ] === active[ 0 ],
4831 collapsing = clickedIsActive && options.collapsible,
4832 toShow = collapsing ? $() : this._getPanelForTab( tab ),
4833 toHide = !active.length ? $() : this._getPanelForTab( active ),
4837 newTab: collapsing ? $() : tab,
4841 event.preventDefault();
4843 if ( tab.hasClass( "ui-state-disabled" ) ||
4844 // tab is already loading
4845 tab.hasClass( "ui-tabs-loading" ) ||
4846 // can't switch durning an animation
4848 // click on active header, but not collapsible
4849 ( clickedIsActive && !options.collapsible ) ||
4850 // allow canceling activation
4851 ( this._trigger( "beforeActivate", event, eventData ) === false ) ) {
4855 options.active = collapsing ? false : this.tabs.index( tab );
4857 this.active = clickedIsActive ? $() : tab;
4862 if ( !toHide.length && !toShow.length ) {
4863 $.error( "jQuery UI Tabs: Mismatching fragment identifier." );
4866 if ( toShow.length ) {
4867 this.load( this.tabs.index( tab ), event );
4869 this._toggle( event, eventData );
4872 // handles show/hide for selecting tabs
4873 _toggle: function( event, eventData ) {
4875 toShow = eventData.newPanel,
4876 toHide = eventData.oldPanel;
4878 this.running = true;
4880 function complete() {
4881 that.running = false;
4882 that._trigger( "activate", event, eventData );
4886 eventData.newTab.closest( "li" ).addClass( "ui-tabs-active ui-state-active" );
4888 if ( toShow.length && that.options.show ) {
4889 that._show( toShow, that.options.show, complete );
4896 // start out by hiding, then showing, then completing
4897 if ( toHide.length && this.options.hide ) {
4898 this._hide( toHide, this.options.hide, function() {
4899 eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" );
4903 eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" );
4909 "aria-expanded": "false",
4910 "aria-hidden": "true"
4912 eventData.oldTab.attr( "aria-selected", "false" );
4913 // If we're switching tabs, remove the old tab from the tab order.
4914 // If we're opening from collapsed state, remove the previous tab from the tab order.
4915 // If we're collapsing, then keep the collapsing tab in the tab order.
4916 if ( toShow.length && toHide.length ) {
4917 eventData.oldTab.attr( "tabIndex", -1 );
4918 } else if ( toShow.length ) {
4919 this.tabs.filter(function() {
4920 return $( this ).attr( "tabIndex" ) === 0;
4922 .attr( "tabIndex", -1 );
4926 "aria-expanded": "true",
4927 "aria-hidden": "false"
4929 eventData.newTab.attr({
4930 "aria-selected": "true",
4935 _activate: function( index ) {
4937 active = this._findActive( index );
4939 // trying to activate the already active panel
4940 if ( active[ 0 ] === this.active[ 0 ] ) {
4944 // trying to collapse, simulate a click on the current active header
4945 if ( !active.length ) {
4946 active = this.active;
4949 anchor = active.find( ".ui-tabs-anchor" )[ 0 ];
4950 this._eventHandler({
4952 currentTarget: anchor,
4953 preventDefault: $.noop
4957 _findActive: function( index ) {
4958 return index === false ? $() : this.tabs.eq( index );
4961 _getIndex: function( index ) {
4962 // meta-function to give users option to provide a href string instead of a numerical index.
4963 if ( typeof index === "string" ) {
4964 index = this.anchors.index( this.anchors.filter( "[href$='" + index + "']" ) );
4970 _destroy: function() {
4975 this.element.removeClass( "ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible" );
4978 .removeClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" )
4979 .removeAttr( "role" );
4982 .removeClass( "ui-tabs-anchor" )
4983 .removeAttr( "role" )
4984 .removeAttr( "tabIndex" )
4985 .removeData( "href.tabs" )
4986 .removeData( "load.tabs" )
4989 this.tabs.add( this.panels ).each(function() {
4990 if ( $.data( this, "ui-tabs-destroy" ) ) {
4994 .removeClass( "ui-state-default ui-state-active ui-state-disabled " +
4995 "ui-corner-top ui-corner-bottom ui-widget-content ui-tabs-active ui-tabs-panel" )
4996 .removeAttr( "tabIndex" )
4997 .removeAttr( "aria-live" )
4998 .removeAttr( "aria-busy" )
4999 .removeAttr( "aria-selected" )
5000 .removeAttr( "aria-labelledby" )
5001 .removeAttr( "aria-hidden" )
5002 .removeAttr( "aria-expanded" )
5003 .removeAttr( "role" );
5007 this.tabs.each(function() {
5009 prev = li.data( "ui-tabs-aria-controls" );
5011 li.attr( "aria-controls", prev );
5013 li.removeAttr( "aria-controls" );
5017 if ( this.options.heightStyle !== "content" ) {
5018 this.panels.css( "height", "" );
5022 enable: function( index ) {
5023 var disabled = this.options.disabled;
5024 if ( disabled === false ) {
5028 if ( index === undefined ) {
5031 index = this._getIndex( index );
5032 if ( $.isArray( disabled ) ) {
5033 disabled = $.map( disabled, function( num ) {
5034 return num !== index ? num : null;
5037 disabled = $.map( this.tabs, function( li, num ) {
5038 return num !== index ? num : null;
5042 this._setupDisabled( disabled );
5045 disable: function( index ) {
5046 var disabled = this.options.disabled;
5047 if ( disabled === true ) {
5051 if ( index === undefined ) {
5054 index = this._getIndex( index );
5055 if ( $.inArray( index, disabled ) !== -1 ) {
5058 if ( $.isArray( disabled ) ) {
5059 disabled = $.merge( [ index ], disabled ).sort();
5061 disabled = [ index ];
5064 this._setupDisabled( disabled );
5067 load: function( index, event ) {
5068 index = this._getIndex( index );
5070 tab = this.tabs.eq( index ),
5071 anchor = tab.find( ".ui-tabs-anchor" ),
5072 panel = this._getPanelForTab( tab ),
5079 if ( isLocal( anchor[ 0 ] ) ) {
5083 this.xhr = $.ajax( this._ajaxSettings( anchor, event, eventData ) );
5085 // support: jQuery <1.8
5086 // jQuery <1.8 returns false if the request is canceled in beforeSend,
5087 // but as of 1.8, $.ajax() always returns a jqXHR object.
5088 if ( this.xhr && this.xhr.statusText !== "canceled" ) {
5089 tab.addClass( "ui-tabs-loading" );
5090 panel.attr( "aria-busy", "true" );
5093 .success(function( response ) {
5094 // support: jQuery <1.8
5095 // http://bugs.jquery.com/ticket/11778
5096 setTimeout(function() {
5097 panel.html( response );
5098 that._trigger( "load", event, eventData );
5101 .complete(function( jqXHR, status ) {
5102 // support: jQuery <1.8
5103 // http://bugs.jquery.com/ticket/11778
5104 setTimeout(function() {
5105 if ( status === "abort" ) {
5106 that.panels.stop( false, true );
5109 tab.removeClass( "ui-tabs-loading" );
5110 panel.removeAttr( "aria-busy" );
5112 if ( jqXHR === that.xhr ) {
5120 // TODO: Remove this function in 1.10 when ajaxOptions is removed
5121 _ajaxSettings: function( anchor, event, eventData ) {
5124 url: anchor.attr( "href" ),
5125 beforeSend: function( jqXHR, settings ) {
5126 return that._trigger( "beforeLoad", event,
5127 $.extend( { jqXHR : jqXHR, ajaxSettings: settings }, eventData ) );
5132 _getPanelForTab: function( tab ) {
5133 var id = $( tab ).attr( "aria-controls" );
5134 return this.element.find( this._sanitizeSelector( "#" + id ) );
5139 if ( $.uiBackCompat !== false ) {
5141 // helper method for a lot of the back compat extensions
5142 $.ui.tabs.prototype._ui = function( tab, panel ) {
5146 index: this.anchors.index( tab )
5151 $.widget( "ui.tabs", $.ui.tabs, {
5152 url: function( index, url ) {
5153 this.anchors.eq( index ).attr( "href", url );
5157 // TODO: Remove _ajaxSettings() method when removing this extension
5158 // ajaxOptions and cache options
5159 $.widget( "ui.tabs", $.ui.tabs, {
5165 _create: function() {
5170 this._on({ tabsbeforeload: function( event, ui ) {
5171 // tab is already cached
5172 if ( $.data( ui.tab[ 0 ], "cache.tabs" ) ) {
5173 event.preventDefault();
5177 ui.jqXHR.success(function() {
5178 if ( that.options.cache ) {
5179 $.data( ui.tab[ 0 ], "cache.tabs", true );
5185 _ajaxSettings: function( anchor, event, ui ) {
5186 var ajaxOptions = this.options.ajaxOptions;
5187 return $.extend( {}, ajaxOptions, {
5188 error: function( xhr, status ) {
5190 // Passing index avoid a race condition when this method is
5191 // called after the user has selected another tab.
5192 // Pass the anchor that initiated this request allows
5193 // loadError to manipulate the tab content panel via $(a.hash)
5195 xhr, status, ui.tab.closest( "li" ).index(), ui.tab[ 0 ] );
5199 }, this._superApply( arguments ) );
5202 _setOption: function( key, value ) {
5203 // reset cache if switching from cached to not cached
5204 if ( key === "cache" && value === false ) {
5205 this.anchors.removeData( "cache.tabs" );
5207 this._super( key, value );
5210 _destroy: function() {
5211 this.anchors.removeData( "cache.tabs" );
5215 url: function( index ){
5216 this.anchors.eq( index ).removeData( "cache.tabs" );
5217 this._superApply( arguments );
5222 $.widget( "ui.tabs", $.ui.tabs, {
5231 $.widget( "ui.tabs", $.ui.tabs, {
5233 spinner: "<em>Loading…</em>"
5235 _create: function() {
5238 tabsbeforeload: function( event, ui ) {
5239 // Don't react to nested tabs or tabs that don't use a spinner
5240 if ( event.target !== this.element[ 0 ] ||
5241 !this.options.spinner ) {
5245 var span = ui.tab.find( "span" ),
5247 span.html( this.options.spinner );
5248 ui.jqXHR.complete(function() {
5256 // enable/disable events
5257 $.widget( "ui.tabs", $.ui.tabs, {
5263 enable: function( index ) {
5264 var options = this.options,
5267 if ( index && options.disabled === true ||
5268 ( $.isArray( options.disabled ) && $.inArray( index, options.disabled ) !== -1 ) ) {
5272 this._superApply( arguments );
5275 this._trigger( "enable", null, this._ui( this.anchors[ index ], this.panels[ index ] ) );
5279 disable: function( index ) {
5280 var options = this.options,
5283 if ( index && options.disabled === false ||
5284 ( $.isArray( options.disabled ) && $.inArray( index, options.disabled ) === -1 ) ) {
5288 this._superApply( arguments );
5291 this._trigger( "disable", null, this._ui( this.anchors[ index ], this.panels[ index ] ) );
5296 // add/remove methods and events
5297 $.widget( "ui.tabs", $.ui.tabs, {
5301 tabTemplate: "<li><a href='#{href}'><span>#{label}</span></a></li>"
5304 add: function( url, label, index ) {
5305 if ( index === undefined ) {
5306 index = this.anchors.length;
5309 var doInsertAfter, panel,
5310 options = this.options,
5311 li = $( options.tabTemplate
5312 .replace( /#\{href\}/g, url )
5313 .replace( /#\{label\}/g, label ) ),
5314 id = !url.indexOf( "#" ) ?
5315 url.replace( "#", "" ) :
5318 li.addClass( "ui-state-default ui-corner-top" ).data( "ui-tabs-destroy", true );
5319 li.attr( "aria-controls", id );
5321 doInsertAfter = index >= this.tabs.length;
5323 // try to find an existing element before creating a new one
5324 panel = this.element.find( "#" + id );
5325 if ( !panel.length ) {
5326 panel = this._createPanel( id );
5327 if ( doInsertAfter ) {
5329 panel.insertAfter( this.panels.eq( -1 ) );
5331 panel.appendTo( this.element );
5334 panel.insertBefore( this.panels[ index ] );
5337 panel.addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" ).hide();
5339 if ( doInsertAfter ) {
5340 li.appendTo( this.tablist );
5342 li.insertBefore( this.tabs[ index ] );
5345 options.disabled = $.map( options.disabled, function( n ) {
5346 return n >= index ? ++n : n;
5350 if ( this.tabs.length === 1 && options.active === false ) {
5351 this.option( "active", 0 );
5354 this._trigger( "add", null, this._ui( this.anchors[ index ], this.panels[ index ] ) );
5358 remove: function( index ) {
5359 index = this._getIndex( index );
5360 var options = this.options,
5361 tab = this.tabs.eq( index ).remove(),
5362 panel = this._getPanelForTab( tab ).remove();
5364 // If selected tab was removed focus tab to the right or
5365 // in case the last tab was removed the tab to the left.
5366 // We check for more than 2 tabs, because if there are only 2,
5367 // then when we remove this tab, there will only be one tab left
5368 // so we don't need to detect which tab to activate.
5369 if ( tab.hasClass( "ui-tabs-active" ) && this.anchors.length > 2 ) {
5370 this._activate( index + ( index + 1 < this.anchors.length ? 1 : -1 ) );
5373 options.disabled = $.map(
5374 $.grep( options.disabled, function( n ) {
5378 return n >= index ? --n : n;
5383 this._trigger( "remove", null, this._ui( tab.find( "a" )[ 0 ], panel[ 0 ] ) );
5389 $.widget( "ui.tabs", $.ui.tabs, {
5390 length: function() {
5391 return this.anchors.length;
5395 // panel ids (idPrefix option + title attribute)
5396 $.widget( "ui.tabs", $.ui.tabs, {
5398 idPrefix: "ui-tabs-"
5401 _tabId: function( tab ) {
5402 var a = tab.is( "li" ) ? tab.find( "a[href]" ) : tab;
5404 return $( a ).closest( "li" ).attr( "aria-controls" ) ||
5405 a.title && a.title.replace( /\s/g, "_" ).replace( /[^\w\u00c0-\uFFFF\-]/g, "" ) ||
5406 this.options.idPrefix + getNextTabId();
5410 // _createPanel method
5411 $.widget( "ui.tabs", $.ui.tabs, {
5413 panelTemplate: "<div></div>"
5416 _createPanel: function( id ) {
5417 return $( this.options.panelTemplate )
5419 .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )
5420 .data( "ui-tabs-destroy", true );
5425 $.widget( "ui.tabs", $.ui.tabs, {
5426 _create: function() {
5427 var options = this.options;
5428 if ( options.active === null && options.selected !== undefined ) {
5429 options.active = options.selected === -1 ? false : options.selected;
5432 options.selected = options.active;
5433 if ( options.selected === false ) {
5434 options.selected = -1;
5438 _setOption: function( key, value ) {
5439 if ( key !== "selected" ) {
5440 return this._super( key, value );
5443 var options = this.options;
5444 this._super( "active", value === -1 ? false : value );
5445 options.selected = options.active;
5446 if ( options.selected === false ) {
5447 options.selected = -1;
5451 _eventHandler: function() {
5452 this._superApply( arguments );
5453 this.options.selected = this.options.active;
5454 if ( this.options.selected === false ) {
5455 this.options.selected = -1;
5460 // show and select event
5461 $.widget( "ui.tabs", $.ui.tabs, {
5466 _create: function() {
5468 if ( this.options.active !== false ) {
5469 this._trigger( "show", null, this._ui(
5470 this.active.find( ".ui-tabs-anchor" )[ 0 ],
5471 this._getPanelForTab( this.active )[ 0 ] ) );
5474 _trigger: function( type, event, data ) {
5475 var ret = this._superApply( arguments );
5479 if ( type === "beforeActivate" && data.newTab.length ) {
5480 ret = this._super( "select", event, {
5481 tab: data.newTab.find( ".ui-tabs-anchor" )[ 0],
5482 panel: data.newPanel[ 0 ],
5483 index: data.newTab.closest( "li" ).index()
5485 } else if ( type === "activate" && data.newTab.length ) {
5486 ret = this._super( "show", event, {
5487 tab: data.newTab.find( ".ui-tabs-anchor" )[ 0 ],
5488 panel: data.newPanel[ 0 ],
5489 index: data.newTab.closest( "li" ).index()
5497 $.widget( "ui.tabs", $.ui.tabs, {
5498 select: function( index ) {
5499 index = this._getIndex( index );
5500 if ( index === -1 ) {
5501 if ( this.options.collapsible && this.options.selected !== -1 ) {
5502 index = this.options.selected;
5507 this.anchors.eq( index ).trigger( this.options.event + this.eventNamespace );
5516 $.widget( "ui.tabs", $.ui.tabs, {
5518 cookie: null // e.g. { expires: 7, path: '/', domain: 'jquery.com', secure: true }
5520 _create: function() {
5521 var options = this.options,
5523 if ( options.active == null && options.cookie ) {
5524 active = parseInt( this._cookie(), 10 );
5525 if ( active === -1 ) {
5528 options.active = active;
5532 _cookie: function( active ) {
5533 var cookie = [ this.cookie ||
5534 ( this.cookie = this.options.cookie.name || "ui-tabs-" + (++listId) ) ];
5535 if ( arguments.length ) {
5536 cookie.push( active === false ? -1 : active );
5537 cookie.push( this.options.cookie );
5539 return $.cookie.apply( null, cookie );
5541 _refresh: function() {
5543 if ( this.options.cookie ) {
5544 this._cookie( this.options.active, this.options.cookie );
5547 _eventHandler: function() {
5548 this._superApply( arguments );
5549 if ( this.options.cookie ) {
5550 this._cookie( this.options.active, this.options.cookie );
5553 _destroy: function() {
5555 if ( this.options.cookie ) {
5556 this._cookie( null, this.options.cookie );
5564 $.widget( "ui.tabs", $.ui.tabs, {
5565 _trigger: function( type, event, data ) {
5566 var _data = $.extend( {}, data );
5567 if ( type === "load" ) {
5568 _data.panel = _data.panel[ 0 ];
5569 _data.tab = _data.tab.find( ".ui-tabs-anchor" )[ 0 ];
5571 return this._super( type, event, _data );
5576 // The new animation options (show, hide) conflict with the old show callback.
5577 // The old fx option wins over show/hide anyway (always favor back-compat).
5578 // If a user wants to use the new animation API, they must give up the old API.
5579 $.widget( "ui.tabs", $.ui.tabs, {
5581 fx: null // e.g. { height: "toggle", opacity: "toggle", duration: 200 }
5584 _getFx: function() {
5586 fx = this.options.fx;
5589 if ( $.isArray( fx ) ) {
5597 return fx ? { show: show, hide: hide } : null;
5600 _toggle: function( event, eventData ) {
5602 toShow = eventData.newPanel,
5603 toHide = eventData.oldPanel,
5607 return this._super( event, eventData );
5610 that.running = true;
5612 function complete() {
5613 that.running = false;
5614 that._trigger( "activate", event, eventData );
5618 eventData.newTab.closest( "li" ).addClass( "ui-tabs-active ui-state-active" );
5620 if ( toShow.length && fx.show ) {
5622 .animate( fx.show, fx.show.duration, function() {
5631 // start out by hiding, then showing, then completing
5632 if ( toHide.length && fx.hide ) {
5633 toHide.animate( fx.hide, fx.hide.duration, function() {
5634 eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" );
5638 eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" );