1 /*! jQuery UI - v1.10.3 - 2013-10-15
3 * Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.sortable.js, jquery.ui.accordion.js, jquery.ui.tabs.js
4 * Copyright 2013 jQuery Foundation and other contributors; Licensed MIT */
6 (function( $, undefined ) {
9 runiqueId
= /^ui-id-\d+$/;
11 // $.ui might exist from components with no dependencies, e.g., $.ui.position
45 focus
: (function( orig
) {
46 return function( delay
, fn
) {
47 return typeof delay
=== "number" ?
48 this.each(function() {
50 setTimeout(function() {
57 orig
.apply( this, arguments
);
61 scrollParent: function() {
63 if (($.ui
.ie
&& (/(static|relative)/).test(this.css("position"))) || (/absolute/).test(this.css("position"))) {
64 scrollParent
= this.parents().filter(function() {
65 return (/(relative|absolute|fixed)/).test($.css(this,"position")) && (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x"));
68 scrollParent
= this.parents().filter(function() {
69 return (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x"));
73 return (/fixed/).test(this.css("position")) || !scrollParent
.length
? $(document
) : scrollParent
;
76 zIndex: function( zIndex
) {
77 if ( zIndex
!== undefined ) {
78 return this.css( "zIndex", zIndex
);
82 var elem
= $( this[ 0 ] ), position
, value
;
83 while ( elem
.length
&& elem
[ 0 ] !== document
) {
84 // Ignore z-index if position is set to a value where z-index is ignored by the browser
85 // This makes behavior of this function consistent across browsers
86 // WebKit always returns auto if the element is positioned
87 position
= elem
.css( "position" );
88 if ( position
=== "absolute" || position
=== "relative" || position
=== "fixed" ) {
89 // IE returns 0 when zIndex is not specified
90 // other browsers return a string
91 // we ignore the case of nested elements with an explicit value of 0
92 // <div style="z-index: -10;"><div style="z-index: 0;"></div></div>
93 value
= parseInt( elem
.css( "zIndex" ), 10 );
94 if ( !isNaN( value
) && value
!== 0 ) {
105 uniqueId: function() {
106 return this.each(function() {
108 this.id
= "ui-id-" + (++uuid
);
113 removeUniqueId: function() {
114 return this.each(function() {
115 if ( runiqueId
.test( this.id
) ) {
116 $( this ).removeAttr( "id" );
123 function focusable( element
, isTabIndexNotNaN
) {
124 var map
, mapName
, img
,
125 nodeName
= element
.nodeName
.toLowerCase();
126 if ( "area" === nodeName
) {
127 map
= element
.parentNode
;
129 if ( !element
.href
|| !mapName
|| map
.nodeName
.toLowerCase() !== "map" ) {
132 img
= $( "img[usemap=#" + mapName
+ "]" )[0];
133 return !!img
&& visible( img
);
135 return ( /input|select|textarea|button|object/.test( nodeName
) ?
138 element
.href
|| isTabIndexNotNaN
:
140 // the element and all of its ancestors must be visible
144 function visible( element
) {
145 return $.expr
.filters
.visible( element
) &&
146 !$( element
).parents().addBack().filter(function() {
147 return $.css( this, "visibility" ) === "hidden";
151 $.extend( $.expr
[ ":" ], {
152 data
: $.expr
.createPseudo
?
153 $.expr
.createPseudo(function( dataName
) {
154 return function( elem
) {
155 return !!$.data( elem
, dataName
);
158 // support: jQuery <1.8
159 function( elem
, i
, match
) {
160 return !!$.data( elem
, match
[ 3 ] );
163 focusable: function( element
) {
164 return focusable( element
, !isNaN( $.attr( element
, "tabindex" ) ) );
167 tabbable: function( element
) {
168 var tabIndex
= $.attr( element
, "tabindex" ),
169 isTabIndexNaN
= isNaN( tabIndex
);
170 return ( isTabIndexNaN
|| tabIndex
>= 0 ) && focusable( element
, !isTabIndexNaN
);
174 // support: jQuery <1.8
175 if ( !$( "<a>" ).outerWidth( 1 ).jquery
) {
176 $.each( [ "Width", "Height" ], function( i
, name
) {
177 var side
= name
=== "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ],
178 type
= name
.toLowerCase(),
180 innerWidth
: $.fn
.innerWidth
,
181 innerHeight
: $.fn
.innerHeight
,
182 outerWidth
: $.fn
.outerWidth
,
183 outerHeight
: $.fn
.outerHeight
186 function reduce( elem
, size
, border
, margin
) {
187 $.each( side
, function() {
188 size
-= parseFloat( $.css( elem
, "padding" + this ) ) || 0;
190 size
-= parseFloat( $.css( elem
, "border" + this + "Width" ) ) || 0;
193 size
-= parseFloat( $.css( elem
, "margin" + this ) ) || 0;
199 $.fn
[ "inner" + name
] = function( size
) {
200 if ( size
=== undefined ) {
201 return orig
[ "inner" + name
].call( this );
204 return this.each(function() {
205 $( this ).css( type
, reduce( this, size
) + "px" );
209 $.fn
[ "outer" + name
] = function( size
, margin
) {
210 if ( typeof size
!== "number" ) {
211 return orig
[ "outer" + name
].call( this, size
);
214 return this.each(function() {
215 $( this).css( type
, reduce( this, size
, true, margin
) + "px" );
221 // support: jQuery <1.8
222 if ( !$.fn
.addBack
) {
223 $.fn
.addBack = function( selector
) {
224 return this.add( selector
== null ?
225 this.prevObject
: this.prevObject
.filter( selector
)
230 // support: jQuery 1.6.1, 1.6.2 (http://bugs.jquery.com/ticket/9413)
231 if ( $( "<a>" ).data( "a-b", "a" ).removeData( "a-b" ).data( "a-b" ) ) {
232 $.fn
.removeData
= (function( removeData
) {
233 return function( key
) {
234 if ( arguments
.length
) {
235 return removeData
.call( this, $.camelCase( key
) );
237 return removeData
.call( this );
240 })( $.fn
.removeData
);
248 $.ui
.ie
= !!/msie [\w.]+/.exec( navigator
.userAgent
.toLowerCase() );
250 $.support
.selectstart
= "onselectstart" in document
.createElement( "div" );
252 disableSelection: function() {
253 return this.bind( ( $.support
.selectstart
? "selectstart" : "mousedown" ) +
254 ".ui-disableSelection", function( event
) {
255 event
.preventDefault();
259 enableSelection: function() {
260 return this.unbind( ".ui-disableSelection" );
265 // $.ui.plugin is deprecated. Use $.widget() extensions instead.
267 add: function( module
, option
, set ) {
269 proto
= $.ui
[ module
].prototype;
271 proto
.plugins
[ i
] = proto
.plugins
[ i
] || [];
272 proto
.plugins
[ i
].push( [ option
, set[ i
] ] );
275 call: function( instance
, name
, args
) {
277 set = instance
.plugins
[ name
];
278 if ( !set || !instance
.element
[ 0 ].parentNode
|| instance
.element
[ 0 ].parentNode
.nodeType
=== 11 ) {
282 for ( i
= 0; i
< set.length
; i
++ ) {
283 if ( instance
.options
[ set[ i
][ 0 ] ] ) {
284 set[ i
][ 1 ].apply( instance
.element
, args
);
290 // only used by resizable
291 hasScroll: function( el
, a
) {
293 //If overflow is hidden, the element might have extra content, but the user wants to hide it
294 if ( $( el
).css( "overflow" ) === "hidden") {
298 var scroll
= ( a
&& a
=== "left" ) ? "scrollLeft" : "scrollTop",
301 if ( el
[ scroll
] > 0 ) {
305 // TODO: determine which cases actually cause this to happen
306 // if the element doesn't have the scroll set, see if it's possible to
309 has
= ( el
[ scroll
] > 0 );
316 (function( $, undefined ) {
319 slice
= Array
.prototype.slice
,
320 _cleanData
= $.cleanData
;
321 $.cleanData = function( elems
) {
322 for ( var i
= 0, elem
; (elem
= elems
[i
]) != null; i
++ ) {
324 $( elem
).triggerHandler( "remove" );
325 // http://bugs.jquery.com/ticket/8235
331 $.widget = function( name
, base
, prototype ) {
332 var fullName
, existingConstructor
, constructor, basePrototype
,
333 // proxiedPrototype allows the provided prototype to remain unmodified
334 // so that it can be used as a mixin for multiple widgets (#8876)
335 proxiedPrototype
= {},
336 namespace = name
.split( "." )[ 0 ];
338 name
= name
.split( "." )[ 1 ];
339 fullName
= namespace + "-" + name
;
346 // create selector for plugin
347 $.expr
[ ":" ][ fullName
.toLowerCase() ] = function( elem
) {
348 return !!$.data( elem
, fullName
);
351 $[ namespace ] = $[ namespace ] || {};
352 existingConstructor
= $[ namespace ][ name
];
353 constructor = $[ namespace ][ name
] = function( options
, element
) {
354 // allow instantiation without "new" keyword
355 if ( !this._createWidget
) {
356 return new constructor( options
, element
);
359 // allow instantiation without initializing for simple inheritance
360 // must use "new" keyword (the code above always passes args)
361 if ( arguments
.length
) {
362 this._createWidget( options
, element
);
365 // extend with the existing constructor to carry over any static properties
366 $.extend( constructor, existingConstructor
, {
367 version
: prototype.version
,
368 // copy the object used to create the prototype in case we need to
369 // redefine the widget later
370 _proto
: $.extend( {}, prototype ),
371 // track widgets that inherit from this widget in case this widget is
372 // redefined after a widget inherits from it
373 _childConstructors
: []
376 basePrototype
= new base();
377 // we need to make the options hash a property directly on the new instance
378 // otherwise we'll modify the options hash on the prototype that we're
380 basePrototype
.options
= $.widget
.extend( {}, basePrototype
.options
);
381 $.each( prototype, function( prop
, value
) {
382 if ( !$.isFunction( value
) ) {
383 proxiedPrototype
[ prop
] = value
;
386 proxiedPrototype
[ prop
] = (function() {
387 var _super = function() {
388 return base
.prototype[ prop
].apply( this, arguments
);
390 _superApply = function( args
) {
391 return base
.prototype[ prop
].apply( this, args
);
394 var __super
= this._super
,
395 __superApply
= this._superApply
,
398 this._super
= _super
;
399 this._superApply
= _superApply
;
401 returnValue
= value
.apply( this, arguments
);
403 this._super
= __super
;
404 this._superApply
= __superApply
;
410 constructor.prototype = $.widget
.extend( basePrototype
, {
411 // TODO: remove support for widgetEventPrefix
412 // always use the name + a colon as the prefix, e.g., draggable:start
413 // don't prefix for widgets that aren't DOM-based
414 widgetEventPrefix
: existingConstructor
? basePrototype
.widgetEventPrefix
: name
415 }, proxiedPrototype
, {
416 constructor: constructor,
417 namespace: namespace,
419 widgetFullName
: fullName
422 // If this widget is being redefined then we need to find all widgets that
423 // are inheriting from it and redefine all of them so that they inherit from
424 // the new version of this widget. We're essentially trying to replace one
425 // level in the prototype chain.
426 if ( existingConstructor
) {
427 $.each( existingConstructor
._childConstructors
, function( i
, child
) {
428 var childPrototype
= child
.prototype;
430 // redefine the child widget using the same prototype that was
431 // originally used, but inherit from the new version of the base
432 $.widget( childPrototype
.namespace + "." + childPrototype
.widgetName
, constructor, child
._proto
);
434 // remove the list of existing child constructors from the old constructor
435 // so the old child constructors can be garbage collected
436 delete existingConstructor
._childConstructors
;
438 base
._childConstructors
.push( constructor );
441 $.widget
.bridge( name
, constructor );
444 $.widget
.extend = function( target
) {
445 var input
= slice
.call( arguments
, 1 ),
447 inputLength
= input
.length
,
450 for ( ; inputIndex
< inputLength
; inputIndex
++ ) {
451 for ( key
in input
[ inputIndex
] ) {
452 value
= input
[ inputIndex
][ key
];
453 if ( input
[ inputIndex
].hasOwnProperty( key
) && value
!== undefined ) {
455 if ( $.isPlainObject( value
) ) {
456 target
[ key
] = $.isPlainObject( target
[ key
] ) ?
457 $.widget
.extend( {}, target
[ key
], value
) :
458 // Don't extend strings, arrays, etc. with objects
459 $.widget
.extend( {}, value
);
460 // Copy everything else by reference
462 target
[ key
] = value
;
470 $.widget
.bridge = function( name
, object
) {
471 var fullName
= object
.prototype.widgetFullName
|| name
;
472 $.fn
[ name
] = function( options
) {
473 var isMethodCall
= typeof options
=== "string",
474 args
= slice
.call( arguments
, 1 ),
477 // allow multiple hashes to be passed on init
478 options
= !isMethodCall
&& args
.length
?
479 $.widget
.extend
.apply( null, [ options
].concat(args
) ) :
482 if ( isMethodCall
) {
483 this.each(function() {
485 instance
= $.data( this, fullName
);
487 return $.error( "cannot call methods on " + name
+ " prior to initialization; " +
488 "attempted to call method '" + options
+ "'" );
490 if ( !$.isFunction( instance
[options
] ) || options
.charAt( 0 ) === "_" ) {
491 return $.error( "no such method '" + options
+ "' for " + name
+ " widget instance" );
493 methodValue
= instance
[ options
].apply( instance
, args
);
494 if ( methodValue
!== instance
&& methodValue
!== undefined ) {
495 returnValue
= methodValue
&& methodValue
.jquery
?
496 returnValue
.pushStack( methodValue
.get() ) :
502 this.each(function() {
503 var instance
= $.data( this, fullName
);
505 instance
.option( options
|| {} )._init();
507 $.data( this, fullName
, new object( options
, this ) );
516 $.Widget = function( /* options, element */ ) {};
517 $.Widget
._childConstructors
= [];
519 $.Widget
.prototype = {
520 widgetName
: "widget",
521 widgetEventPrefix
: "",
522 defaultElement
: "<div>",
529 _createWidget: function( options
, element
) {
530 element
= $( element
|| this.defaultElement
|| this )[ 0 ];
531 this.element
= $( element
);
533 this.eventNamespace
= "." + this.widgetName
+ this.uuid
;
534 this.options
= $.widget
.extend( {},
536 this._getCreateOptions(),
540 this.hoverable
= $();
541 this.focusable
= $();
543 if ( element
!== this ) {
544 $.data( element
, this.widgetFullName
, this );
545 this._on( true, this.element
, {
546 remove: function( event
) {
547 if ( event
.target
=== element
) {
552 this.document
= $( element
.style
?
553 // element within the document
554 element
.ownerDocument
:
555 // element is window or document
556 element
.document
|| element
);
557 this.window
= $( this.document
[0].defaultView
|| this.document
[0].parentWindow
);
561 this._trigger( "create", null, this._getCreateEventData() );
564 _getCreateOptions
: $.noop
,
565 _getCreateEventData
: $.noop
,
569 destroy: function() {
571 // we can probably remove the unbind calls in 2.0
572 // all event bindings should go through this._on()
574 .unbind( this.eventNamespace
)
576 // TODO remove dual storage
577 .removeData( this.widgetName
)
578 .removeData( this.widgetFullName
)
579 // support: jquery <1.6.3
580 // http://bugs.jquery.com/ticket/9413
581 .removeData( $.camelCase( this.widgetFullName
) );
583 .unbind( this.eventNamespace
)
584 .removeAttr( "aria-disabled" )
586 this.widgetFullName
+ "-disabled " +
587 "ui-state-disabled" );
589 // clean up events and states
590 this.bindings
.unbind( this.eventNamespace
);
591 this.hoverable
.removeClass( "ui-state-hover" );
592 this.focusable
.removeClass( "ui-state-focus" );
600 option: function( key
, value
) {
606 if ( arguments
.length
=== 0 ) {
607 // don't return a reference to the internal hash
608 return $.widget
.extend( {}, this.options
);
611 if ( typeof key
=== "string" ) {
612 // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
614 parts
= key
.split( "." );
616 if ( parts
.length
) {
617 curOption
= options
[ key
] = $.widget
.extend( {}, this.options
[ key
] );
618 for ( i
= 0; i
< parts
.length
- 1; i
++ ) {
619 curOption
[ parts
[ i
] ] = curOption
[ parts
[ i
] ] || {};
620 curOption
= curOption
[ parts
[ i
] ];
623 if ( value
=== undefined ) {
624 return curOption
[ key
] === undefined ? null : curOption
[ key
];
626 curOption
[ key
] = value
;
628 if ( value
=== undefined ) {
629 return this.options
[ key
] === undefined ? null : this.options
[ key
];
631 options
[ key
] = value
;
635 this._setOptions( options
);
639 _setOptions: function( options
) {
642 for ( key
in options
) {
643 this._setOption( key
, options
[ key
] );
648 _setOption: function( key
, value
) {
649 this.options
[ key
] = value
;
651 if ( key
=== "disabled" ) {
653 .toggleClass( this.widgetFullName
+ "-disabled ui-state-disabled", !!value
)
654 .attr( "aria-disabled", value
);
655 this.hoverable
.removeClass( "ui-state-hover" );
656 this.focusable
.removeClass( "ui-state-focus" );
663 return this._setOption( "disabled", false );
665 disable: function() {
666 return this._setOption( "disabled", true );
669 _on: function( suppressDisabledCheck
, element
, handlers
) {
673 // no suppressDisabledCheck flag, shuffle arguments
674 if ( typeof suppressDisabledCheck
!== "boolean" ) {
676 element
= suppressDisabledCheck
;
677 suppressDisabledCheck
= false;
680 // no element argument, shuffle and use this.element
683 element
= this.element
;
684 delegateElement
= this.widget();
686 // accept selectors, DOM elements
687 element
= delegateElement
= $( element
);
688 this.bindings
= this.bindings
.add( element
);
691 $.each( handlers
, function( event
, handler
) {
692 function handlerProxy() {
693 // allow widgets to customize the disabled handling
694 // - disabled as an array instead of boolean
695 // - disabled class as method for disabling individual parts
696 if ( !suppressDisabledCheck
&&
697 ( instance
.options
.disabled
=== true ||
698 $( this ).hasClass( "ui-state-disabled" ) ) ) {
701 return ( typeof handler
=== "string" ? instance
[ handler
] : handler
)
702 .apply( instance
, arguments
);
705 // copy the guid so direct unbinding works
706 if ( typeof handler
!== "string" ) {
707 handlerProxy
.guid
= handler
.guid
=
708 handler
.guid
|| handlerProxy
.guid
|| $.guid
++;
711 var match
= event
.match( /^(\w+)\s*(.*)$/ ),
712 eventName
= match
[1] + instance
.eventNamespace
,
715 delegateElement
.delegate( selector
, eventName
, handlerProxy
);
717 element
.bind( eventName
, handlerProxy
);
722 _off: function( element
, eventName
) {
723 eventName
= (eventName
|| "").split( " " ).join( this.eventNamespace
+ " " ) + this.eventNamespace
;
724 element
.unbind( eventName
).undelegate( eventName
);
727 _delay: function( handler
, delay
) {
728 function handlerProxy() {
729 return ( typeof handler
=== "string" ? instance
[ handler
] : handler
)
730 .apply( instance
, arguments
);
733 return setTimeout( handlerProxy
, delay
|| 0 );
736 _hoverable: function( element
) {
737 this.hoverable
= this.hoverable
.add( element
);
739 mouseenter: function( event
) {
740 $( event
.currentTarget
).addClass( "ui-state-hover" );
742 mouseleave: function( event
) {
743 $( event
.currentTarget
).removeClass( "ui-state-hover" );
748 _focusable: function( element
) {
749 this.focusable
= this.focusable
.add( element
);
751 focusin: function( event
) {
752 $( event
.currentTarget
).addClass( "ui-state-focus" );
754 focusout: function( event
) {
755 $( event
.currentTarget
).removeClass( "ui-state-focus" );
760 _trigger: function( type
, event
, data
) {
762 callback
= this.options
[ type
];
765 event
= $.Event( event
);
766 event
.type
= ( type
=== this.widgetEventPrefix
?
768 this.widgetEventPrefix
+ type
).toLowerCase();
769 // the original event may come from any element
770 // so we need to reset the target on the new event
771 event
.target
= this.element
[ 0 ];
773 // copy original event properties over to the new event
774 orig
= event
.originalEvent
;
776 for ( prop
in orig
) {
777 if ( !( prop
in event
) ) {
778 event
[ prop
] = orig
[ prop
];
783 this.element
.trigger( event
, data
);
784 return !( $.isFunction( callback
) &&
785 callback
.apply( this.element
[0], [ event
].concat( data
) ) === false ||
786 event
.isDefaultPrevented() );
790 $.each( { show
: "fadeIn", hide
: "fadeOut" }, function( method
, defaultEffect
) {
791 $.Widget
.prototype[ "_" + method
] = function( element
, options
, callback
) {
792 if ( typeof options
=== "string" ) {
793 options
= { effect
: options
};
796 effectName
= !options
?
798 options
=== true || typeof options
=== "number" ?
800 options
.effect
|| defaultEffect
;
801 options
= options
|| {};
802 if ( typeof options
=== "number" ) {
803 options
= { duration
: options
};
805 hasOptions
= !$.isEmptyObject( options
);
806 options
.complete
= callback
;
807 if ( options
.delay
) {
808 element
.delay( options
.delay
);
810 if ( hasOptions
&& $.effects
&& $.effects
.effect
[ effectName
] ) {
811 element
[ method
]( options
);
812 } else if ( effectName
!== method
&& element
[ effectName
] ) {
813 element
[ effectName
]( options
.duration
, options
.easing
, callback
);
815 element
.queue(function( next
) {
816 $( this )[ method
]();
818 callback
.call( element
[ 0 ] );
827 (function( $, undefined ) {
829 var mouseHandled
= false;
830 $( document
).mouseup( function() {
831 mouseHandled
= false;
834 $.widget("ui.mouse", {
837 cancel
: "input,textarea,button,select,option",
841 _mouseInit: function() {
845 .bind("mousedown."+this.widgetName
, function(event
) {
846 return that
._mouseDown(event
);
848 .bind("click."+this.widgetName
, function(event
) {
849 if (true === $.data(event
.target
, that
.widgetName
+ ".preventClickEvent")) {
850 $.removeData(event
.target
, that
.widgetName
+ ".preventClickEvent");
851 event
.stopImmediatePropagation();
856 this.started
= false;
859 // TODO: make sure destroying one instance of mouse doesn't mess with
860 // other instances of mouse
861 _mouseDestroy: function() {
862 this.element
.unbind("."+this.widgetName
);
863 if ( this._mouseMoveDelegate
) {
865 .unbind("mousemove."+this.widgetName
, this._mouseMoveDelegate
)
866 .unbind("mouseup."+this.widgetName
, this._mouseUpDelegate
);
870 _mouseDown: function(event
) {
871 // don't let more than one widget handle mouseStart
872 if( mouseHandled
) { return; }
874 // we may have missed mouseup (out of window)
875 (this._mouseStarted
&& this._mouseUp(event
));
877 this._mouseDownEvent
= event
;
880 btnIsLeft
= (event
.which
=== 1),
881 // event.target.nodeName works around a bug in IE 8 with
882 // disabled inputs (#7620)
883 elIsCancel
= (typeof this.options
.cancel
=== "string" && event
.target
.nodeName
? $(event
.target
).closest(this.options
.cancel
).length
: false);
884 if (!btnIsLeft
|| elIsCancel
|| !this._mouseCapture(event
)) {
888 this.mouseDelayMet
= !this.options
.delay
;
889 if (!this.mouseDelayMet
) {
890 this._mouseDelayTimer
= setTimeout(function() {
891 that
.mouseDelayMet
= true;
892 }, this.options
.delay
);
895 if (this._mouseDistanceMet(event
) && this._mouseDelayMet(event
)) {
896 this._mouseStarted
= (this._mouseStart(event
) !== false);
897 if (!this._mouseStarted
) {
898 event
.preventDefault();
903 // Click event may never have fired (Gecko & Opera)
904 if (true === $.data(event
.target
, this.widgetName
+ ".preventClickEvent")) {
905 $.removeData(event
.target
, this.widgetName
+ ".preventClickEvent");
908 // these delegates are required to keep context
909 this._mouseMoveDelegate = function(event
) {
910 return that
._mouseMove(event
);
912 this._mouseUpDelegate = function(event
) {
913 return that
._mouseUp(event
);
916 .bind("mousemove."+this.widgetName
, this._mouseMoveDelegate
)
917 .bind("mouseup."+this.widgetName
, this._mouseUpDelegate
);
919 event
.preventDefault();
925 _mouseMove: function(event
) {
926 // IE mouseup check - mouseup happened when mouse was out of window
927 if ($.ui
.ie
&& ( !document
.documentMode
|| document
.documentMode
< 9 ) && !event
.button
) {
928 return this._mouseUp(event
);
931 if (this._mouseStarted
) {
932 this._mouseDrag(event
);
933 return event
.preventDefault();
936 if (this._mouseDistanceMet(event
) && this._mouseDelayMet(event
)) {
938 (this._mouseStart(this._mouseDownEvent
, event
) !== false);
939 (this._mouseStarted
? this._mouseDrag(event
) : this._mouseUp(event
));
942 return !this._mouseStarted
;
945 _mouseUp: function(event
) {
947 .unbind("mousemove."+this.widgetName
, this._mouseMoveDelegate
)
948 .unbind("mouseup."+this.widgetName
, this._mouseUpDelegate
);
950 if (this._mouseStarted
) {
951 this._mouseStarted
= false;
953 if (event
.target
=== this._mouseDownEvent
.target
) {
954 $.data(event
.target
, this.widgetName
+ ".preventClickEvent", true);
957 this._mouseStop(event
);
963 _mouseDistanceMet: function(event
) {
965 Math
.abs(this._mouseDownEvent
.pageX
- event
.pageX
),
966 Math
.abs(this._mouseDownEvent
.pageY
- event
.pageY
)
967 ) >= this.options
.distance
971 _mouseDelayMet: function(/* event */) {
972 return this.mouseDelayMet
;
975 // These are placeholder methods, to be overriden by extending plugin
976 _mouseStart: function(/* event */) {},
977 _mouseDrag: function(/* event */) {},
978 _mouseStop: function(/* event */) {},
979 _mouseCapture: function(/* event */) { return true; }
983 (function( $, undefined ) {
985 /*jshint loopfunc: true */
987 function isOverAxis( x
, reference
, size
) {
988 return ( x
> reference
) && ( x
< ( reference
+ size
) );
991 function isFloating(item
) {
992 return (/left|right/).test(item
.css("float")) || (/inline|table-cell/).test(item
.css("display"));
995 $.widget("ui.sortable", $.ui
.mouse
, {
997 widgetEventPrefix
: "sort",
1007 forcePlaceholderSize
: false,
1008 forceHelperSize
: false,
1017 scrollSensitivity
: 20,
1020 tolerance
: "intersect",
1037 _create: function() {
1039 var o
= this.options
;
1040 this.containerCache
= {};
1041 this.element
.addClass("ui-sortable");
1046 //Let's determine if the items are being displayed horizontally
1047 this.floating
= this.items
.length
? o
.axis
=== "x" || isFloating(this.items
[0].item
) : false;
1049 //Let's determine the parent's offset
1050 this.offset
= this.element
.offset();
1052 //Initialize mouse events for interaction
1060 _destroy: function() {
1062 .removeClass("ui-sortable ui-sortable-disabled");
1063 this._mouseDestroy();
1065 for ( var i
= this.items
.length
- 1; i
>= 0; i
-- ) {
1066 this.items
[i
].item
.removeData(this.widgetName
+ "-item");
1072 _setOption: function(key
, value
){
1073 if ( key
=== "disabled" ) {
1074 this.options
[ key
] = value
;
1076 this.widget().toggleClass( "ui-sortable-disabled", !!value
);
1078 // Don't call widget base _setOption for disable as it adds ui-state-disabled class
1079 $.Widget
.prototype._setOption
.apply(this, arguments
);
1083 _mouseCapture: function(event
, overrideHandle
) {
1084 var currentItem
= null,
1085 validHandle
= false,
1088 if (this.reverting
) {
1092 if(this.options
.disabled
|| this.options
.type
=== "static") {
1096 //We have to refresh the items data once first
1097 this._refreshItems(event
);
1099 //Find out if the clicked node (or one of its parents) is a actual item in this.items
1100 $(event
.target
).parents().each(function() {
1101 if($.data(this, that
.widgetName
+ "-item") === that
) {
1102 currentItem
= $(this);
1106 if($.data(event
.target
, that
.widgetName
+ "-item") === that
) {
1107 currentItem
= $(event
.target
);
1113 if(this.options
.handle
&& !overrideHandle
) {
1114 $(this.options
.handle
, currentItem
).find("*").addBack().each(function() {
1115 if(this === event
.target
) {
1124 this.currentItem
= currentItem
;
1125 this._removeCurrentsFromItems();
1130 _mouseStart: function(event
, overrideHandle
, noActivation
) {
1135 this.currentContainer
= this;
1137 //We only need to call refreshPositions, because the refreshItems call has been moved to mouseCapture
1138 this.refreshPositions();
1140 //Create and append the visible helper
1141 this.helper
= this._createHelper(event
);
1143 //Cache the helper size
1144 this._cacheHelperProportions();
1147 * - Position generation -
1148 * This block generates everything position related - it's the core of draggables.
1151 //Cache the margins of the original element
1152 this._cacheMargins();
1154 //Get the next scrolling parent
1155 this.scrollParent
= this.helper
.scrollParent();
1157 //The element's absolute position on the page minus margins
1158 this.offset
= this.currentItem
.offset();
1160 top
: this.offset
.top
- this.margins
.top
,
1161 left
: this.offset
.left
- this.margins
.left
1164 $.extend(this.offset
, {
1165 click
: { //Where the click happened, relative to the element
1166 left
: event
.pageX
- this.offset
.left
,
1167 top
: event
.pageY
- this.offset
.top
1169 parent
: this._getParentOffset(),
1170 relative
: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
1173 // Only after we got the offset, we can change the helper's position to absolute
1174 // TODO: Still need to figure out a way to make relative sorting possible
1175 this.helper
.css("position", "absolute");
1176 this.cssPosition
= this.helper
.css("position");
1178 //Generate the original position
1179 this.originalPosition
= this._generatePosition(event
);
1180 this.originalPageX
= event
.pageX
;
1181 this.originalPageY
= event
.pageY
;
1183 //Adjust the mouse offset relative to the helper if "cursorAt" is supplied
1184 (o
.cursorAt
&& this._adjustOffsetFromHelper(o
.cursorAt
));
1186 //Cache the former DOM position
1187 this.domPosition
= { prev
: this.currentItem
.prev()[0], parent
: this.currentItem
.parent()[0] };
1189 //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
1190 if(this.helper
[0] !== this.currentItem
[0]) {
1191 this.currentItem
.hide();
1194 //Create the placeholder
1195 this._createPlaceholder();
1197 //Set a containment if given in the options
1199 this._setContainment();
1202 if( o
.cursor
&& o
.cursor
!== "auto" ) { // cursor option
1203 body
= this.document
.find( "body" );
1206 this.storedCursor
= body
.css( "cursor" );
1207 body
.css( "cursor", o
.cursor
);
1209 this.storedStylesheet
= $( "<style>*{ cursor: "+o
.cursor
+" !important; }</style>" ).appendTo( body
);
1212 if(o
.opacity
) { // opacity option
1213 if (this.helper
.css("opacity")) {
1214 this._storedOpacity
= this.helper
.css("opacity");
1216 this.helper
.css("opacity", o
.opacity
);
1219 if(o
.zIndex
) { // zIndex option
1220 if (this.helper
.css("zIndex")) {
1221 this._storedZIndex
= this.helper
.css("zIndex");
1223 this.helper
.css("zIndex", o
.zIndex
);
1227 if(this.scrollParent
[0] !== document
&& this.scrollParent
[0].tagName
!== "HTML") {
1228 this.overflowOffset
= this.scrollParent
.offset();
1232 this._trigger("start", event
, this._uiHash());
1234 //Recache the helper size
1235 if(!this._preserveHelperProportions
) {
1236 this._cacheHelperProportions();
1240 //Post "activate" events to possible containers
1241 if( !noActivation
) {
1242 for ( i
= this.containers
.length
- 1; i
>= 0; i
-- ) {
1243 this.containers
[ i
]._trigger( "activate", event
, this._uiHash( this ) );
1247 //Prepare possible droppables
1248 if($.ui
.ddmanager
) {
1249 $.ui
.ddmanager
.current
= this;
1252 if ($.ui
.ddmanager
&& !o
.dropBehaviour
) {
1253 $.ui
.ddmanager
.prepareOffsets(this, event
);
1256 this.dragging
= true;
1258 this.helper
.addClass("ui-sortable-helper");
1259 this._mouseDrag(event
); //Execute the drag once - this causes the helper not to be visible before getting its correct position
1264 _mouseDrag: function(event
) {
1265 var i
, item
, itemElement
, intersection
,
1269 //Compute the helpers position
1270 this.position
= this._generatePosition(event
);
1271 this.positionAbs
= this._convertPositionTo("absolute");
1273 if (!this.lastPositionAbs
) {
1274 this.lastPositionAbs
= this.positionAbs
;
1278 if(this.options
.scroll
) {
1279 if(this.scrollParent
[0] !== document
&& this.scrollParent
[0].tagName
!== "HTML") {
1281 if((this.overflowOffset
.top
+ this.scrollParent
[0].offsetHeight
) - event
.pageY
< o
.scrollSensitivity
) {
1282 this.scrollParent
[0].scrollTop
= scrolled
= this.scrollParent
[0].scrollTop
+ o
.scrollSpeed
;
1283 } else if(event
.pageY
- this.overflowOffset
.top
< o
.scrollSensitivity
) {
1284 this.scrollParent
[0].scrollTop
= scrolled
= this.scrollParent
[0].scrollTop
- o
.scrollSpeed
;
1287 if((this.overflowOffset
.left
+ this.scrollParent
[0].offsetWidth
) - event
.pageX
< o
.scrollSensitivity
) {
1288 this.scrollParent
[0].scrollLeft
= scrolled
= this.scrollParent
[0].scrollLeft
+ o
.scrollSpeed
;
1289 } else if(event
.pageX
- this.overflowOffset
.left
< o
.scrollSensitivity
) {
1290 this.scrollParent
[0].scrollLeft
= scrolled
= this.scrollParent
[0].scrollLeft
- o
.scrollSpeed
;
1295 if(event
.pageY
- $(document
).scrollTop() < o
.scrollSensitivity
) {
1296 scrolled
= $(document
).scrollTop($(document
).scrollTop() - o
.scrollSpeed
);
1297 } else if($(window
).height() - (event
.pageY
- $(document
).scrollTop()) < o
.scrollSensitivity
) {
1298 scrolled
= $(document
).scrollTop($(document
).scrollTop() + o
.scrollSpeed
);
1301 if(event
.pageX
- $(document
).scrollLeft() < o
.scrollSensitivity
) {
1302 scrolled
= $(document
).scrollLeft($(document
).scrollLeft() - o
.scrollSpeed
);
1303 } else if($(window
).width() - (event
.pageX
- $(document
).scrollLeft()) < o
.scrollSensitivity
) {
1304 scrolled
= $(document
).scrollLeft($(document
).scrollLeft() + o
.scrollSpeed
);
1309 if(scrolled
!== false && $.ui
.ddmanager
&& !o
.dropBehaviour
) {
1310 $.ui
.ddmanager
.prepareOffsets(this, event
);
1314 //Regenerate the absolute position used for position checks
1315 this.positionAbs
= this._convertPositionTo("absolute");
1317 //Set the helper position
1318 if(!this.options
.axis
|| this.options
.axis
!== "y") {
1319 this.helper
[0].style
.left
= this.position
.left
+"px";
1321 if(!this.options
.axis
|| this.options
.axis
!== "x") {
1322 this.helper
[0].style
.top
= this.position
.top
+"px";
1326 for (i
= this.items
.length
- 1; i
>= 0; i
--) {
1328 //Cache variables and intersection, continue if no intersection
1329 item
= this.items
[i
];
1330 itemElement
= item
.item
[0];
1331 intersection
= this._intersectsWithPointer(item
);
1332 if (!intersection
) {
1336 // Only put the placeholder inside the current Container, skip all
1337 // items form other containers. This works because when moving
1338 // an item from one container to another the
1339 // currentContainer is switched before the placeholder is moved.
1341 // Without this moving items in "sub-sortables" can cause the placeholder to jitter
1342 // beetween the outer and inner container.
1343 if (item
.instance
!== this.currentContainer
) {
1347 // cannot intersect with itself
1348 // no useless actions that have been done before
1349 // no action if the item moved is the parent of the item checked
1350 if (itemElement
!== this.currentItem
[0] &&
1351 this.placeholder
[intersection
=== 1 ? "next" : "prev"]()[0] !== itemElement
&&
1352 !$.contains(this.placeholder
[0], itemElement
) &&
1353 (this.options
.type
=== "semi-dynamic" ? !$.contains(this.element
[0], itemElement
) : true)
1356 this.direction
= intersection
=== 1 ? "down" : "up";
1358 if (this.options
.tolerance
=== "pointer" || this._intersectsWithSides(item
)) {
1359 this._rearrange(event
, item
);
1364 this._trigger("change", event
, this._uiHash());
1369 //Post events to containers
1370 this._contactContainers(event
);
1372 //Interconnect with droppables
1373 if($.ui
.ddmanager
) {
1374 $.ui
.ddmanager
.drag(this, event
);
1378 this._trigger("sort", event
, this._uiHash());
1380 this.lastPositionAbs
= this.positionAbs
;
1385 _mouseStop: function(event
, noPropagation
) {
1391 //If we are using droppables, inform the manager about the drop
1392 if ($.ui
.ddmanager
&& !this.options
.dropBehaviour
) {
1393 $.ui
.ddmanager
.drop(this, event
);
1396 if(this.options
.revert
) {
1398 cur
= this.placeholder
.offset(),
1399 axis
= this.options
.axis
,
1402 if ( !axis
|| axis
=== "x" ) {
1403 animation
.left
= cur
.left
- this.offset
.parent
.left
- this.margins
.left
+ (this.offsetParent
[0] === document
.body
? 0 : this.offsetParent
[0].scrollLeft
);
1405 if ( !axis
|| axis
=== "y" ) {
1406 animation
.top
= cur
.top
- this.offset
.parent
.top
- this.margins
.top
+ (this.offsetParent
[0] === document
.body
? 0 : this.offsetParent
[0].scrollTop
);
1408 this.reverting
= true;
1409 $(this.helper
).animate( animation
, parseInt(this.options
.revert
, 10) || 500, function() {
1413 this._clear(event
, noPropagation
);
1420 cancel: function() {
1424 this._mouseUp({ target
: null });
1426 if(this.options
.helper
=== "original") {
1427 this.currentItem
.css(this._storedCSS
).removeClass("ui-sortable-helper");
1429 this.currentItem
.show();
1432 //Post deactivating events to containers
1433 for (var i
= this.containers
.length
- 1; i
>= 0; i
--){
1434 this.containers
[i
]._trigger("deactivate", null, this._uiHash(this));
1435 if(this.containers
[i
].containerCache
.over
) {
1436 this.containers
[i
]._trigger("out", null, this._uiHash(this));
1437 this.containers
[i
].containerCache
.over
= 0;
1443 if (this.placeholder
) {
1444 //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
1445 if(this.placeholder
[0].parentNode
) {
1446 this.placeholder
[0].parentNode
.removeChild(this.placeholder
[0]);
1448 if(this.options
.helper
!== "original" && this.helper
&& this.helper
[0].parentNode
) {
1449 this.helper
.remove();
1459 if(this.domPosition
.prev
) {
1460 $(this.domPosition
.prev
).after(this.currentItem
);
1462 $(this.domPosition
.parent
).prepend(this.currentItem
);
1470 serialize: function(o
) {
1472 var items
= this._getItemsAsjQuery(o
&& o
.connected
),
1476 $(items
).each(function() {
1477 var res
= ($(o
.item
|| this).attr(o
.attribute
|| "id") || "").match(o
.expression
|| (/(.+)[\-=_](.+)/));
1479 str
.push((o
.key
|| res
[1]+"[]")+"="+(o
.key
&& o
.expression
? res
[1] : res
[2]));
1483 if(!str
.length
&& o
.key
) {
1484 str
.push(o
.key
+ "=");
1487 return str
.join("&");
1491 toArray: function(o
) {
1493 var items
= this._getItemsAsjQuery(o
&& o
.connected
),
1498 items
.each(function() { ret
.push($(o
.item
|| this).attr(o
.attribute
|| "id") || ""); });
1503 /* Be careful with the following core functions */
1504 _intersectsWith: function(item
) {
1506 var x1
= this.positionAbs
.left
,
1507 x2
= x1
+ this.helperProportions
.width
,
1508 y1
= this.positionAbs
.top
,
1509 y2
= y1
+ this.helperProportions
.height
,
1513 b
= t
+ item
.height
,
1514 dyClick
= this.offset
.click
.top
,
1515 dxClick
= this.offset
.click
.left
,
1516 isOverElementHeight
= ( this.options
.axis
=== "x" ) || ( ( y1
+ dyClick
) > t
&& ( y1
+ dyClick
) < b
),
1517 isOverElementWidth
= ( this.options
.axis
=== "y" ) || ( ( x1
+ dxClick
) > l
&& ( x1
+ dxClick
) < r
),
1518 isOverElement
= isOverElementHeight
&& isOverElementWidth
;
1520 if ( this.options
.tolerance
=== "pointer" ||
1521 this.options
.forcePointerForContainers
||
1522 (this.options
.tolerance
!== "pointer" && this.helperProportions
[this.floating
? "width" : "height"] > item
[this.floating
? "width" : "height"])
1524 return isOverElement
;
1527 return (l
< x1
+ (this.helperProportions
.width
/ 2) && // Right Half
1528 x2
- (this.helperProportions
.width
/ 2) < r
&& // Left Half
1529 t
< y1
+ (this.helperProportions
.height
/ 2) && // Bottom Half
1530 y2
- (this.helperProportions
.height
/ 2) < b
); // Top Half
1535 _intersectsWithPointer: function(item
) {
1537 var isOverElementHeight
= (this.options
.axis
=== "x") || isOverAxis(this.positionAbs
.top
+ this.offset
.click
.top
, item
.top
, item
.height
),
1538 isOverElementWidth
= (this.options
.axis
=== "y") || isOverAxis(this.positionAbs
.left
+ this.offset
.click
.left
, item
.left
, item
.width
),
1539 isOverElement
= isOverElementHeight
&& isOverElementWidth
,
1540 verticalDirection
= this._getDragVerticalDirection(),
1541 horizontalDirection
= this._getDragHorizontalDirection();
1543 if (!isOverElement
) {
1547 return this.floating
?
1548 ( ((horizontalDirection
&& horizontalDirection
=== "right") || verticalDirection
=== "down") ? 2 : 1 )
1549 : ( verticalDirection
&& (verticalDirection
=== "down" ? 2 : 1) );
1553 _intersectsWithSides: function(item
) {
1555 var isOverBottomHalf
= isOverAxis(this.positionAbs
.top
+ this.offset
.click
.top
, item
.top
+ (item
.height
/2), item
.height
),
1556 isOverRightHalf
= isOverAxis(this.positionAbs
.left
+ this.offset
.click
.left
, item
.left
+ (item
.width
/2), item
.width
),
1557 verticalDirection
= this._getDragVerticalDirection(),
1558 horizontalDirection
= this._getDragHorizontalDirection();
1560 if (this.floating
&& horizontalDirection
) {
1561 return ((horizontalDirection
=== "right" && isOverRightHalf
) || (horizontalDirection
=== "left" && !isOverRightHalf
));
1563 return verticalDirection
&& ((verticalDirection
=== "down" && isOverBottomHalf
) || (verticalDirection
=== "up" && !isOverBottomHalf
));
1568 _getDragVerticalDirection: function() {
1569 var delta
= this.positionAbs
.top
- this.lastPositionAbs
.top
;
1570 return delta
!== 0 && (delta
> 0 ? "down" : "up");
1573 _getDragHorizontalDirection: function() {
1574 var delta
= this.positionAbs
.left
- this.lastPositionAbs
.left
;
1575 return delta
!== 0 && (delta
> 0 ? "right" : "left");
1578 refresh: function(event
) {
1579 this._refreshItems(event
);
1580 this.refreshPositions();
1584 _connectWith: function() {
1585 var options
= this.options
;
1586 return options
.connectWith
.constructor === String
? [options
.connectWith
] : options
.connectWith
;
1589 _getItemsAsjQuery: function(connected
) {
1591 var i
, j
, cur
, inst
,
1594 connectWith
= this._connectWith();
1596 if(connectWith
&& connected
) {
1597 for (i
= connectWith
.length
- 1; i
>= 0; i
--){
1598 cur
= $(connectWith
[i
]);
1599 for ( j
= cur
.length
- 1; j
>= 0; j
--){
1600 inst
= $.data(cur
[j
], this.widgetFullName
);
1601 if(inst
&& inst
!== this && !inst
.options
.disabled
) {
1602 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
]);
1608 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]);
1610 for (i
= queries
.length
- 1; i
>= 0; i
--){
1611 queries
[i
][0].each(function() {
1620 _removeCurrentsFromItems: function() {
1622 var list
= this.currentItem
.find(":data(" + this.widgetName
+ "-item)");
1624 this.items
= $.grep(this.items
, function (item
) {
1625 for (var j
=0; j
< list
.length
; j
++) {
1626 if(list
[j
] === item
.item
[0]) {
1635 _refreshItems: function(event
) {
1638 this.containers
= [this];
1640 var i
, j
, cur
, inst
, targetData
, _queries
, item
, queriesLength
,
1642 queries
= [[$.isFunction(this.options
.items
) ? this.options
.items
.call(this.element
[0], event
, { item
: this.currentItem
}) : $(this.options
.items
, this.element
), this]],
1643 connectWith
= this._connectWith();
1645 if(connectWith
&& this.ready
) { //Shouldn't be run the first time through due to massive slow-down
1646 for (i
= connectWith
.length
- 1; i
>= 0; i
--){
1647 cur
= $(connectWith
[i
]);
1648 for (j
= cur
.length
- 1; j
>= 0; j
--){
1649 inst
= $.data(cur
[j
], this.widgetFullName
);
1650 if(inst
&& inst
!== this && !inst
.options
.disabled
) {
1651 queries
.push([$.isFunction(inst
.options
.items
) ? inst
.options
.items
.call(inst
.element
[0], event
, { item
: this.currentItem
}) : $(inst
.options
.items
, inst
.element
), inst
]);
1652 this.containers
.push(inst
);
1658 for (i
= queries
.length
- 1; i
>= 0; i
--) {
1659 targetData
= queries
[i
][1];
1660 _queries
= queries
[i
][0];
1662 for (j
=0, queriesLength
= _queries
.length
; j
< queriesLength
; j
++) {
1663 item
= $(_queries
[j
]);
1665 item
.data(this.widgetName
+ "-item", targetData
); // Data for target checking (mouse manager)
1669 instance
: targetData
,
1670 width
: 0, height
: 0,
1678 refreshPositions: function(fast
) {
1680 //This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change
1681 if(this.offsetParent
&& this.helper
) {
1682 this.offset
.parent
= this._getParentOffset();
1687 for (i
= this.items
.length
- 1; i
>= 0; i
--){
1688 item
= this.items
[i
];
1690 //We ignore calculating positions of all connected containers when we're not over them
1691 if(item
.instance
!== this.currentContainer
&& this.currentContainer
&& item
.item
[0] !== this.currentItem
[0]) {
1695 t
= this.options
.toleranceElement
? $(this.options
.toleranceElement
, item
.item
) : item
.item
;
1698 item
.width
= t
.outerWidth();
1699 item
.height
= t
.outerHeight();
1707 if(this.options
.custom
&& this.options
.custom
.refreshContainers
) {
1708 this.options
.custom
.refreshContainers
.call(this);
1710 for (i
= this.containers
.length
- 1; i
>= 0; i
--){
1711 p
= this.containers
[i
].element
.offset();
1712 this.containers
[i
].containerCache
.left
= p
.left
;
1713 this.containers
[i
].containerCache
.top
= p
.top
;
1714 this.containers
[i
].containerCache
.width
= this.containers
[i
].element
.outerWidth();
1715 this.containers
[i
].containerCache
.height
= this.containers
[i
].element
.outerHeight();
1722 _createPlaceholder: function(that
) {
1723 that
= that
|| this;
1727 if(!o
.placeholder
|| o
.placeholder
.constructor === String
) {
1728 className
= o
.placeholder
;
1730 element: function() {
1732 var nodeName
= that
.currentItem
[0].nodeName
.toLowerCase(),
1733 element
= $( "<" + nodeName
+ ">", that
.document
[0] )
1734 .addClass(className
|| that
.currentItem
[0].className
+" ui-sortable-placeholder")
1735 .removeClass("ui-sortable-helper");
1737 if ( nodeName
=== "tr" ) {
1738 that
.currentItem
.children().each(function() {
1739 $( "<td> </td>", that
.document
[0] )
1740 .attr( "colspan", $( this ).attr( "colspan" ) || 1 )
1741 .appendTo( element
);
1743 } else if ( nodeName
=== "img" ) {
1744 element
.attr( "src", that
.currentItem
.attr( "src" ) );
1748 element
.css( "visibility", "hidden" );
1753 update: function(container
, p
) {
1755 // 1. If a className is set as 'placeholder option, we don't force sizes - the class is responsible for that
1756 // 2. The option 'forcePlaceholderSize can be enabled to force it even if a class name is specified
1757 if(className
&& !o
.forcePlaceholderSize
) {
1761 //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
1762 if(!p
.height()) { p
.height(that
.currentItem
.innerHeight() - parseInt(that
.currentItem
.css("paddingTop")||0, 10) - parseInt(that
.currentItem
.css("paddingBottom")||0, 10)); }
1763 if(!p
.width()) { p
.width(that
.currentItem
.innerWidth() - parseInt(that
.currentItem
.css("paddingLeft")||0, 10) - parseInt(that
.currentItem
.css("paddingRight")||0, 10)); }
1768 //Create the placeholder
1769 that
.placeholder
= $(o
.placeholder
.element
.call(that
.element
, that
.currentItem
));
1771 //Append it after the actual current item
1772 that
.currentItem
.after(that
.placeholder
);
1774 //Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317)
1775 o
.placeholder
.update(that
, that
.placeholder
);
1779 _contactContainers: function(event
) {
1780 var i
, j
, dist
, itemWithLeastDistance
, posProperty
, sizeProperty
, base
, cur
, nearBottom
, floating
,
1781 innermostContainer
= null,
1782 innermostIndex
= null;
1784 // get innermost container that intersects with item
1785 for (i
= this.containers
.length
- 1; i
>= 0; i
--) {
1787 // never consider a container that's located within the item itself
1788 if($.contains(this.currentItem
[0], this.containers
[i
].element
[0])) {
1792 if(this._intersectsWith(this.containers
[i
].containerCache
)) {
1794 // if we've already found a container and it's more "inner" than this, then continue
1795 if(innermostContainer
&& $.contains(this.containers
[i
].element
[0], innermostContainer
.element
[0])) {
1799 innermostContainer
= this.containers
[i
];
1803 // container doesn't intersect. trigger "out" event if necessary
1804 if(this.containers
[i
].containerCache
.over
) {
1805 this.containers
[i
]._trigger("out", event
, this._uiHash(this));
1806 this.containers
[i
].containerCache
.over
= 0;
1812 // if no intersecting containers found, return
1813 if(!innermostContainer
) {
1817 // move the item into the container if it's not there already
1818 if(this.containers
.length
=== 1) {
1819 if (!this.containers
[innermostIndex
].containerCache
.over
) {
1820 this.containers
[innermostIndex
]._trigger("over", event
, this._uiHash(this));
1821 this.containers
[innermostIndex
].containerCache
.over
= 1;
1825 //When entering a new container, we will find the item with the least distance and append our item near it
1827 itemWithLeastDistance
= null;
1828 floating
= innermostContainer
.floating
|| isFloating(this.currentItem
);
1829 posProperty
= floating
? "left" : "top";
1830 sizeProperty
= floating
? "width" : "height";
1831 base
= this.positionAbs
[posProperty
] + this.offset
.click
[posProperty
];
1832 for (j
= this.items
.length
- 1; j
>= 0; j
--) {
1833 if(!$.contains(this.containers
[innermostIndex
].element
[0], this.items
[j
].item
[0])) {
1836 if(this.items
[j
].item
[0] === this.currentItem
[0]) {
1839 if (floating
&& !isOverAxis(this.positionAbs
.top
+ this.offset
.click
.top
, this.items
[j
].top
, this.items
[j
].height
)) {
1842 cur
= this.items
[j
].item
.offset()[posProperty
];
1844 if(Math
.abs(cur
- base
) > Math
.abs(cur
+ this.items
[j
][sizeProperty
] - base
)){
1846 cur
+= this.items
[j
][sizeProperty
];
1849 if(Math
.abs(cur
- base
) < dist
) {
1850 dist
= Math
.abs(cur
- base
); itemWithLeastDistance
= this.items
[j
];
1851 this.direction
= nearBottom
? "up": "down";
1855 //Check if dropOnEmpty is enabled
1856 if(!itemWithLeastDistance
&& !this.options
.dropOnEmpty
) {
1860 if(this.currentContainer
=== this.containers
[innermostIndex
]) {
1864 itemWithLeastDistance
? this._rearrange(event
, itemWithLeastDistance
, null, true) : this._rearrange(event
, null, this.containers
[innermostIndex
].element
, true);
1865 this._trigger("change", event
, this._uiHash());
1866 this.containers
[innermostIndex
]._trigger("change", event
, this._uiHash(this));
1867 this.currentContainer
= this.containers
[innermostIndex
];
1869 //Update the placeholder
1870 this.options
.placeholder
.update(this.currentContainer
, this.placeholder
);
1872 this.containers
[innermostIndex
]._trigger("over", event
, this._uiHash(this));
1873 this.containers
[innermostIndex
].containerCache
.over
= 1;
1879 _createHelper: function(event
) {
1881 var o
= this.options
,
1882 helper
= $.isFunction(o
.helper
) ? $(o
.helper
.apply(this.element
[0], [event
, this.currentItem
])) : (o
.helper
=== "clone" ? this.currentItem
.clone() : this.currentItem
);
1884 //Add the helper to the DOM if that didn't happen already
1885 if(!helper
.parents("body").length
) {
1886 $(o
.appendTo
!== "parent" ? o
.appendTo
: this.currentItem
[0].parentNode
)[0].appendChild(helper
[0]);
1889 if(helper
[0] === this.currentItem
[0]) {
1890 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") };
1893 if(!helper
[0].style
.width
|| o
.forceHelperSize
) {
1894 helper
.width(this.currentItem
.width());
1896 if(!helper
[0].style
.height
|| o
.forceHelperSize
) {
1897 helper
.height(this.currentItem
.height());
1904 _adjustOffsetFromHelper: function(obj
) {
1905 if (typeof obj
=== "string") {
1906 obj
= obj
.split(" ");
1908 if ($.isArray(obj
)) {
1909 obj
= {left
: +obj
[0], top
: +obj
[1] || 0};
1911 if ("left" in obj
) {
1912 this.offset
.click
.left
= obj
.left
+ this.margins
.left
;
1914 if ("right" in obj
) {
1915 this.offset
.click
.left
= this.helperProportions
.width
- obj
.right
+ this.margins
.left
;
1918 this.offset
.click
.top
= obj
.top
+ this.margins
.top
;
1920 if ("bottom" in obj
) {
1921 this.offset
.click
.top
= this.helperProportions
.height
- obj
.bottom
+ this.margins
.top
;
1925 _getParentOffset: function() {
1928 //Get the offsetParent and cache its position
1929 this.offsetParent
= this.helper
.offsetParent();
1930 var po
= this.offsetParent
.offset();
1932 // This is a special case where we need to modify a offset calculated on start, since the following happened:
1933 // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
1934 // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
1935 // the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
1936 if(this.cssPosition
=== "absolute" && this.scrollParent
[0] !== document
&& $.contains(this.scrollParent
[0], this.offsetParent
[0])) {
1937 po
.left
+= this.scrollParent
.scrollLeft();
1938 po
.top
+= this.scrollParent
.scrollTop();
1941 // This needs to be actually done for all browsers, since pageX/pageY includes this information
1942 // with an ugly IE fix
1943 if( this.offsetParent
[0] === document
.body
|| (this.offsetParent
[0].tagName
&& this.offsetParent
[0].tagName
.toLowerCase() === "html" && $.ui
.ie
)) {
1944 po
= { top
: 0, left
: 0 };
1948 top
: po
.top
+ (parseInt(this.offsetParent
.css("borderTopWidth"),10) || 0),
1949 left
: po
.left
+ (parseInt(this.offsetParent
.css("borderLeftWidth"),10) || 0)
1954 _getRelativeOffset: function() {
1956 if(this.cssPosition
=== "relative") {
1957 var p
= this.currentItem
.position();
1959 top
: p
.top
- (parseInt(this.helper
.css("top"),10) || 0) + this.scrollParent
.scrollTop(),
1960 left
: p
.left
- (parseInt(this.helper
.css("left"),10) || 0) + this.scrollParent
.scrollLeft()
1963 return { top
: 0, left
: 0 };
1968 _cacheMargins: function() {
1970 left
: (parseInt(this.currentItem
.css("marginLeft"),10) || 0),
1971 top
: (parseInt(this.currentItem
.css("marginTop"),10) || 0)
1975 _cacheHelperProportions: function() {
1976 this.helperProportions
= {
1977 width
: this.helper
.outerWidth(),
1978 height
: this.helper
.outerHeight()
1982 _setContainment: function() {
1986 if(o
.containment
=== "parent") {
1987 o
.containment
= this.helper
[0].parentNode
;
1989 if(o
.containment
=== "document" || o
.containment
=== "window") {
1990 this.containment
= [
1991 0 - this.offset
.relative
.left
- this.offset
.parent
.left
,
1992 0 - this.offset
.relative
.top
- this.offset
.parent
.top
,
1993 $(o
.containment
=== "document" ? document
: window
).width() - this.helperProportions
.width
- this.margins
.left
,
1994 ($(o
.containment
=== "document" ? document
: window
).height() || document
.body
.parentNode
.scrollHeight
) - this.helperProportions
.height
- this.margins
.top
1998 if(!(/^(document|window|parent)$/).test(o
.containment
)) {
1999 ce
= $(o
.containment
)[0];
2000 co
= $(o
.containment
).offset();
2001 over
= ($(ce
).css("overflow") !== "hidden");
2003 this.containment
= [
2004 co
.left
+ (parseInt($(ce
).css("borderLeftWidth"),10) || 0) + (parseInt($(ce
).css("paddingLeft"),10) || 0) - this.margins
.left
,
2005 co
.top
+ (parseInt($(ce
).css("borderTopWidth"),10) || 0) + (parseInt($(ce
).css("paddingTop"),10) || 0) - this.margins
.top
,
2006 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
,
2007 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
2013 _convertPositionTo: function(d
, pos
) {
2016 pos
= this.position
;
2018 var mod
= d
=== "absolute" ? 1 : -1,
2019 scroll
= this.cssPosition
=== "absolute" && !(this.scrollParent
[0] !== document
&& $.contains(this.scrollParent
[0], this.offsetParent
[0])) ? this.offsetParent
: this.scrollParent
,
2020 scrollIsRootNode
= (/(html|body)/i).test(scroll
[0].tagName
);
2024 pos
.top
+ // The absolute mouse position
2025 this.offset
.relative
.top
* mod
+ // Only for relative positioned nodes: Relative offset from element to offset parent
2026 this.offset
.parent
.top
* mod
- // The offsetParent's offset without borders (offset + border)
2027 ( ( this.cssPosition
=== "fixed" ? -this.scrollParent
.scrollTop() : ( scrollIsRootNode
? 0 : scroll
.scrollTop() ) ) * mod
)
2030 pos
.left
+ // The absolute mouse position
2031 this.offset
.relative
.left
* mod
+ // Only for relative positioned nodes: Relative offset from element to offset parent
2032 this.offset
.parent
.left
* mod
- // The offsetParent's offset without borders (offset + border)
2033 ( ( this.cssPosition
=== "fixed" ? -this.scrollParent
.scrollLeft() : scrollIsRootNode
? 0 : scroll
.scrollLeft() ) * mod
)
2039 _generatePosition: function(event
) {
2043 pageX
= event
.pageX
,
2044 pageY
= event
.pageY
,
2045 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
);
2047 // This is another very weird special case that only happens for relative elements:
2048 // 1. If the css position is relative
2049 // 2. and the scroll parent is the document or similar to the offset parent
2050 // we have to refresh the relative offset during the scroll so there are no jumps
2051 if(this.cssPosition
=== "relative" && !(this.scrollParent
[0] !== document
&& this.scrollParent
[0] !== this.offsetParent
[0])) {
2052 this.offset
.relative
= this._getRelativeOffset();
2056 * - Position constraining -
2057 * Constrain the position to a mix of grid, containment.
2060 if(this.originalPosition
) { //If we are not dragging yet, we won't check for options
2062 if(this.containment
) {
2063 if(event
.pageX
- this.offset
.click
.left
< this.containment
[0]) {
2064 pageX
= this.containment
[0] + this.offset
.click
.left
;
2066 if(event
.pageY
- this.offset
.click
.top
< this.containment
[1]) {
2067 pageY
= this.containment
[1] + this.offset
.click
.top
;
2069 if(event
.pageX
- this.offset
.click
.left
> this.containment
[2]) {
2070 pageX
= this.containment
[2] + this.offset
.click
.left
;
2072 if(event
.pageY
- this.offset
.click
.top
> this.containment
[3]) {
2073 pageY
= this.containment
[3] + this.offset
.click
.top
;
2078 top
= this.originalPageY
+ Math
.round((pageY
- this.originalPageY
) / o
.grid
[1]) * o
.grid
[1];
2079 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
;
2081 left
= this.originalPageX
+ Math
.round((pageX
- this.originalPageX
) / o
.grid
[0]) * o
.grid
[0];
2082 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
;
2089 pageY
- // The absolute mouse position
2090 this.offset
.click
.top
- // Click offset (relative to the element)
2091 this.offset
.relative
.top
- // Only for relative positioned nodes: Relative offset from element to offset parent
2092 this.offset
.parent
.top
+ // The offsetParent's offset without borders (offset + border)
2093 ( ( this.cssPosition
=== "fixed" ? -this.scrollParent
.scrollTop() : ( scrollIsRootNode
? 0 : scroll
.scrollTop() ) ))
2096 pageX
- // The absolute mouse position
2097 this.offset
.click
.left
- // Click offset (relative to the element)
2098 this.offset
.relative
.left
- // Only for relative positioned nodes: Relative offset from element to offset parent
2099 this.offset
.parent
.left
+ // The offsetParent's offset without borders (offset + border)
2100 ( ( this.cssPosition
=== "fixed" ? -this.scrollParent
.scrollLeft() : scrollIsRootNode
? 0 : scroll
.scrollLeft() ))
2106 _rearrange: function(event
, i
, a
, hardRefresh
) {
2108 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
));
2110 //Various things done here to improve the performance:
2111 // 1. we create a setTimeout, that calls refreshPositions
2112 // 2. on the instance, we have a counter variable, that get's higher after every append
2113 // 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same
2114 // 4. this lets only the last addition to the timeout stack through
2115 this.counter
= this.counter
? ++this.counter
: 1;
2116 var counter
= this.counter
;
2118 this._delay(function() {
2119 if(counter
=== this.counter
) {
2120 this.refreshPositions(!hardRefresh
); //Precompute after each DOM insertion, NOT on mousemove
2126 _clear: function(event
, noPropagation
) {
2128 this.reverting
= false;
2129 // We delay all events that have to be triggered to after the point where the placeholder has been removed and
2130 // everything else normalized again
2132 delayedTriggers
= [];
2134 // We first have to update the dom position of the actual currentItem
2135 // Note: don't do it if the current item is already removed (by a user), or it gets reappended (see #4088)
2136 if(!this._noFinalSort
&& this.currentItem
.parent().length
) {
2137 this.placeholder
.before(this.currentItem
);
2139 this._noFinalSort
= null;
2141 if(this.helper
[0] === this.currentItem
[0]) {
2142 for(i
in this._storedCSS
) {
2143 if(this._storedCSS
[i
] === "auto" || this._storedCSS
[i
] === "static") {
2144 this._storedCSS
[i
] = "";
2147 this.currentItem
.css(this._storedCSS
).removeClass("ui-sortable-helper");
2149 this.currentItem
.show();
2152 if(this.fromOutside
&& !noPropagation
) {
2153 delayedTriggers
.push(function(event
) { this._trigger("receive", event
, this._uiHash(this.fromOutside
)); });
2155 if((this.fromOutside
|| this.domPosition
.prev
!== this.currentItem
.prev().not(".ui-sortable-helper")[0] || this.domPosition
.parent
!== this.currentItem
.parent()[0]) && !noPropagation
) {
2156 delayedTriggers
.push(function(event
) { this._trigger("update", event
, this._uiHash()); }); //Trigger update callback if the DOM position has changed
2159 // Check if the items Container has Changed and trigger appropriate
2161 if (this !== this.currentContainer
) {
2162 if(!noPropagation
) {
2163 delayedTriggers
.push(function(event
) { this._trigger("remove", event
, this._uiHash()); });
2164 delayedTriggers
.push((function(c
) { return function(event
) { c
._trigger("receive", event
, this._uiHash(this)); }; }).call(this, this.currentContainer
));
2165 delayedTriggers
.push((function(c
) { return function(event
) { c
._trigger("update", event
, this._uiHash(this)); }; }).call(this, this.currentContainer
));
2170 //Post events to containers
2171 for (i
= this.containers
.length
- 1; i
>= 0; i
--){
2172 if(!noPropagation
) {
2173 delayedTriggers
.push((function(c
) { return function(event
) { c
._trigger("deactivate", event
, this._uiHash(this)); }; }).call(this, this.containers
[i
]));
2175 if(this.containers
[i
].containerCache
.over
) {
2176 delayedTriggers
.push((function(c
) { return function(event
) { c
._trigger("out", event
, this._uiHash(this)); }; }).call(this, this.containers
[i
]));
2177 this.containers
[i
].containerCache
.over
= 0;
2181 //Do what was originally in plugins
2182 if ( this.storedCursor
) {
2183 this.document
.find( "body" ).css( "cursor", this.storedCursor
);
2184 this.storedStylesheet
.remove();
2186 if(this._storedOpacity
) {
2187 this.helper
.css("opacity", this._storedOpacity
);
2189 if(this._storedZIndex
) {
2190 this.helper
.css("zIndex", this._storedZIndex
=== "auto" ? "" : this._storedZIndex
);
2193 this.dragging
= false;
2194 if(this.cancelHelperRemoval
) {
2195 if(!noPropagation
) {
2196 this._trigger("beforeStop", event
, this._uiHash());
2197 for (i
=0; i
< delayedTriggers
.length
; i
++) {
2198 delayedTriggers
[i
].call(this, event
);
2199 } //Trigger all delayed events
2200 this._trigger("stop", event
, this._uiHash());
2203 this.fromOutside
= false;
2207 if(!noPropagation
) {
2208 this._trigger("beforeStop", event
, this._uiHash());
2211 //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
2212 this.placeholder
[0].parentNode
.removeChild(this.placeholder
[0]);
2214 if(this.helper
[0] !== this.currentItem
[0]) {
2215 this.helper
.remove();
2219 if(!noPropagation
) {
2220 for (i
=0; i
< delayedTriggers
.length
; i
++) {
2221 delayedTriggers
[i
].call(this, event
);
2222 } //Trigger all delayed events
2223 this._trigger("stop", event
, this._uiHash());
2226 this.fromOutside
= false;
2231 _trigger: function() {
2232 if ($.Widget
.prototype._trigger
.apply(this, arguments
) === false) {
2237 _uiHash: function(_inst
) {
2238 var inst
= _inst
|| this;
2240 helper
: inst
.helper
,
2241 placeholder
: inst
.placeholder
|| $([]),
2242 position
: inst
.position
,
2243 originalPosition
: inst
.originalPosition
,
2244 offset
: inst
.positionAbs
,
2245 item
: inst
.currentItem
,
2246 sender
: _inst
? _inst
.element
: null
2253 (function( $, undefined ) {
2259 hideProps
.height
= hideProps
.paddingTop
= hideProps
.paddingBottom
=
2260 hideProps
.borderTopWidth
= hideProps
.borderBottomWidth
= "hide";
2261 showProps
.height
= showProps
.paddingTop
= showProps
.paddingBottom
=
2262 showProps
.borderTopWidth
= showProps
.borderBottomWidth
= "show";
2264 $.widget( "ui.accordion", {
2271 header
: "> li > :first-child,> :not(li):even",
2272 heightStyle
: "auto",
2274 activeHeader
: "ui-icon-triangle-1-s",
2275 header
: "ui-icon-triangle-1-e"
2280 beforeActivate
: null
2283 _create: function() {
2284 var options
= this.options
;
2285 this.prevShow
= this.prevHide
= $();
2286 this.element
.addClass( "ui-accordion ui-widget ui-helper-reset" )
2288 .attr( "role", "tablist" );
2290 // don't allow collapsible: false and active: false / null
2291 if ( !options
.collapsible
&& (options
.active
=== false || options
.active
== null) ) {
2295 this._processPanels();
2296 // handle negative values
2297 if ( options
.active
< 0 ) {
2298 options
.active
+= this.headers
.length
;
2303 _getCreateEventData: function() {
2305 header
: this.active
,
2306 panel
: !this.active
.length
? $() : this.active
.next(),
2307 content
: !this.active
.length
? $() : this.active
.next()
2311 _createIcons: function() {
2312 var icons
= this.options
.icons
;
2315 .addClass( "ui-accordion-header-icon ui-icon " + icons
.header
)
2316 .prependTo( this.headers
);
2317 this.active
.children( ".ui-accordion-header-icon" )
2318 .removeClass( icons
.header
)
2319 .addClass( icons
.activeHeader
);
2320 this.headers
.addClass( "ui-accordion-icons" );
2324 _destroyIcons: function() {
2326 .removeClass( "ui-accordion-icons" )
2327 .children( ".ui-accordion-header-icon" )
2331 _destroy: function() {
2334 // clean up main element
2336 .removeClass( "ui-accordion ui-widget ui-helper-reset" )
2337 .removeAttr( "role" );
2341 .removeClass( "ui-accordion-header ui-accordion-header-active ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top" )
2342 .removeAttr( "role" )
2343 .removeAttr( "aria-selected" )
2344 .removeAttr( "aria-controls" )
2345 .removeAttr( "tabIndex" )
2347 if ( /^ui-accordion/.test( this.id
) ) {
2348 this.removeAttribute( "id" );
2351 this._destroyIcons();
2353 // clean up content panels
2354 contents
= this.headers
.next()
2355 .css( "display", "" )
2356 .removeAttr( "role" )
2357 .removeAttr( "aria-expanded" )
2358 .removeAttr( "aria-hidden" )
2359 .removeAttr( "aria-labelledby" )
2360 .removeClass( "ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-state-disabled" )
2362 if ( /^ui-accordion/.test( this.id
) ) {
2363 this.removeAttribute( "id" );
2366 if ( this.options
.heightStyle
!== "content" ) {
2367 contents
.css( "height", "" );
2371 _setOption: function( key
, value
) {
2372 if ( key
=== "active" ) {
2373 // _activate() will handle invalid values and update this.options
2374 this._activate( value
);
2378 if ( key
=== "event" ) {
2379 if ( this.options
.event
) {
2380 this._off( this.headers
, this.options
.event
);
2382 this._setupEvents( value
);
2385 this._super( key
, value
);
2387 // setting collapsible: false while collapsed; open first panel
2388 if ( key
=== "collapsible" && !value
&& this.options
.active
=== false ) {
2389 this._activate( 0 );
2392 if ( key
=== "icons" ) {
2393 this._destroyIcons();
2395 this._createIcons();
2399 // #5332 - opacity doesn't cascade to positioned elements in IE
2400 // so we need to add the disabled class to the headers and panels
2401 if ( key
=== "disabled" ) {
2402 this.headers
.add( this.headers
.next() )
2403 .toggleClass( "ui-state-disabled", !!value
);
2407 _keydown: function( event
) {
2408 /*jshint maxcomplexity:15*/
2409 if ( event
.altKey
|| event
.ctrlKey
) {
2413 var keyCode
= $.ui
.keyCode
,
2414 length
= this.headers
.length
,
2415 currentIndex
= this.headers
.index( event
.target
),
2418 switch ( event
.keyCode
) {
2421 toFocus
= this.headers
[ ( currentIndex
+ 1 ) % length
];
2425 toFocus
= this.headers
[ ( currentIndex
- 1 + length
) % length
];
2429 this._eventHandler( event
);
2432 toFocus
= this.headers
[ 0 ];
2435 toFocus
= this.headers
[ length
- 1 ];
2440 $( event
.target
).attr( "tabIndex", -1 );
2441 $( toFocus
).attr( "tabIndex", 0 );
2443 event
.preventDefault();
2447 _panelKeyDown : function( event
) {
2448 if ( event
.keyCode
=== $.ui
.keyCode
.UP
&& event
.ctrlKey
) {
2449 $( event
.currentTarget
).prev().focus();
2453 refresh: function() {
2454 var options
= this.options
;
2455 this._processPanels();
2457 // was collapsed or no panel
2458 if ( ( options
.active
=== false && options
.collapsible
=== true ) || !this.headers
.length
) {
2459 options
.active
= false;
2461 // active false only when collapsible is true
2462 } else if ( options
.active
=== false ) {
2463 this._activate( 0 );
2464 // was active, but active panel is gone
2465 } else if ( this.active
.length
&& !$.contains( this.element
[ 0 ], this.active
[ 0 ] ) ) {
2466 // all remaining panel are disabled
2467 if ( this.headers
.length
=== this.headers
.find(".ui-state-disabled").length
) {
2468 options
.active
= false;
2470 // activate previous panel
2472 this._activate( Math
.max( 0, options
.active
- 1 ) );
2474 // was active, active panel still exists
2476 // make sure active index is correct
2477 options
.active
= this.headers
.index( this.active
);
2480 this._destroyIcons();
2485 _processPanels: function() {
2486 this.headers
= this.element
.find( this.options
.header
)
2487 .addClass( "ui-accordion-header ui-helper-reset ui-state-default ui-corner-all" );
2490 .addClass( "ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom" )
2491 .filter(":not(.ui-accordion-content-active)")
2495 _refresh: function() {
2497 options
= this.options
,
2498 heightStyle
= options
.heightStyle
,
2499 parent
= this.element
.parent(),
2500 accordionId
= this.accordionId
= "ui-accordion-" +
2501 (this.element
.attr( "id" ) || ++uid
);
2503 this.active
= this._findActive( options
.active
)
2504 .addClass( "ui-accordion-header-active ui-state-active ui-corner-top" )
2505 .removeClass( "ui-corner-all" );
2507 .addClass( "ui-accordion-content-active" )
2511 .attr( "role", "tab" )
2512 .each(function( i
) {
2513 var header
= $( this ),
2514 headerId
= header
.attr( "id" ),
2515 panel
= header
.next(),
2516 panelId
= panel
.attr( "id" );
2518 headerId
= accordionId
+ "-header-" + i
;
2519 header
.attr( "id", headerId
);
2522 panelId
= accordionId
+ "-panel-" + i
;
2523 panel
.attr( "id", panelId
);
2525 header
.attr( "aria-controls", panelId
);
2526 panel
.attr( "aria-labelledby", headerId
);
2529 .attr( "role", "tabpanel" );
2534 "aria-selected": "false",
2539 "aria-expanded": "false",
2540 "aria-hidden": "true"
2544 // make sure at least one header is in the tab order
2545 if ( !this.active
.length
) {
2546 this.headers
.eq( 0 ).attr( "tabIndex", 0 );
2549 "aria-selected": "true",
2554 "aria-expanded": "true",
2555 "aria-hidden": "false"
2559 this._createIcons();
2561 this._setupEvents( options
.event
);
2563 if ( heightStyle
=== "fill" ) {
2564 maxHeight
= parent
.height();
2565 this.element
.siblings( ":visible" ).each(function() {
2566 var elem
= $( this ),
2567 position
= elem
.css( "position" );
2569 if ( position
=== "absolute" || position
=== "fixed" ) {
2572 maxHeight
-= elem
.outerHeight( true );
2575 this.headers
.each(function() {
2576 maxHeight
-= $( this ).outerHeight( true );
2581 $( this ).height( Math
.max( 0, maxHeight
-
2582 $( this ).innerHeight() + $( this ).height() ) );
2584 .css( "overflow", "auto" );
2585 } else if ( heightStyle
=== "auto" ) {
2589 maxHeight
= Math
.max( maxHeight
, $( this ).css( "height", "" ).height() );
2591 .height( maxHeight
);
2595 _activate: function( index
) {
2596 var active
= this._findActive( index
)[ 0 ];
2598 // trying to activate the already active panel
2599 if ( active
=== this.active
[ 0 ] ) {
2603 // trying to collapse, simulate a click on the currently active header
2604 active
= active
|| this.active
[ 0 ];
2606 this._eventHandler({
2608 currentTarget
: active
,
2609 preventDefault
: $.noop
2613 _findActive: function( selector
) {
2614 return typeof selector
=== "number" ? this.headers
.eq( selector
) : $();
2617 _setupEvents: function( event
) {
2622 $.each( event
.split(" "), function( index
, eventName
) {
2623 events
[ eventName
] = "_eventHandler";
2627 this._off( this.headers
.add( this.headers
.next() ) );
2628 this._on( this.headers
, events
);
2629 this._on( this.headers
.next(), { keydown
: "_panelKeyDown" });
2630 this._hoverable( this.headers
);
2631 this._focusable( this.headers
);
2634 _eventHandler: function( event
) {
2635 var options
= this.options
,
2636 active
= this.active
,
2637 clicked
= $( event
.currentTarget
),
2638 clickedIsActive
= clicked
[ 0 ] === active
[ 0 ],
2639 collapsing
= clickedIsActive
&& options
.collapsible
,
2640 toShow
= collapsing
? $() : clicked
.next(),
2641 toHide
= active
.next(),
2645 newHeader
: collapsing
? $() : clicked
,
2649 event
.preventDefault();
2652 // click on active header, but not collapsible
2653 ( clickedIsActive
&& !options
.collapsible
) ||
2654 // allow canceling activation
2655 ( this._trigger( "beforeActivate", event
, eventData
) === false ) ) {
2659 options
.active
= collapsing
? false : this.headers
.index( clicked
);
2661 // when the call to ._toggle() comes after the class changes
2662 // it causes a very odd bug in IE 8 (see #6720)
2663 this.active
= clickedIsActive
? $() : clicked
;
2664 this._toggle( eventData
);
2667 // corner classes on the previously active header stay after the animation
2668 active
.removeClass( "ui-accordion-header-active ui-state-active" );
2669 if ( options
.icons
) {
2670 active
.children( ".ui-accordion-header-icon" )
2671 .removeClass( options
.icons
.activeHeader
)
2672 .addClass( options
.icons
.header
);
2675 if ( !clickedIsActive
) {
2677 .removeClass( "ui-corner-all" )
2678 .addClass( "ui-accordion-header-active ui-state-active ui-corner-top" );
2679 if ( options
.icons
) {
2680 clicked
.children( ".ui-accordion-header-icon" )
2681 .removeClass( options
.icons
.header
)
2682 .addClass( options
.icons
.activeHeader
);
2687 .addClass( "ui-accordion-content-active" );
2691 _toggle: function( data
) {
2692 var toShow
= data
.newPanel
,
2693 toHide
= this.prevShow
.length
? this.prevShow
: data
.oldPanel
;
2695 // handle activating a panel during the animation for another activation
2696 this.prevShow
.add( this.prevHide
).stop( true, true );
2697 this.prevShow
= toShow
;
2698 this.prevHide
= toHide
;
2700 if ( this.options
.animate
) {
2701 this._animate( toShow
, toHide
, data
);
2705 this._toggleComplete( data
);
2709 "aria-expanded": "false",
2710 "aria-hidden": "true"
2712 toHide
.prev().attr( "aria-selected", "false" );
2713 // if we're switching panels, remove the old header from the tab order
2714 // if we're opening from collapsed state, remove the previous header from the tab order
2715 // if we're collapsing, then keep the collapsing header in the tab order
2716 if ( toShow
.length
&& toHide
.length
) {
2717 toHide
.prev().attr( "tabIndex", -1 );
2718 } else if ( toShow
.length
) {
2719 this.headers
.filter(function() {
2720 return $( this ).attr( "tabIndex" ) === 0;
2722 .attr( "tabIndex", -1 );
2727 "aria-expanded": "true",
2728 "aria-hidden": "false"
2732 "aria-selected": "true",
2737 _animate: function( toShow
, toHide
, data
) {
2738 var total
, easing
, duration
,
2741 down
= toShow
.length
&&
2742 ( !toHide
.length
|| ( toShow
.index() < toHide
.index() ) ),
2743 animate
= this.options
.animate
|| {},
2744 options
= down
&& animate
.down
|| animate
,
2745 complete = function() {
2746 that
._toggleComplete( data
);
2749 if ( typeof options
=== "number" ) {
2752 if ( typeof options
=== "string" ) {
2755 // fall back from options to animation in case of partial down settings
2756 easing
= easing
|| options
.easing
|| animate
.easing
;
2757 duration
= duration
|| options
.duration
|| animate
.duration
;
2759 if ( !toHide
.length
) {
2760 return toShow
.animate( showProps
, duration
, easing
, complete
);
2762 if ( !toShow
.length
) {
2763 return toHide
.animate( hideProps
, duration
, easing
, complete
);
2766 total
= toShow
.show().outerHeight();
2767 toHide
.animate( hideProps
, {
2770 step: function( now
, fx
) {
2771 fx
.now
= Math
.round( now
);
2776 .animate( showProps
, {
2780 step: function( now
, fx
) {
2781 fx
.now
= Math
.round( now
);
2782 if ( fx
.prop
!== "height" ) {
2784 } else if ( that
.options
.heightStyle
!== "content" ) {
2785 fx
.now
= Math
.round( total
- toHide
.outerHeight() - adjust
);
2792 _toggleComplete: function( data
) {
2793 var toHide
= data
.oldPanel
;
2796 .removeClass( "ui-accordion-content-active" )
2798 .removeClass( "ui-corner-top" )
2799 .addClass( "ui-corner-all" );
2801 // Work around for rendering bug in IE (#5421)
2802 if ( toHide
.length
) {
2803 toHide
.parent()[0].className
= toHide
.parent()[0].className
;
2806 this._trigger( "activate", null, data
);
2811 (function( $, undefined ) {
2816 function getNextTabId() {
2820 function isLocal( anchor
) {
2821 return anchor
.hash
.length
> 1 &&
2822 decodeURIComponent( anchor
.href
.replace( rhash
, "" ) ) ===
2823 decodeURIComponent( location
.href
.replace( rhash
, "" ) );
2826 $.widget( "ui.tabs", {
2833 heightStyle
: "content",
2839 beforeActivate
: null,
2844 _create: function() {
2846 options
= this.options
;
2848 this.running
= false;
2851 .addClass( "ui-tabs ui-widget ui-widget-content ui-corner-all" )
2852 .toggleClass( "ui-tabs-collapsible", options
.collapsible
)
2853 // Prevent users from focusing disabled tabs via click
2854 .delegate( ".ui-tabs-nav > li", "mousedown" + this.eventNamespace
, function( event
) {
2855 if ( $( this ).is( ".ui-state-disabled" ) ) {
2856 event
.preventDefault();
2860 // Preventing the default action in mousedown doesn't prevent IE
2861 // from focusing the element, so if the anchor gets focused, blur.
2862 // We don't have to worry about focusing the previously focused
2863 // element since clicking on a non-focusable element should focus
2865 .delegate( ".ui-tabs-anchor", "focus" + this.eventNamespace
, function() {
2866 if ( $( this ).closest( "li" ).is( ".ui-state-disabled" ) ) {
2871 this._processTabs();
2872 options
.active
= this._initialActive();
2874 // Take disabling tabs via class attribute from HTML
2875 // into account and update option properly.
2876 if ( $.isArray( options
.disabled
) ) {
2877 options
.disabled
= $.unique( options
.disabled
.concat(
2878 $.map( this.tabs
.filter( ".ui-state-disabled" ), function( li
) {
2879 return that
.tabs
.index( li
);
2884 // check for length avoids error when initializing empty list
2885 if ( this.options
.active
!== false && this.anchors
.length
) {
2886 this.active
= this._findActive( options
.active
);
2893 if ( this.active
.length
) {
2894 this.load( options
.active
);
2898 _initialActive: function() {
2899 var active
= this.options
.active
,
2900 collapsible
= this.options
.collapsible
,
2901 locationHash
= location
.hash
.substring( 1 );
2903 if ( active
=== null ) {
2904 // check the fragment identifier in the URL
2905 if ( locationHash
) {
2906 this.tabs
.each(function( i
, tab
) {
2907 if ( $( tab
).attr( "aria-controls" ) === locationHash
) {
2914 // check for a tab marked active via a class
2915 if ( active
=== null ) {
2916 active
= this.tabs
.index( this.tabs
.filter( ".ui-tabs-active" ) );
2919 // no active tab, set to false
2920 if ( active
=== null || active
=== -1 ) {
2921 active
= this.tabs
.length
? 0 : false;
2925 // handle numbers: negative, out of range
2926 if ( active
!== false ) {
2927 active
= this.tabs
.index( this.tabs
.eq( active
) );
2928 if ( active
=== -1 ) {
2929 active
= collapsible
? false : 0;
2933 // don't allow collapsible: false and active: false
2934 if ( !collapsible
&& active
=== false && this.anchors
.length
) {
2941 _getCreateEventData: function() {
2944 panel
: !this.active
.length
? $() : this._getPanelForTab( this.active
)
2948 _tabKeydown: function( event
) {
2949 /*jshint maxcomplexity:15*/
2950 var focusedTab
= $( this.document
[0].activeElement
).closest( "li" ),
2951 selectedIndex
= this.tabs
.index( focusedTab
),
2952 goingForward
= true;
2954 if ( this._handlePageNav( event
) ) {
2958 switch ( event
.keyCode
) {
2959 case $.ui
.keyCode
.RIGHT
:
2960 case $.ui
.keyCode
.DOWN
:
2963 case $.ui
.keyCode
.UP
:
2964 case $.ui
.keyCode
.LEFT
:
2965 goingForward
= false;
2968 case $.ui
.keyCode
.END
:
2969 selectedIndex
= this.anchors
.length
- 1;
2971 case $.ui
.keyCode
.HOME
:
2974 case $.ui
.keyCode
.SPACE
:
2975 // Activate only, no collapsing
2976 event
.preventDefault();
2977 clearTimeout( this.activating
);
2978 this._activate( selectedIndex
);
2980 case $.ui
.keyCode
.ENTER
:
2981 // Toggle (cancel delayed activation, allow collapsing)
2982 event
.preventDefault();
2983 clearTimeout( this.activating
);
2984 // Determine if we should collapse or activate
2985 this._activate( selectedIndex
=== this.options
.active
? false : selectedIndex
);
2991 // Focus the appropriate tab, based on which key was pressed
2992 event
.preventDefault();
2993 clearTimeout( this.activating
);
2994 selectedIndex
= this._focusNextTab( selectedIndex
, goingForward
);
2996 // Navigating with control key will prevent automatic activation
2997 if ( !event
.ctrlKey
) {
2998 // Update aria-selected immediately so that AT think the tab is already selected.
2999 // Otherwise AT may confuse the user by stating that they need to activate the tab,
3000 // but the tab will already be activated by the time the announcement finishes.
3001 focusedTab
.attr( "aria-selected", "false" );
3002 this.tabs
.eq( selectedIndex
).attr( "aria-selected", "true" );
3004 this.activating
= this._delay(function() {
3005 this.option( "active", selectedIndex
);
3010 _panelKeydown: function( event
) {
3011 if ( this._handlePageNav( event
) ) {
3015 // Ctrl+up moves focus to the current tab
3016 if ( event
.ctrlKey
&& event
.keyCode
=== $.ui
.keyCode
.UP
) {
3017 event
.preventDefault();
3018 this.active
.focus();
3022 // Alt+page up/down moves focus to the previous/next tab (and activates)
3023 _handlePageNav: function( event
) {
3024 if ( event
.altKey
&& event
.keyCode
=== $.ui
.keyCode
.PAGE_UP
) {
3025 this._activate( this._focusNextTab( this.options
.active
- 1, false ) );
3028 if ( event
.altKey
&& event
.keyCode
=== $.ui
.keyCode
.PAGE_DOWN
) {
3029 this._activate( this._focusNextTab( this.options
.active
+ 1, true ) );
3034 _findNextTab: function( index
, goingForward
) {
3035 var lastTabIndex
= this.tabs
.length
- 1;
3037 function constrain() {
3038 if ( index
> lastTabIndex
) {
3042 index
= lastTabIndex
;
3047 while ( $.inArray( constrain(), this.options
.disabled
) !== -1 ) {
3048 index
= goingForward
? index
+ 1 : index
- 1;
3054 _focusNextTab: function( index
, goingForward
) {
3055 index
= this._findNextTab( index
, goingForward
);
3056 this.tabs
.eq( index
).focus();
3060 _setOption: function( key
, value
) {
3061 if ( key
=== "active" ) {
3062 // _activate() will handle invalid values and update this.options
3063 this._activate( value
);
3067 if ( key
=== "disabled" ) {
3068 // don't use the widget factory's disabled handling
3069 this._setupDisabled( value
);
3073 this._super( key
, value
);
3075 if ( key
=== "collapsible" ) {
3076 this.element
.toggleClass( "ui-tabs-collapsible", value
);
3077 // Setting collapsible: false while collapsed; open first panel
3078 if ( !value
&& this.options
.active
=== false ) {
3079 this._activate( 0 );
3083 if ( key
=== "event" ) {
3084 this._setupEvents( value
);
3087 if ( key
=== "heightStyle" ) {
3088 this._setupHeightStyle( value
);
3092 _tabId: function( tab
) {
3093 return tab
.attr( "aria-controls" ) || "ui-tabs-" + getNextTabId();
3096 _sanitizeSelector: function( hash
) {
3097 return hash
? hash
.replace( /[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g, "\\$&" ) : "";
3100 refresh: function() {
3101 var options
= this.options
,
3102 lis
= this.tablist
.children( ":has(a[href])" );
3104 // get disabled tabs from class attribute from HTML
3105 // this will get converted to a boolean if needed in _refresh()
3106 options
.disabled
= $.map( lis
.filter( ".ui-state-disabled" ), function( tab
) {
3107 return lis
.index( tab
);
3110 this._processTabs();
3112 // was collapsed or no tabs
3113 if ( options
.active
=== false || !this.anchors
.length
) {
3114 options
.active
= false;
3116 // was active, but active tab is gone
3117 } else if ( this.active
.length
&& !$.contains( this.tablist
[ 0 ], this.active
[ 0 ] ) ) {
3118 // all remaining tabs are disabled
3119 if ( this.tabs
.length
=== options
.disabled
.length
) {
3120 options
.active
= false;
3122 // activate previous tab
3124 this._activate( this._findNextTab( Math
.max( 0, options
.active
- 1 ), false ) );
3126 // was active, active tab still exists
3128 // make sure active index is correct
3129 options
.active
= this.tabs
.index( this.active
);
3135 _refresh: function() {
3136 this._setupDisabled( this.options
.disabled
);
3137 this._setupEvents( this.options
.event
);
3138 this._setupHeightStyle( this.options
.heightStyle
);
3140 this.tabs
.not( this.active
).attr({
3141 "aria-selected": "false",
3144 this.panels
.not( this._getPanelForTab( this.active
) )
3147 "aria-expanded": "false",
3148 "aria-hidden": "true"
3151 // Make sure one tab is in the tab order
3152 if ( !this.active
.length
) {
3153 this.tabs
.eq( 0 ).attr( "tabIndex", 0 );
3156 .addClass( "ui-tabs-active ui-state-active" )
3158 "aria-selected": "true",
3161 this._getPanelForTab( this.active
)
3164 "aria-expanded": "true",
3165 "aria-hidden": "false"
3170 _processTabs: function() {
3173 this.tablist
= this._getList()
3174 .addClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" )
3175 .attr( "role", "tablist" );
3177 this.tabs
= this.tablist
.find( "> li:has(a[href])" )
3178 .addClass( "ui-state-default ui-corner-top" )
3184 this.anchors
= this.tabs
.map(function() {
3185 return $( "a", this )[ 0 ];
3187 .addClass( "ui-tabs-anchor" )
3189 role
: "presentation",
3195 this.anchors
.each(function( i
, anchor
) {
3196 var selector
, panel
, panelId
,
3197 anchorId
= $( anchor
).uniqueId().attr( "id" ),
3198 tab
= $( anchor
).closest( "li" ),
3199 originalAriaControls
= tab
.attr( "aria-controls" );
3202 if ( isLocal( anchor
) ) {
3203 selector
= anchor
.hash
;
3204 panel
= that
.element
.find( that
._sanitizeSelector( selector
) );
3207 panelId
= that
._tabId( tab
);
3208 selector
= "#" + panelId
;
3209 panel
= that
.element
.find( selector
);
3210 if ( !panel
.length
) {
3211 panel
= that
._createPanel( panelId
);
3212 panel
.insertAfter( that
.panels
[ i
- 1 ] || that
.tablist
);
3214 panel
.attr( "aria-live", "polite" );
3217 if ( panel
.length
) {
3218 that
.panels
= that
.panels
.add( panel
);
3220 if ( originalAriaControls
) {
3221 tab
.data( "ui-tabs-aria-controls", originalAriaControls
);
3224 "aria-controls": selector
.substring( 1 ),
3225 "aria-labelledby": anchorId
3227 panel
.attr( "aria-labelledby", anchorId
);
3231 .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )
3232 .attr( "role", "tabpanel" );
3235 // allow overriding how to find the list for rare usage scenarios (#7715)
3236 _getList: function() {
3237 return this.element
.find( "ol,ul" ).eq( 0 );
3240 _createPanel: function( id
) {
3243 .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )
3244 .data( "ui-tabs-destroy", true );
3247 _setupDisabled: function( disabled
) {
3248 if ( $.isArray( disabled
) ) {
3249 if ( !disabled
.length
) {
3251 } else if ( disabled
.length
=== this.anchors
.length
) {
3257 for ( var i
= 0, li
; ( li
= this.tabs
[ i
] ); i
++ ) {
3258 if ( disabled
=== true || $.inArray( i
, disabled
) !== -1 ) {
3260 .addClass( "ui-state-disabled" )
3261 .attr( "aria-disabled", "true" );
3264 .removeClass( "ui-state-disabled" )
3265 .removeAttr( "aria-disabled" );
3269 this.options
.disabled
= disabled
;
3272 _setupEvents: function( event
) {
3274 click: function( event
) {
3275 event
.preventDefault();
3279 $.each( event
.split(" "), function( index
, eventName
) {
3280 events
[ eventName
] = "_eventHandler";
3284 this._off( this.anchors
.add( this.tabs
).add( this.panels
) );
3285 this._on( this.anchors
, events
);
3286 this._on( this.tabs
, { keydown
: "_tabKeydown" } );
3287 this._on( this.panels
, { keydown
: "_panelKeydown" } );
3289 this._focusable( this.tabs
);
3290 this._hoverable( this.tabs
);
3293 _setupHeightStyle: function( heightStyle
) {
3295 parent
= this.element
.parent();
3297 if ( heightStyle
=== "fill" ) {
3298 maxHeight
= parent
.height();
3299 maxHeight
-= this.element
.outerHeight() - this.element
.height();
3301 this.element
.siblings( ":visible" ).each(function() {
3302 var elem
= $( this ),
3303 position
= elem
.css( "position" );
3305 if ( position
=== "absolute" || position
=== "fixed" ) {
3308 maxHeight
-= elem
.outerHeight( true );
3311 this.element
.children().not( this.panels
).each(function() {
3312 maxHeight
-= $( this ).outerHeight( true );
3315 this.panels
.each(function() {
3316 $( this ).height( Math
.max( 0, maxHeight
-
3317 $( this ).innerHeight() + $( this ).height() ) );
3319 .css( "overflow", "auto" );
3320 } else if ( heightStyle
=== "auto" ) {
3322 this.panels
.each(function() {
3323 maxHeight
= Math
.max( maxHeight
, $( this ).height( "" ).height() );
3324 }).height( maxHeight
);
3328 _eventHandler: function( event
) {
3329 var options
= this.options
,
3330 active
= this.active
,
3331 anchor
= $( event
.currentTarget
),
3332 tab
= anchor
.closest( "li" ),
3333 clickedIsActive
= tab
[ 0 ] === active
[ 0 ],
3334 collapsing
= clickedIsActive
&& options
.collapsible
,
3335 toShow
= collapsing
? $() : this._getPanelForTab( tab
),
3336 toHide
= !active
.length
? $() : this._getPanelForTab( active
),
3340 newTab
: collapsing
? $() : tab
,
3344 event
.preventDefault();
3346 if ( tab
.hasClass( "ui-state-disabled" ) ||
3347 // tab is already loading
3348 tab
.hasClass( "ui-tabs-loading" ) ||
3349 // can't switch durning an animation
3351 // click on active header, but not collapsible
3352 ( clickedIsActive
&& !options
.collapsible
) ||
3353 // allow canceling activation
3354 ( this._trigger( "beforeActivate", event
, eventData
) === false ) ) {
3358 options
.active
= collapsing
? false : this.tabs
.index( tab
);
3360 this.active
= clickedIsActive
? $() : tab
;
3365 if ( !toHide
.length
&& !toShow
.length
) {
3366 $.error( "jQuery UI Tabs: Mismatching fragment identifier." );
3369 if ( toShow
.length
) {
3370 this.load( this.tabs
.index( tab
), event
);
3372 this._toggle( event
, eventData
);
3375 // handles show/hide for selecting tabs
3376 _toggle: function( event
, eventData
) {
3378 toShow
= eventData
.newPanel
,
3379 toHide
= eventData
.oldPanel
;
3381 this.running
= true;
3383 function complete() {
3384 that
.running
= false;
3385 that
._trigger( "activate", event
, eventData
);
3389 eventData
.newTab
.closest( "li" ).addClass( "ui-tabs-active ui-state-active" );
3391 if ( toShow
.length
&& that
.options
.show
) {
3392 that
._show( toShow
, that
.options
.show
, complete
);
3399 // start out by hiding, then showing, then completing
3400 if ( toHide
.length
&& this.options
.hide
) {
3401 this._hide( toHide
, this.options
.hide
, function() {
3402 eventData
.oldTab
.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" );
3406 eventData
.oldTab
.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" );
3412 "aria-expanded": "false",
3413 "aria-hidden": "true"
3415 eventData
.oldTab
.attr( "aria-selected", "false" );
3416 // If we're switching tabs, remove the old tab from the tab order.
3417 // If we're opening from collapsed state, remove the previous tab from the tab order.
3418 // If we're collapsing, then keep the collapsing tab in the tab order.
3419 if ( toShow
.length
&& toHide
.length
) {
3420 eventData
.oldTab
.attr( "tabIndex", -1 );
3421 } else if ( toShow
.length
) {
3422 this.tabs
.filter(function() {
3423 return $( this ).attr( "tabIndex" ) === 0;
3425 .attr( "tabIndex", -1 );
3429 "aria-expanded": "true",
3430 "aria-hidden": "false"
3432 eventData
.newTab
.attr({
3433 "aria-selected": "true",
3438 _activate: function( index
) {
3440 active
= this._findActive( index
);
3442 // trying to activate the already active panel
3443 if ( active
[ 0 ] === this.active
[ 0 ] ) {
3447 // trying to collapse, simulate a click on the current active header
3448 if ( !active
.length
) {
3449 active
= this.active
;
3452 anchor
= active
.find( ".ui-tabs-anchor" )[ 0 ];
3453 this._eventHandler({
3455 currentTarget
: anchor
,
3456 preventDefault
: $.noop
3460 _findActive: function( index
) {
3461 return index
=== false ? $() : this.tabs
.eq( index
);
3464 _getIndex: function( index
) {
3465 // meta-function to give users option to provide a href string instead of a numerical index.
3466 if ( typeof index
=== "string" ) {
3467 index
= this.anchors
.index( this.anchors
.filter( "[href$='" + index
+ "']" ) );
3473 _destroy: function() {
3478 this.element
.removeClass( "ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible" );
3481 .removeClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" )
3482 .removeAttr( "role" );
3485 .removeClass( "ui-tabs-anchor" )
3486 .removeAttr( "role" )
3487 .removeAttr( "tabIndex" )
3490 this.tabs
.add( this.panels
).each(function() {
3491 if ( $.data( this, "ui-tabs-destroy" ) ) {
3495 .removeClass( "ui-state-default ui-state-active ui-state-disabled " +
3496 "ui-corner-top ui-corner-bottom ui-widget-content ui-tabs-active ui-tabs-panel" )
3497 .removeAttr( "tabIndex" )
3498 .removeAttr( "aria-live" )
3499 .removeAttr( "aria-busy" )
3500 .removeAttr( "aria-selected" )
3501 .removeAttr( "aria-labelledby" )
3502 .removeAttr( "aria-hidden" )
3503 .removeAttr( "aria-expanded" )
3504 .removeAttr( "role" );
3508 this.tabs
.each(function() {
3510 prev
= li
.data( "ui-tabs-aria-controls" );
3513 .attr( "aria-controls", prev
)
3514 .removeData( "ui-tabs-aria-controls" );
3516 li
.removeAttr( "aria-controls" );
3522 if ( this.options
.heightStyle
!== "content" ) {
3523 this.panels
.css( "height", "" );
3527 enable: function( index
) {
3528 var disabled
= this.options
.disabled
;
3529 if ( disabled
=== false ) {
3533 if ( index
=== undefined ) {
3536 index
= this._getIndex( index
);
3537 if ( $.isArray( disabled
) ) {
3538 disabled
= $.map( disabled
, function( num
) {
3539 return num
!== index
? num
: null;
3542 disabled
= $.map( this.tabs
, function( li
, num
) {
3543 return num
!== index
? num
: null;
3547 this._setupDisabled( disabled
);
3550 disable: function( index
) {
3551 var disabled
= this.options
.disabled
;
3552 if ( disabled
=== true ) {
3556 if ( index
=== undefined ) {
3559 index
= this._getIndex( index
);
3560 if ( $.inArray( index
, disabled
) !== -1 ) {
3563 if ( $.isArray( disabled
) ) {
3564 disabled
= $.merge( [ index
], disabled
).sort();
3566 disabled
= [ index
];
3569 this._setupDisabled( disabled
);
3572 load: function( index
, event
) {
3573 index
= this._getIndex( index
);
3575 tab
= this.tabs
.eq( index
),
3576 anchor
= tab
.find( ".ui-tabs-anchor" ),
3577 panel
= this._getPanelForTab( tab
),
3584 if ( isLocal( anchor
[ 0 ] ) ) {
3588 this.xhr
= $.ajax( this._ajaxSettings( anchor
, event
, eventData
) );
3590 // support: jQuery <1.8
3591 // jQuery <1.8 returns false if the request is canceled in beforeSend,
3592 // but as of 1.8, $.ajax() always returns a jqXHR object.
3593 if ( this.xhr
&& this.xhr
.statusText
!== "canceled" ) {
3594 tab
.addClass( "ui-tabs-loading" );
3595 panel
.attr( "aria-busy", "true" );
3598 .success(function( response
) {
3599 // support: jQuery <1.8
3600 // http://bugs.jquery.com/ticket/11778
3601 setTimeout(function() {
3602 panel
.html( response
);
3603 that
._trigger( "load", event
, eventData
);
3606 .complete(function( jqXHR
, status
) {
3607 // support: jQuery <1.8
3608 // http://bugs.jquery.com/ticket/11778
3609 setTimeout(function() {
3610 if ( status
=== "abort" ) {
3611 that
.panels
.stop( false, true );
3614 tab
.removeClass( "ui-tabs-loading" );
3615 panel
.removeAttr( "aria-busy" );
3617 if ( jqXHR
=== that
.xhr
) {
3625 _ajaxSettings: function( anchor
, event
, eventData
) {
3628 url
: anchor
.attr( "href" ),
3629 beforeSend: function( jqXHR
, settings
) {
3630 return that
._trigger( "beforeLoad", event
,
3631 $.extend( { jqXHR
: jqXHR
, ajaxSettings
: settings
}, eventData
) );
3636 _getPanelForTab: function( tab
) {
3637 var id
= $( tab
).attr( "aria-controls" );
3638 return this.element
.find( this._sanitizeSelector( "#" + id
) );