commiting uncommited changes on live site
[weblabels.fsf.org.git] / crm.fsf.org / 20131203 / files / sites / all / modules-new / civicrm / bower_components / jquery-ui / ui / tabs.js
1 /*!
2 * jQuery UI Tabs 1.11.4
3 * http://jqueryui.com
4 *
5 * Copyright jQuery Foundation and other contributors
6 * Released under the MIT license.
7 * http://jquery.org/license
8 *
9 * http://api.jqueryui.com/tabs/
10 */
11 (function( factory ) {
12 if ( typeof define === "function" && define.amd ) {
13
14 // AMD. Register as an anonymous module.
15 define([
16 "jquery",
17 "./core",
18 "./widget"
19 ], factory );
20 } else {
21
22 // Browser globals
23 factory( jQuery );
24 }
25 }(function( $ ) {
26
27 return $.widget( "ui.tabs", {
28 version: "1.11.4",
29 delay: 300,
30 options: {
31 active: null,
32 collapsible: false,
33 event: "click",
34 heightStyle: "content",
35 hide: null,
36 show: null,
37
38 // callbacks
39 activate: null,
40 beforeActivate: null,
41 beforeLoad: null,
42 load: null
43 },
44
45 _isLocal: (function() {
46 var rhash = /#.*$/;
47
48 return function( anchor ) {
49 var anchorUrl, locationUrl;
50
51 // support: IE7
52 // IE7 doesn't normalize the href property when set via script (#9317)
53 anchor = anchor.cloneNode( false );
54
55 anchorUrl = anchor.href.replace( rhash, "" );
56 locationUrl = location.href.replace( rhash, "" );
57
58 // decoding may throw an error if the URL isn't UTF-8 (#9518)
59 try {
60 anchorUrl = decodeURIComponent( anchorUrl );
61 } catch ( error ) {}
62 try {
63 locationUrl = decodeURIComponent( locationUrl );
64 } catch ( error ) {}
65
66 return anchor.hash.length > 1 && anchorUrl === locationUrl;
67 };
68 })(),
69
70 _create: function() {
71 var that = this,
72 options = this.options;
73
74 this.running = false;
75
76 this.element
77 .addClass( "ui-tabs ui-widget ui-widget-content ui-corner-all" )
78 .toggleClass( "ui-tabs-collapsible", options.collapsible );
79
80 this._processTabs();
81 options.active = this._initialActive();
82
83 // Take disabling tabs via class attribute from HTML
84 // into account and update option properly.
85 if ( $.isArray( options.disabled ) ) {
86 options.disabled = $.unique( options.disabled.concat(
87 $.map( this.tabs.filter( ".ui-state-disabled" ), function( li ) {
88 return that.tabs.index( li );
89 })
90 ) ).sort();
91 }
92
93 // check for length avoids error when initializing empty list
94 if ( this.options.active !== false && this.anchors.length ) {
95 this.active = this._findActive( options.active );
96 } else {
97 this.active = $();
98 }
99
100 this._refresh();
101
102 if ( this.active.length ) {
103 this.load( options.active );
104 }
105 },
106
107 _initialActive: function() {
108 var active = this.options.active,
109 collapsible = this.options.collapsible,
110 locationHash = location.hash.substring( 1 );
111
112 if ( active === null ) {
113 // check the fragment identifier in the URL
114 if ( locationHash ) {
115 this.tabs.each(function( i, tab ) {
116 if ( $( tab ).attr( "aria-controls" ) === locationHash ) {
117 active = i;
118 return false;
119 }
120 });
121 }
122
123 // check for a tab marked active via a class
124 if ( active === null ) {
125 active = this.tabs.index( this.tabs.filter( ".ui-tabs-active" ) );
126 }
127
128 // no active tab, set to false
129 if ( active === null || active === -1 ) {
130 active = this.tabs.length ? 0 : false;
131 }
132 }
133
134 // handle numbers: negative, out of range
135 if ( active !== false ) {
136 active = this.tabs.index( this.tabs.eq( active ) );
137 if ( active === -1 ) {
138 active = collapsible ? false : 0;
139 }
140 }
141
142 // don't allow collapsible: false and active: false
143 if ( !collapsible && active === false && this.anchors.length ) {
144 active = 0;
145 }
146
147 return active;
148 },
149
150 _getCreateEventData: function() {
151 return {
152 tab: this.active,
153 panel: !this.active.length ? $() : this._getPanelForTab( this.active )
154 };
155 },
156
157 _tabKeydown: function( event ) {
158 var focusedTab = $( this.document[0].activeElement ).closest( "li" ),
159 selectedIndex = this.tabs.index( focusedTab ),
160 goingForward = true;
161
162 if ( this._handlePageNav( event ) ) {
163 return;
164 }
165
166 switch ( event.keyCode ) {
167 case $.ui.keyCode.RIGHT:
168 case $.ui.keyCode.DOWN:
169 selectedIndex++;
170 break;
171 case $.ui.keyCode.UP:
172 case $.ui.keyCode.LEFT:
173 goingForward = false;
174 selectedIndex--;
175 break;
176 case $.ui.keyCode.END:
177 selectedIndex = this.anchors.length - 1;
178 break;
179 case $.ui.keyCode.HOME:
180 selectedIndex = 0;
181 break;
182 case $.ui.keyCode.SPACE:
183 // Activate only, no collapsing
184 event.preventDefault();
185 clearTimeout( this.activating );
186 this._activate( selectedIndex );
187 return;
188 case $.ui.keyCode.ENTER:
189 // Toggle (cancel delayed activation, allow collapsing)
190 event.preventDefault();
191 clearTimeout( this.activating );
192 // Determine if we should collapse or activate
193 this._activate( selectedIndex === this.options.active ? false : selectedIndex );
194 return;
195 default:
196 return;
197 }
198
199 // Focus the appropriate tab, based on which key was pressed
200 event.preventDefault();
201 clearTimeout( this.activating );
202 selectedIndex = this._focusNextTab( selectedIndex, goingForward );
203
204 // Navigating with control/command key will prevent automatic activation
205 if ( !event.ctrlKey && !event.metaKey ) {
206
207 // Update aria-selected immediately so that AT think the tab is already selected.
208 // Otherwise AT may confuse the user by stating that they need to activate the tab,
209 // but the tab will already be activated by the time the announcement finishes.
210 focusedTab.attr( "aria-selected", "false" );
211 this.tabs.eq( selectedIndex ).attr( "aria-selected", "true" );
212
213 this.activating = this._delay(function() {
214 this.option( "active", selectedIndex );
215 }, this.delay );
216 }
217 },
218
219 _panelKeydown: function( event ) {
220 if ( this._handlePageNav( event ) ) {
221 return;
222 }
223
224 // Ctrl+up moves focus to the current tab
225 if ( event.ctrlKey && event.keyCode === $.ui.keyCode.UP ) {
226 event.preventDefault();
227 this.active.focus();
228 }
229 },
230
231 // Alt+page up/down moves focus to the previous/next tab (and activates)
232 _handlePageNav: function( event ) {
233 if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_UP ) {
234 this._activate( this._focusNextTab( this.options.active - 1, false ) );
235 return true;
236 }
237 if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_DOWN ) {
238 this._activate( this._focusNextTab( this.options.active + 1, true ) );
239 return true;
240 }
241 },
242
243 _findNextTab: function( index, goingForward ) {
244 var lastTabIndex = this.tabs.length - 1;
245
246 function constrain() {
247 if ( index > lastTabIndex ) {
248 index = 0;
249 }
250 if ( index < 0 ) {
251 index = lastTabIndex;
252 }
253 return index;
254 }
255
256 while ( $.inArray( constrain(), this.options.disabled ) !== -1 ) {
257 index = goingForward ? index + 1 : index - 1;
258 }
259
260 return index;
261 },
262
263 _focusNextTab: function( index, goingForward ) {
264 index = this._findNextTab( index, goingForward );
265 this.tabs.eq( index ).focus();
266 return index;
267 },
268
269 _setOption: function( key, value ) {
270 if ( key === "active" ) {
271 // _activate() will handle invalid values and update this.options
272 this._activate( value );
273 return;
274 }
275
276 if ( key === "disabled" ) {
277 // don't use the widget factory's disabled handling
278 this._setupDisabled( value );
279 return;
280 }
281
282 this._super( key, value);
283
284 if ( key === "collapsible" ) {
285 this.element.toggleClass( "ui-tabs-collapsible", value );
286 // Setting collapsible: false while collapsed; open first panel
287 if ( !value && this.options.active === false ) {
288 this._activate( 0 );
289 }
290 }
291
292 if ( key === "event" ) {
293 this._setupEvents( value );
294 }
295
296 if ( key === "heightStyle" ) {
297 this._setupHeightStyle( value );
298 }
299 },
300
301 _sanitizeSelector: function( hash ) {
302 return hash ? hash.replace( /[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g, "\\$&" ) : "";
303 },
304
305 refresh: function() {
306 var options = this.options,
307 lis = this.tablist.children( ":has(a[href])" );
308
309 // get disabled tabs from class attribute from HTML
310 // this will get converted to a boolean if needed in _refresh()
311 options.disabled = $.map( lis.filter( ".ui-state-disabled" ), function( tab ) {
312 return lis.index( tab );
313 });
314
315 this._processTabs();
316
317 // was collapsed or no tabs
318 if ( options.active === false || !this.anchors.length ) {
319 options.active = false;
320 this.active = $();
321 // was active, but active tab is gone
322 } else if ( this.active.length && !$.contains( this.tablist[ 0 ], this.active[ 0 ] ) ) {
323 // all remaining tabs are disabled
324 if ( this.tabs.length === options.disabled.length ) {
325 options.active = false;
326 this.active = $();
327 // activate previous tab
328 } else {
329 this._activate( this._findNextTab( Math.max( 0, options.active - 1 ), false ) );
330 }
331 // was active, active tab still exists
332 } else {
333 // make sure active index is correct
334 options.active = this.tabs.index( this.active );
335 }
336
337 this._refresh();
338 },
339
340 _refresh: function() {
341 this._setupDisabled( this.options.disabled );
342 this._setupEvents( this.options.event );
343 this._setupHeightStyle( this.options.heightStyle );
344
345 this.tabs.not( this.active ).attr({
346 "aria-selected": "false",
347 "aria-expanded": "false",
348 tabIndex: -1
349 });
350 this.panels.not( this._getPanelForTab( this.active ) )
351 .hide()
352 .attr({
353 "aria-hidden": "true"
354 });
355
356 // Make sure one tab is in the tab order
357 if ( !this.active.length ) {
358 this.tabs.eq( 0 ).attr( "tabIndex", 0 );
359 } else {
360 this.active
361 .addClass( "ui-tabs-active ui-state-active" )
362 .attr({
363 "aria-selected": "true",
364 "aria-expanded": "true",
365 tabIndex: 0
366 });
367 this._getPanelForTab( this.active )
368 .show()
369 .attr({
370 "aria-hidden": "false"
371 });
372 }
373 },
374
375 _processTabs: function() {
376 var that = this,
377 prevTabs = this.tabs,
378 prevAnchors = this.anchors,
379 prevPanels = this.panels;
380
381 this.tablist = this._getList()
382 .addClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" )
383 .attr( "role", "tablist" )
384
385 // Prevent users from focusing disabled tabs via click
386 .delegate( "> li", "mousedown" + this.eventNamespace, function( event ) {
387 if ( $( this ).is( ".ui-state-disabled" ) ) {
388 event.preventDefault();
389 }
390 })
391
392 // support: IE <9
393 // Preventing the default action in mousedown doesn't prevent IE
394 // from focusing the element, so if the anchor gets focused, blur.
395 // We don't have to worry about focusing the previously focused
396 // element since clicking on a non-focusable element should focus
397 // the body anyway.
398 .delegate( ".ui-tabs-anchor", "focus" + this.eventNamespace, function() {
399 if ( $( this ).closest( "li" ).is( ".ui-state-disabled" ) ) {
400 this.blur();
401 }
402 });
403
404 this.tabs = this.tablist.find( "> li:has(a[href])" )
405 .addClass( "ui-state-default ui-corner-top" )
406 .attr({
407 role: "tab",
408 tabIndex: -1
409 });
410
411 this.anchors = this.tabs.map(function() {
412 return $( "a", this )[ 0 ];
413 })
414 .addClass( "ui-tabs-anchor" )
415 .attr({
416 role: "presentation",
417 tabIndex: -1
418 });
419
420 this.panels = $();
421
422 this.anchors.each(function( i, anchor ) {
423 var selector, panel, panelId,
424 anchorId = $( anchor ).uniqueId().attr( "id" ),
425 tab = $( anchor ).closest( "li" ),
426 originalAriaControls = tab.attr( "aria-controls" );
427
428 // inline tab
429 if ( that._isLocal( anchor ) ) {
430 selector = anchor.hash;
431 panelId = selector.substring( 1 );
432 panel = that.element.find( that._sanitizeSelector( selector ) );
433 // remote tab
434 } else {
435 // If the tab doesn't already have aria-controls,
436 // generate an id by using a throw-away element
437 panelId = tab.attr( "aria-controls" ) || $( {} ).uniqueId()[ 0 ].id;
438 selector = "#" + panelId;
439 panel = that.element.find( selector );
440 if ( !panel.length ) {
441 panel = that._createPanel( panelId );
442 panel.insertAfter( that.panels[ i - 1 ] || that.tablist );
443 }
444 panel.attr( "aria-live", "polite" );
445 }
446
447 if ( panel.length) {
448 that.panels = that.panels.add( panel );
449 }
450 if ( originalAriaControls ) {
451 tab.data( "ui-tabs-aria-controls", originalAriaControls );
452 }
453 tab.attr({
454 "aria-controls": panelId,
455 "aria-labelledby": anchorId
456 });
457 panel.attr( "aria-labelledby", anchorId );
458 });
459
460 this.panels
461 .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )
462 .attr( "role", "tabpanel" );
463
464 // Avoid memory leaks (#10056)
465 if ( prevTabs ) {
466 this._off( prevTabs.not( this.tabs ) );
467 this._off( prevAnchors.not( this.anchors ) );
468 this._off( prevPanels.not( this.panels ) );
469 }
470 },
471
472 // allow overriding how to find the list for rare usage scenarios (#7715)
473 _getList: function() {
474 return this.tablist || this.element.find( "ol,ul" ).eq( 0 );
475 },
476
477 _createPanel: function( id ) {
478 return $( "<div>" )
479 .attr( "id", id )
480 .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )
481 .data( "ui-tabs-destroy", true );
482 },
483
484 _setupDisabled: function( disabled ) {
485 if ( $.isArray( disabled ) ) {
486 if ( !disabled.length ) {
487 disabled = false;
488 } else if ( disabled.length === this.anchors.length ) {
489 disabled = true;
490 }
491 }
492
493 // disable tabs
494 for ( var i = 0, li; ( li = this.tabs[ i ] ); i++ ) {
495 if ( disabled === true || $.inArray( i, disabled ) !== -1 ) {
496 $( li )
497 .addClass( "ui-state-disabled" )
498 .attr( "aria-disabled", "true" );
499 } else {
500 $( li )
501 .removeClass( "ui-state-disabled" )
502 .removeAttr( "aria-disabled" );
503 }
504 }
505
506 this.options.disabled = disabled;
507 },
508
509 _setupEvents: function( event ) {
510 var events = {};
511 if ( event ) {
512 $.each( event.split(" "), function( index, eventName ) {
513 events[ eventName ] = "_eventHandler";
514 });
515 }
516
517 this._off( this.anchors.add( this.tabs ).add( this.panels ) );
518 // Always prevent the default action, even when disabled
519 this._on( true, this.anchors, {
520 click: function( event ) {
521 event.preventDefault();
522 }
523 });
524 this._on( this.anchors, events );
525 this._on( this.tabs, { keydown: "_tabKeydown" } );
526 this._on( this.panels, { keydown: "_panelKeydown" } );
527
528 this._focusable( this.tabs );
529 this._hoverable( this.tabs );
530 },
531
532 _setupHeightStyle: function( heightStyle ) {
533 var maxHeight,
534 parent = this.element.parent();
535
536 if ( heightStyle === "fill" ) {
537 maxHeight = parent.height();
538 maxHeight -= this.element.outerHeight() - this.element.height();
539
540 this.element.siblings( ":visible" ).each(function() {
541 var elem = $( this ),
542 position = elem.css( "position" );
543
544 if ( position === "absolute" || position === "fixed" ) {
545 return;
546 }
547 maxHeight -= elem.outerHeight( true );
548 });
549
550 this.element.children().not( this.panels ).each(function() {
551 maxHeight -= $( this ).outerHeight( true );
552 });
553
554 this.panels.each(function() {
555 $( this ).height( Math.max( 0, maxHeight -
556 $( this ).innerHeight() + $( this ).height() ) );
557 })
558 .css( "overflow", "auto" );
559 } else if ( heightStyle === "auto" ) {
560 maxHeight = 0;
561 this.panels.each(function() {
562 maxHeight = Math.max( maxHeight, $( this ).height( "" ).height() );
563 }).height( maxHeight );
564 }
565 },
566
567 _eventHandler: function( event ) {
568 var options = this.options,
569 active = this.active,
570 anchor = $( event.currentTarget ),
571 tab = anchor.closest( "li" ),
572 clickedIsActive = tab[ 0 ] === active[ 0 ],
573 collapsing = clickedIsActive && options.collapsible,
574 toShow = collapsing ? $() : this._getPanelForTab( tab ),
575 toHide = !active.length ? $() : this._getPanelForTab( active ),
576 eventData = {
577 oldTab: active,
578 oldPanel: toHide,
579 newTab: collapsing ? $() : tab,
580 newPanel: toShow
581 };
582
583 event.preventDefault();
584
585 if ( tab.hasClass( "ui-state-disabled" ) ||
586 // tab is already loading
587 tab.hasClass( "ui-tabs-loading" ) ||
588 // can't switch durning an animation
589 this.running ||
590 // click on active header, but not collapsible
591 ( clickedIsActive && !options.collapsible ) ||
592 // allow canceling activation
593 ( this._trigger( "beforeActivate", event, eventData ) === false ) ) {
594 return;
595 }
596
597 options.active = collapsing ? false : this.tabs.index( tab );
598
599 this.active = clickedIsActive ? $() : tab;
600 if ( this.xhr ) {
601 this.xhr.abort();
602 }
603
604 if ( !toHide.length && !toShow.length ) {
605 $.error( "jQuery UI Tabs: Mismatching fragment identifier." );
606 }
607
608 if ( toShow.length ) {
609 this.load( this.tabs.index( tab ), event );
610 }
611 this._toggle( event, eventData );
612 },
613
614 // handles show/hide for selecting tabs
615 _toggle: function( event, eventData ) {
616 var that = this,
617 toShow = eventData.newPanel,
618 toHide = eventData.oldPanel;
619
620 this.running = true;
621
622 function complete() {
623 that.running = false;
624 that._trigger( "activate", event, eventData );
625 }
626
627 function show() {
628 eventData.newTab.closest( "li" ).addClass( "ui-tabs-active ui-state-active" );
629
630 if ( toShow.length && that.options.show ) {
631 that._show( toShow, that.options.show, complete );
632 } else {
633 toShow.show();
634 complete();
635 }
636 }
637
638 // start out by hiding, then showing, then completing
639 if ( toHide.length && this.options.hide ) {
640 this._hide( toHide, this.options.hide, function() {
641 eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" );
642 show();
643 });
644 } else {
645 eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" );
646 toHide.hide();
647 show();
648 }
649
650 toHide.attr( "aria-hidden", "true" );
651 eventData.oldTab.attr({
652 "aria-selected": "false",
653 "aria-expanded": "false"
654 });
655 // If we're switching tabs, remove the old tab from the tab order.
656 // If we're opening from collapsed state, remove the previous tab from the tab order.
657 // If we're collapsing, then keep the collapsing tab in the tab order.
658 if ( toShow.length && toHide.length ) {
659 eventData.oldTab.attr( "tabIndex", -1 );
660 } else if ( toShow.length ) {
661 this.tabs.filter(function() {
662 return $( this ).attr( "tabIndex" ) === 0;
663 })
664 .attr( "tabIndex", -1 );
665 }
666
667 toShow.attr( "aria-hidden", "false" );
668 eventData.newTab.attr({
669 "aria-selected": "true",
670 "aria-expanded": "true",
671 tabIndex: 0
672 });
673 },
674
675 _activate: function( index ) {
676 var anchor,
677 active = this._findActive( index );
678
679 // trying to activate the already active panel
680 if ( active[ 0 ] === this.active[ 0 ] ) {
681 return;
682 }
683
684 // trying to collapse, simulate a click on the current active header
685 if ( !active.length ) {
686 active = this.active;
687 }
688
689 anchor = active.find( ".ui-tabs-anchor" )[ 0 ];
690 this._eventHandler({
691 target: anchor,
692 currentTarget: anchor,
693 preventDefault: $.noop
694 });
695 },
696
697 _findActive: function( index ) {
698 return index === false ? $() : this.tabs.eq( index );
699 },
700
701 _getIndex: function( index ) {
702 // meta-function to give users option to provide a href string instead of a numerical index.
703 if ( typeof index === "string" ) {
704 index = this.anchors.index( this.anchors.filter( "[href$='" + index + "']" ) );
705 }
706
707 return index;
708 },
709
710 _destroy: function() {
711 if ( this.xhr ) {
712 this.xhr.abort();
713 }
714
715 this.element.removeClass( "ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible" );
716
717 this.tablist
718 .removeClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" )
719 .removeAttr( "role" );
720
721 this.anchors
722 .removeClass( "ui-tabs-anchor" )
723 .removeAttr( "role" )
724 .removeAttr( "tabIndex" )
725 .removeUniqueId();
726
727 this.tablist.unbind( this.eventNamespace );
728
729 this.tabs.add( this.panels ).each(function() {
730 if ( $.data( this, "ui-tabs-destroy" ) ) {
731 $( this ).remove();
732 } else {
733 $( this )
734 .removeClass( "ui-state-default ui-state-active ui-state-disabled " +
735 "ui-corner-top ui-corner-bottom ui-widget-content ui-tabs-active ui-tabs-panel" )
736 .removeAttr( "tabIndex" )
737 .removeAttr( "aria-live" )
738 .removeAttr( "aria-busy" )
739 .removeAttr( "aria-selected" )
740 .removeAttr( "aria-labelledby" )
741 .removeAttr( "aria-hidden" )
742 .removeAttr( "aria-expanded" )
743 .removeAttr( "role" );
744 }
745 });
746
747 this.tabs.each(function() {
748 var li = $( this ),
749 prev = li.data( "ui-tabs-aria-controls" );
750 if ( prev ) {
751 li
752 .attr( "aria-controls", prev )
753 .removeData( "ui-tabs-aria-controls" );
754 } else {
755 li.removeAttr( "aria-controls" );
756 }
757 });
758
759 this.panels.show();
760
761 if ( this.options.heightStyle !== "content" ) {
762 this.panels.css( "height", "" );
763 }
764 },
765
766 enable: function( index ) {
767 var disabled = this.options.disabled;
768 if ( disabled === false ) {
769 return;
770 }
771
772 if ( index === undefined ) {
773 disabled = false;
774 } else {
775 index = this._getIndex( index );
776 if ( $.isArray( disabled ) ) {
777 disabled = $.map( disabled, function( num ) {
778 return num !== index ? num : null;
779 });
780 } else {
781 disabled = $.map( this.tabs, function( li, num ) {
782 return num !== index ? num : null;
783 });
784 }
785 }
786 this._setupDisabled( disabled );
787 },
788
789 disable: function( index ) {
790 var disabled = this.options.disabled;
791 if ( disabled === true ) {
792 return;
793 }
794
795 if ( index === undefined ) {
796 disabled = true;
797 } else {
798 index = this._getIndex( index );
799 if ( $.inArray( index, disabled ) !== -1 ) {
800 return;
801 }
802 if ( $.isArray( disabled ) ) {
803 disabled = $.merge( [ index ], disabled ).sort();
804 } else {
805 disabled = [ index ];
806 }
807 }
808 this._setupDisabled( disabled );
809 },
810
811 load: function( index, event ) {
812 index = this._getIndex( index );
813 var that = this,
814 tab = this.tabs.eq( index ),
815 anchor = tab.find( ".ui-tabs-anchor" ),
816 panel = this._getPanelForTab( tab ),
817 eventData = {
818 tab: tab,
819 panel: panel
820 },
821 complete = function( jqXHR, status ) {
822 if ( status === "abort" ) {
823 that.panels.stop( false, true );
824 }
825
826 tab.removeClass( "ui-tabs-loading" );
827 panel.removeAttr( "aria-busy" );
828
829 if ( jqXHR === that.xhr ) {
830 delete that.xhr;
831 }
832 };
833
834 // not remote
835 if ( this._isLocal( anchor[ 0 ] ) ) {
836 return;
837 }
838
839 this.xhr = $.ajax( this._ajaxSettings( anchor, event, eventData ) );
840
841 // support: jQuery <1.8
842 // jQuery <1.8 returns false if the request is canceled in beforeSend,
843 // but as of 1.8, $.ajax() always returns a jqXHR object.
844 if ( this.xhr && this.xhr.statusText !== "canceled" ) {
845 tab.addClass( "ui-tabs-loading" );
846 panel.attr( "aria-busy", "true" );
847
848 this.xhr
849 .done(function( response, status, jqXHR ) {
850 // support: jQuery <1.8
851 // http://bugs.jquery.com/ticket/11778
852 setTimeout(function() {
853 panel.html( response );
854 that._trigger( "load", event, eventData );
855
856 complete( jqXHR, status );
857 }, 1 );
858 })
859 .fail(function( jqXHR, status ) {
860 // support: jQuery <1.8
861 // http://bugs.jquery.com/ticket/11778
862 setTimeout(function() {
863 complete( jqXHR, status );
864 }, 1 );
865 });
866 }
867 },
868
869 _ajaxSettings: function( anchor, event, eventData ) {
870 var that = this;
871 return {
872 url: anchor.attr( "href" ),
873 beforeSend: function( jqXHR, settings ) {
874 return that._trigger( "beforeLoad", event,
875 $.extend( { jqXHR: jqXHR, ajaxSettings: settings }, eventData ) );
876 }
877 };
878 },
879
880 _getPanelForTab: function( tab ) {
881 var id = $( tab ).attr( "aria-controls" );
882 return this.element.find( this._sanitizeSelector( "#" + id ) );
883 }
884 });
885
886 }));