[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 /*! 2 * jQuery UI Tabs 1.9.2 3 * http://jqueryui.com 4 * 5 * Copyright 2012 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 * Depends: 12 * jquery.ui.core.js 13 * jquery.ui.widget.js 14 */ 15 (function( $, undefined ) { 16 17 var tabId = 0, 18 rhash = /#.*$/; 19 20 function getNextTabId() { 21 return ++tabId; 22 } 23 24 function isLocal( anchor ) { 25 return anchor.hash.length > 1 && 26 anchor.href.replace( rhash, "" ) === 27 location.href.replace( rhash, "" ) 28 // support: Safari 5.1 29 // Safari 5.1 doesn't encode spaces in window.location 30 // but it does encode spaces from anchors (#8777) 31 .replace( /\s/g, "%20" ); 32 } 33 34 $.widget( "ui.tabs", { 35 version: "1.9.2", 36 delay: 300, 37 options: { 38 active: null, 39 collapsible: false, 40 event: "click", 41 heightStyle: "content", 42 hide: null, 43 show: null, 44 45 // callbacks 46 activate: null, 47 beforeActivate: null, 48 beforeLoad: null, 49 load: null 50 }, 51 52 _create: function() { 53 var that = this, 54 options = this.options, 55 active = options.active, 56 locationHash = location.hash.substring( 1 ); 57 58 this.running = false; 59 60 this.element 61 .addClass( "ui-tabs ui-widget ui-widget-content ui-corner-all" ) 62 .toggleClass( "ui-tabs-collapsible", options.collapsible ) 63 // Prevent users from focusing disabled tabs via click 64 .delegate( ".ui-tabs-nav > li", "mousedown" + this.eventNamespace, function( event ) { 65 if ( $( this ).is( ".ui-state-disabled" ) ) { 66 event.preventDefault(); 67 } 68 }) 69 // support: IE <9 70 // Preventing the default action in mousedown doesn't prevent IE 71 // from focusing the element, so if the anchor gets focused, blur. 72 // We don't have to worry about focusing the previously focused 73 // element since clicking on a non-focusable element should focus 74 // the body anyway. 75 .delegate( ".ui-tabs-anchor", "focus" + this.eventNamespace, function() { 76 if ( $( this ).closest( "li" ).is( ".ui-state-disabled" ) ) { 77 this.blur(); 78 } 79 }); 80 81 this._processTabs(); 82 83 if ( active === null ) { 84 // check the fragment identifier in the URL 85 if ( locationHash ) { 86 this.tabs.each(function( i, tab ) { 87 if ( $( tab ).attr( "aria-controls" ) === locationHash ) { 88 active = i; 89 return false; 90 } 91 }); 92 } 93 94 // check for a tab marked active via a class 95 if ( active === null ) { 96 active = this.tabs.index( this.tabs.filter( ".ui-tabs-active" ) ); 97 } 98 99 // no active tab, set to false 100 if ( active === null || active === -1 ) { 101 active = this.tabs.length ? 0 : false; 102 } 103 } 104 105 // handle numbers: negative, out of range 106 if ( active !== false ) { 107 active = this.tabs.index( this.tabs.eq( active ) ); 108 if ( active === -1 ) { 109 active = options.collapsible ? false : 0; 110 } 111 } 112 options.active = active; 113 114 // don't allow collapsible: false and active: false 115 if ( !options.collapsible && options.active === false && this.anchors.length ) { 116 options.active = 0; 117 } 118 119 // Take disabling tabs via class attribute from HTML 120 // into account and update option properly. 121 if ( $.isArray( options.disabled ) ) { 122 options.disabled = $.unique( options.disabled.concat( 123 $.map( this.tabs.filter( ".ui-state-disabled" ), function( li ) { 124 return that.tabs.index( li ); 125 }) 126 ) ).sort(); 127 } 128 129 // check for length avoids error when initializing empty list 130 if ( this.options.active !== false && this.anchors.length ) { 131 this.active = this._findActive( this.options.active ); 132 } else { 133 this.active = $(); 134 } 135 136 this._refresh(); 137 138 if ( this.active.length ) { 139 this.load( options.active ); 140 } 141 }, 142 143 _getCreateEventData: function() { 144 return { 145 tab: this.active, 146 panel: !this.active.length ? $() : this._getPanelForTab( this.active ) 147 }; 148 }, 149 150 _tabKeydown: function( event ) { 151 var focusedTab = $( this.document[0].activeElement ).closest( "li" ), 152 selectedIndex = this.tabs.index( focusedTab ), 153 goingForward = true; 154 155 if ( this._handlePageNav( event ) ) { 156 return; 157 } 158 159 switch ( event.keyCode ) { 160 case $.ui.keyCode.RIGHT: 161 case $.ui.keyCode.DOWN: 162 selectedIndex++; 163 break; 164 case $.ui.keyCode.UP: 165 case $.ui.keyCode.LEFT: 166 goingForward = false; 167 selectedIndex--; 168 break; 169 case $.ui.keyCode.END: 170 selectedIndex = this.anchors.length - 1; 171 break; 172 case $.ui.keyCode.HOME: 173 selectedIndex = 0; 174 break; 175 case $.ui.keyCode.SPACE: 176 // Activate only, no collapsing 177 event.preventDefault(); 178 clearTimeout( this.activating ); 179 this._activate( selectedIndex ); 180 return; 181 case $.ui.keyCode.ENTER: 182 // Toggle (cancel delayed activation, allow collapsing) 183 event.preventDefault(); 184 clearTimeout( this.activating ); 185 // Determine if we should collapse or activate 186 this._activate( selectedIndex === this.options.active ? false : selectedIndex ); 187 return; 188 default: 189 return; 190 } 191 192 // Focus the appropriate tab, based on which key was pressed 193 event.preventDefault(); 194 clearTimeout( this.activating ); 195 selectedIndex = this._focusNextTab( selectedIndex, goingForward ); 196 197 // Navigating with control key will prevent automatic activation 198 if ( !event.ctrlKey ) { 199 // Update aria-selected immediately so that AT think the tab is already selected. 200 // Otherwise AT may confuse the user by stating that they need to activate the tab, 201 // but the tab will already be activated by the time the announcement finishes. 202 focusedTab.attr( "aria-selected", "false" ); 203 this.tabs.eq( selectedIndex ).attr( "aria-selected", "true" ); 204 205 this.activating = this._delay(function() { 206 this.option( "active", selectedIndex ); 207 }, this.delay ); 208 } 209 }, 210 211 _panelKeydown: function( event ) { 212 if ( this._handlePageNav( event ) ) { 213 return; 214 } 215 216 // Ctrl+up moves focus to the current tab 217 if ( event.ctrlKey && event.keyCode === $.ui.keyCode.UP ) { 218 event.preventDefault(); 219 this.active.focus(); 220 } 221 }, 222 223 // Alt+page up/down moves focus to the previous/next tab (and activates) 224 _handlePageNav: function( event ) { 225 if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_UP ) { 226 this._activate( this._focusNextTab( this.options.active - 1, false ) ); 227 return true; 228 } 229 if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_DOWN ) { 230 this._activate( this._focusNextTab( this.options.active + 1, true ) ); 231 return true; 232 } 233 }, 234 235 _findNextTab: function( index, goingForward ) { 236 var lastTabIndex = this.tabs.length - 1; 237 238 function constrain() { 239 if ( index > lastTabIndex ) { 240 index = 0; 241 } 242 if ( index < 0 ) { 243 index = lastTabIndex; 244 } 245 return index; 246 } 247 248 while ( $.inArray( constrain(), this.options.disabled ) !== -1 ) { 249 index = goingForward ? index + 1 : index - 1; 250 } 251 252 return index; 253 }, 254 255 _focusNextTab: function( index, goingForward ) { 256 index = this._findNextTab( index, goingForward ); 257 this.tabs.eq( index ).focus(); 258 return index; 259 }, 260 261 _setOption: function( key, value ) { 262 if ( key === "active" ) { 263 // _activate() will handle invalid values and update this.options 264 this._activate( value ); 265 return; 266 } 267 268 if ( key === "disabled" ) { 269 // don't use the widget factory's disabled handling 270 this._setupDisabled( value ); 271 return; 272 } 273 274 this._super( key, value); 275 276 if ( key === "collapsible" ) { 277 this.element.toggleClass( "ui-tabs-collapsible", value ); 278 // Setting collapsible: false while collapsed; open first panel 279 if ( !value && this.options.active === false ) { 280 this._activate( 0 ); 281 } 282 } 283 284 if ( key === "event" ) { 285 this._setupEvents( value ); 286 } 287 288 if ( key === "heightStyle" ) { 289 this._setupHeightStyle( value ); 290 } 291 }, 292 293 _tabId: function( tab ) { 294 return tab.attr( "aria-controls" ) || "ui-tabs-" + getNextTabId(); 295 }, 296 297 _sanitizeSelector: function( hash ) { 298 return hash ? hash.replace( /[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g, "\\$&" ) : ""; 299 }, 300 301 refresh: function() { 302 var options = this.options, 303 lis = this.tablist.children( ":has(a[href])" ); 304 305 // get disabled tabs from class attribute from HTML 306 // this will get converted to a boolean if needed in _refresh() 307 options.disabled = $.map( lis.filter( ".ui-state-disabled" ), function( tab ) { 308 return lis.index( tab ); 309 }); 310 311 this._processTabs(); 312 313 // was collapsed or no tabs 314 if ( options.active === false || !this.anchors.length ) { 315 options.active = false; 316 this.active = $(); 317 // was active, but active tab is gone 318 } else if ( this.active.length && !$.contains( this.tablist[ 0 ], this.active[ 0 ] ) ) { 319 // all remaining tabs are disabled 320 if ( this.tabs.length === options.disabled.length ) { 321 options.active = false; 322 this.active = $(); 323 // activate previous tab 324 } else { 325 this._activate( this._findNextTab( Math.max( 0, options.active - 1 ), false ) ); 326 } 327 // was active, active tab still exists 328 } else { 329 // make sure active index is correct 330 options.active = this.tabs.index( this.active ); 331 } 332 333 this._refresh(); 334 }, 335 336 _refresh: function() { 337 this._setupDisabled( this.options.disabled ); 338 this._setupEvents( this.options.event ); 339 this._setupHeightStyle( this.options.heightStyle ); 340 341 this.tabs.not( this.active ).attr({ 342 "aria-selected": "false", 343 tabIndex: -1 344 }); 345 this.panels.not( this._getPanelForTab( this.active ) ) 346 .hide() 347 .attr({ 348 "aria-expanded": "false", 349 "aria-hidden": "true" 350 }); 351 352 // Make sure one tab is in the tab order 353 if ( !this.active.length ) { 354 this.tabs.eq( 0 ).attr( "tabIndex", 0 ); 355 } else { 356 this.active 357 .addClass( "ui-tabs-active ui-state-active" ) 358 .attr({ 359 "aria-selected": "true", 360 tabIndex: 0 361 }); 362 this._getPanelForTab( this.active ) 363 .show() 364 .attr({ 365 "aria-expanded": "true", 366 "aria-hidden": "false" 367 }); 368 } 369 }, 370 371 _processTabs: function() { 372 var that = this; 373 374 this.tablist = this._getList() 375 .addClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" ) 376 .attr( "role", "tablist" ); 377 378 this.tabs = this.tablist.find( "> li:has(a[href])" ) 379 .addClass( "ui-state-default ui-corner-top" ) 380 .attr({ 381 role: "tab", 382 tabIndex: -1 383 }); 384 385 this.anchors = this.tabs.map(function() { 386 return $( "a", this )[ 0 ]; 387 }) 388 .addClass( "ui-tabs-anchor" ) 389 .attr({ 390 role: "presentation", 391 tabIndex: -1 392 }); 393 394 this.panels = $(); 395 396 this.anchors.each(function( i, anchor ) { 397 var selector, panel, panelId, 398 anchorId = $( anchor ).uniqueId().attr( "id" ), 399 tab = $( anchor ).closest( "li" ), 400 originalAriaControls = tab.attr( "aria-controls" ); 401 402 // inline tab 403 if ( isLocal( anchor ) ) { 404 selector = anchor.hash; 405 panel = that.element.find( that._sanitizeSelector( selector ) ); 406 // remote tab 407 } else { 408 panelId = that._tabId( tab ); 409 selector = "#" + panelId; 410 panel = that.element.find( selector ); 411 if ( !panel.length ) { 412 panel = that._createPanel( panelId ); 413 panel.insertAfter( that.panels[ i - 1 ] || that.tablist ); 414 } 415 panel.attr( "aria-live", "polite" ); 416 } 417 418 if ( panel.length) { 419 that.panels = that.panels.add( panel ); 420 } 421 if ( originalAriaControls ) { 422 tab.data( "ui-tabs-aria-controls", originalAriaControls ); 423 } 424 tab.attr({ 425 "aria-controls": selector.substring( 1 ), 426 "aria-labelledby": anchorId 427 }); 428 panel.attr( "aria-labelledby", anchorId ); 429 }); 430 431 this.panels 432 .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" ) 433 .attr( "role", "tabpanel" ); 434 }, 435 436 // allow overriding how to find the list for rare usage scenarios (#7715) 437 _getList: function() { 438 return this.element.find( "ol,ul" ).eq( 0 ); 439 }, 440 441 _createPanel: function( id ) { 442 return $( "<div>" ) 443 .attr( "id", id ) 444 .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" ) 445 .data( "ui-tabs-destroy", true ); 446 }, 447 448 _setupDisabled: function( disabled ) { 449 if ( $.isArray( disabled ) ) { 450 if ( !disabled.length ) { 451 disabled = false; 452 } else if ( disabled.length === this.anchors.length ) { 453 disabled = true; 454 } 455 } 456 457 // disable tabs 458 for ( var i = 0, li; ( li = this.tabs[ i ] ); i++ ) { 459 if ( disabled === true || $.inArray( i, disabled ) !== -1 ) { 460 $( li ) 461 .addClass( "ui-state-disabled" ) 462 .attr( "aria-disabled", "true" ); 463 } else { 464 $( li ) 465 .removeClass( "ui-state-disabled" ) 466 .removeAttr( "aria-disabled" ); 467 } 468 } 469 470 this.options.disabled = disabled; 471 }, 472 473 _setupEvents: function( event ) { 474 var events = { 475 click: function( event ) { 476 event.preventDefault(); 477 } 478 }; 479 if ( event ) { 480 $.each( event.split(" "), function( index, eventName ) { 481 events[ eventName ] = "_eventHandler"; 482 }); 483 } 484 485 this._off( this.anchors.add( this.tabs ).add( this.panels ) ); 486 this._on( this.anchors, events ); 487 this._on( this.tabs, { keydown: "_tabKeydown" } ); 488 this._on( this.panels, { keydown: "_panelKeydown" } ); 489 490 this._focusable( this.tabs ); 491 this._hoverable( this.tabs ); 492 }, 493 494 _setupHeightStyle: function( heightStyle ) { 495 var maxHeight, overflow, 496 parent = this.element.parent(); 497 498 if ( heightStyle === "fill" ) { 499 // IE 6 treats height like minHeight, so we need to turn off overflow 500 // in order to get a reliable height 501 // we use the minHeight support test because we assume that only 502 // browsers that don't support minHeight will treat height as minHeight 503 if ( !$.support.minHeight ) { 504 overflow = parent.css( "overflow" ); 505 parent.css( "overflow", "hidden"); 506 } 507 maxHeight = parent.height(); 508 this.element.siblings( ":visible" ).each(function() { 509 var elem = $( this ), 510 position = elem.css( "position" ); 511 512 if ( position === "absolute" || position === "fixed" ) { 513 return; 514 } 515 maxHeight -= elem.outerHeight( true ); 516 }); 517 if ( overflow ) { 518 parent.css( "overflow", overflow ); 519 } 520 521 this.element.children().not( this.panels ).each(function() { 522 maxHeight -= $( this ).outerHeight( true ); 523 }); 524 525 this.panels.each(function() { 526 $( this ).height( Math.max( 0, maxHeight - 527 $( this ).innerHeight() + $( this ).height() ) ); 528 }) 529 .css( "overflow", "auto" ); 530 } else if ( heightStyle === "auto" ) { 531 maxHeight = 0; 532 this.panels.each(function() { 533 maxHeight = Math.max( maxHeight, $( this ).height( "" ).height() ); 534 }).height( maxHeight ); 535 } 536 }, 537 538 _eventHandler: function( event ) { 539 var options = this.options, 540 active = this.active, 541 anchor = $( event.currentTarget ), 542 tab = anchor.closest( "li" ), 543 clickedIsActive = tab[ 0 ] === active[ 0 ], 544 collapsing = clickedIsActive && options.collapsible, 545 toShow = collapsing ? $() : this._getPanelForTab( tab ), 546 toHide = !active.length ? $() : this._getPanelForTab( active ), 547 eventData = { 548 oldTab: active, 549 oldPanel: toHide, 550 newTab: collapsing ? $() : tab, 551 newPanel: toShow 552 }; 553 554 event.preventDefault(); 555 556 if ( tab.hasClass( "ui-state-disabled" ) || 557 // tab is already loading 558 tab.hasClass( "ui-tabs-loading" ) || 559 // can't switch durning an animation 560 this.running || 561 // click on active header, but not collapsible 562 ( clickedIsActive && !options.collapsible ) || 563 // allow canceling activation 564 ( this._trigger( "beforeActivate", event, eventData ) === false ) ) { 565 return; 566 } 567 568 options.active = collapsing ? false : this.tabs.index( tab ); 569 570 this.active = clickedIsActive ? $() : tab; 571 if ( this.xhr ) { 572 this.xhr.abort(); 573 } 574 575 if ( !toHide.length && !toShow.length ) { 576 $.error( "jQuery UI Tabs: Mismatching fragment identifier." ); 577 } 578 579 if ( toShow.length ) { 580 this.load( this.tabs.index( tab ), event ); 581 } 582 this._toggle( event, eventData ); 583 }, 584 585 // handles show/hide for selecting tabs 586 _toggle: function( event, eventData ) { 587 var that = this, 588 toShow = eventData.newPanel, 589 toHide = eventData.oldPanel; 590 591 this.running = true; 592 593 function complete() { 594 that.running = false; 595 that._trigger( "activate", event, eventData ); 596 } 597 598 function show() { 599 eventData.newTab.closest( "li" ).addClass( "ui-tabs-active ui-state-active" ); 600 601 if ( toShow.length && that.options.show ) { 602 that._show( toShow, that.options.show, complete ); 603 } else { 604 toShow.show(); 605 complete(); 606 } 607 } 608 609 // start out by hiding, then showing, then completing 610 if ( toHide.length && this.options.hide ) { 611 this._hide( toHide, this.options.hide, function() { 612 eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" ); 613 show(); 614 }); 615 } else { 616 eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" ); 617 toHide.hide(); 618 show(); 619 } 620 621 toHide.attr({ 622 "aria-expanded": "false", 623 "aria-hidden": "true" 624 }); 625 eventData.oldTab.attr( "aria-selected", "false" ); 626 // If we're switching tabs, remove the old tab from the tab order. 627 // If we're opening from collapsed state, remove the previous tab from the tab order. 628 // If we're collapsing, then keep the collapsing tab in the tab order. 629 if ( toShow.length && toHide.length ) { 630 eventData.oldTab.attr( "tabIndex", -1 ); 631 } else if ( toShow.length ) { 632 this.tabs.filter(function() { 633 return $( this ).attr( "tabIndex" ) === 0; 634 }) 635 .attr( "tabIndex", -1 ); 636 } 637 638 toShow.attr({ 639 "aria-expanded": "true", 640 "aria-hidden": "false" 641 }); 642 eventData.newTab.attr({ 643 "aria-selected": "true", 644 tabIndex: 0 645 }); 646 }, 647 648 _activate: function( index ) { 649 var anchor, 650 active = this._findActive( index ); 651 652 // trying to activate the already active panel 653 if ( active[ 0 ] === this.active[ 0 ] ) { 654 return; 655 } 656 657 // trying to collapse, simulate a click on the current active header 658 if ( !active.length ) { 659 active = this.active; 660 } 661 662 anchor = active.find( ".ui-tabs-anchor" )[ 0 ]; 663 this._eventHandler({ 664 target: anchor, 665 currentTarget: anchor, 666 preventDefault: $.noop 667 }); 668 }, 669 670 _findActive: function( index ) { 671 return index === false ? $() : this.tabs.eq( index ); 672 }, 673 674 _getIndex: function( index ) { 675 // meta-function to give users option to provide a href string instead of a numerical index. 676 if ( typeof index === "string" ) { 677 index = this.anchors.index( this.anchors.filter( "[href$='" + index + "']" ) ); 678 } 679 680 return index; 681 }, 682 683 _destroy: function() { 684 if ( this.xhr ) { 685 this.xhr.abort(); 686 } 687 688 this.element.removeClass( "ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible" ); 689 690 this.tablist 691 .removeClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" ) 692 .removeAttr( "role" ); 693 694 this.anchors 695 .removeClass( "ui-tabs-anchor" ) 696 .removeAttr( "role" ) 697 .removeAttr( "tabIndex" ) 698 .removeData( "href.tabs" ) 699 .removeData( "load.tabs" ) 700 .removeUniqueId(); 701 702 this.tabs.add( this.panels ).each(function() { 703 if ( $.data( this, "ui-tabs-destroy" ) ) { 704 $( this ).remove(); 705 } else { 706 $( this ) 707 .removeClass( "ui-state-default ui-state-active ui-state-disabled " + 708 "ui-corner-top ui-corner-bottom ui-widget-content ui-tabs-active ui-tabs-panel" ) 709 .removeAttr( "tabIndex" ) 710 .removeAttr( "aria-live" ) 711 .removeAttr( "aria-busy" ) 712 .removeAttr( "aria-selected" ) 713 .removeAttr( "aria-labelledby" ) 714 .removeAttr( "aria-hidden" ) 715 .removeAttr( "aria-expanded" ) 716 .removeAttr( "role" ); 717 } 718 }); 719 720 this.tabs.each(function() { 721 var li = $( this ), 722 prev = li.data( "ui-tabs-aria-controls" ); 723 if ( prev ) { 724 li.attr( "aria-controls", prev ); 725 } else { 726 li.removeAttr( "aria-controls" ); 727 } 728 }); 729 730 this.panels.show(); 731 732 if ( this.options.heightStyle !== "content" ) { 733 this.panels.css( "height", "" ); 734 } 735 }, 736 737 enable: function( index ) { 738 var disabled = this.options.disabled; 739 if ( disabled === false ) { 740 return; 741 } 742 743 if ( index === undefined ) { 744 disabled = false; 745 } else { 746 index = this._getIndex( index ); 747 if ( $.isArray( disabled ) ) { 748 disabled = $.map( disabled, function( num ) { 749 return num !== index ? num : null; 750 }); 751 } else { 752 disabled = $.map( this.tabs, function( li, num ) { 753 return num !== index ? num : null; 754 }); 755 } 756 } 757 this._setupDisabled( disabled ); 758 }, 759 760 disable: function( index ) { 761 var disabled = this.options.disabled; 762 if ( disabled === true ) { 763 return; 764 } 765 766 if ( index === undefined ) { 767 disabled = true; 768 } else { 769 index = this._getIndex( index ); 770 if ( $.inArray( index, disabled ) !== -1 ) { 771 return; 772 } 773 if ( $.isArray( disabled ) ) { 774 disabled = $.merge( [ index ], disabled ).sort(); 775 } else { 776 disabled = [ index ]; 777 } 778 } 779 this._setupDisabled( disabled ); 780 }, 781 782 load: function( index, event ) { 783 index = this._getIndex( index ); 784 var that = this, 785 tab = this.tabs.eq( index ), 786 anchor = tab.find( ".ui-tabs-anchor" ), 787 panel = this._getPanelForTab( tab ), 788 eventData = { 789 tab: tab, 790 panel: panel 791 }; 792 793 // not remote 794 if ( isLocal( anchor[ 0 ] ) ) { 795 return; 796 } 797 798 this.xhr = $.ajax( this._ajaxSettings( anchor, event, eventData ) ); 799 800 // support: jQuery <1.8 801 // jQuery <1.8 returns false if the request is canceled in beforeSend, 802 // but as of 1.8, $.ajax() always returns a jqXHR object. 803 if ( this.xhr && this.xhr.statusText !== "canceled" ) { 804 tab.addClass( "ui-tabs-loading" ); 805 panel.attr( "aria-busy", "true" ); 806 807 this.xhr 808 .success(function( response ) { 809 // support: jQuery <1.8 810 // http://bugs.jquery.com/ticket/11778 811 setTimeout(function() { 812 panel.html( response ); 813 that._trigger( "load", event, eventData ); 814 }, 1 ); 815 }) 816 .complete(function( jqXHR, status ) { 817 // support: jQuery <1.8 818 // http://bugs.jquery.com/ticket/11778 819 setTimeout(function() { 820 if ( status === "abort" ) { 821 that.panels.stop( false, true ); 822 } 823 824 tab.removeClass( "ui-tabs-loading" ); 825 panel.removeAttr( "aria-busy" ); 826 827 if ( jqXHR === that.xhr ) { 828 delete that.xhr; 829 } 830 }, 1 ); 831 }); 832 } 833 }, 834 835 // TODO: Remove this function in 1.10 when ajaxOptions is removed 836 _ajaxSettings: function( anchor, event, eventData ) { 837 var that = this; 838 return { 839 url: anchor.attr( "href" ), 840 beforeSend: function( jqXHR, settings ) { 841 return that._trigger( "beforeLoad", event, 842 $.extend( { jqXHR : jqXHR, ajaxSettings: settings }, eventData ) ); 843 } 844 }; 845 }, 846 847 _getPanelForTab: function( tab ) { 848 var id = $( tab ).attr( "aria-controls" ); 849 return this.element.find( this._sanitizeSelector( "#" + id ) ); 850 } 851 }); 852 853 // DEPRECATED 854 if ( $.uiBackCompat !== false ) { 855 856 // helper method for a lot of the back compat extensions 857 $.ui.tabs.prototype._ui = function( tab, panel ) { 858 return { 859 tab: tab, 860 panel: panel, 861 index: this.anchors.index( tab ) 862 }; 863 }; 864 865 // url method 866 $.widget( "ui.tabs", $.ui.tabs, { 867 url: function( index, url ) { 868 this.anchors.eq( index ).attr( "href", url ); 869 } 870 }); 871 872 // TODO: Remove _ajaxSettings() method when removing this extension 873 // ajaxOptions and cache options 874 $.widget( "ui.tabs", $.ui.tabs, { 875 options: { 876 ajaxOptions: null, 877 cache: false 878 }, 879 880 _create: function() { 881 this._super(); 882 883 var that = this; 884 885 this._on({ tabsbeforeload: function( event, ui ) { 886 // tab is already cached 887 if ( $.data( ui.tab[ 0 ], "cache.tabs" ) ) { 888 event.preventDefault(); 889 return; 890 } 891 892 ui.jqXHR.success(function() { 893 if ( that.options.cache ) { 894 $.data( ui.tab[ 0 ], "cache.tabs", true ); 895 } 896 }); 897 }}); 898 }, 899 900 _ajaxSettings: function( anchor, event, ui ) { 901 var ajaxOptions = this.options.ajaxOptions; 902 return $.extend( {}, ajaxOptions, { 903 error: function( xhr, status ) { 904 try { 905 // Passing index avoid a race condition when this method is 906 // called after the user has selected another tab. 907 // Pass the anchor that initiated this request allows 908 // loadError to manipulate the tab content panel via $(a.hash) 909 ajaxOptions.error( 910 xhr, status, ui.tab.closest( "li" ).index(), ui.tab[ 0 ] ); 911 } 912 catch ( error ) {} 913 } 914 }, this._superApply( arguments ) ); 915 }, 916 917 _setOption: function( key, value ) { 918 // reset cache if switching from cached to not cached 919 if ( key === "cache" && value === false ) { 920 this.anchors.removeData( "cache.tabs" ); 921 } 922 this._super( key, value ); 923 }, 924 925 _destroy: function() { 926 this.anchors.removeData( "cache.tabs" ); 927 this._super(); 928 }, 929 930 url: function( index ){ 931 this.anchors.eq( index ).removeData( "cache.tabs" ); 932 this._superApply( arguments ); 933 } 934 }); 935 936 // abort method 937 $.widget( "ui.tabs", $.ui.tabs, { 938 abort: function() { 939 if ( this.xhr ) { 940 this.xhr.abort(); 941 } 942 } 943 }); 944 945 // spinner 946 $.widget( "ui.tabs", $.ui.tabs, { 947 options: { 948 spinner: "<em>Loading…</em>" 949 }, 950 _create: function() { 951 this._super(); 952 this._on({ 953 tabsbeforeload: function( event, ui ) { 954 // Don't react to nested tabs or tabs that don't use a spinner 955 if ( event.target !== this.element[ 0 ] || 956 !this.options.spinner ) { 957 return; 958 } 959 960 var span = ui.tab.find( "span" ), 961 html = span.html(); 962 span.html( this.options.spinner ); 963 ui.jqXHR.complete(function() { 964 span.html( html ); 965 }); 966 } 967 }); 968 } 969 }); 970 971 // enable/disable events 972 $.widget( "ui.tabs", $.ui.tabs, { 973 options: { 974 enable: null, 975 disable: null 976 }, 977 978 enable: function( index ) { 979 var options = this.options, 980 trigger; 981 982 if ( index && options.disabled === true || 983 ( $.isArray( options.disabled ) && $.inArray( index, options.disabled ) !== -1 ) ) { 984 trigger = true; 985 } 986 987 this._superApply( arguments ); 988 989 if ( trigger ) { 990 this._trigger( "enable", null, this._ui( this.anchors[ index ], this.panels[ index ] ) ); 991 } 992 }, 993 994 disable: function( index ) { 995 var options = this.options, 996 trigger; 997 998 if ( index && options.disabled === false || 999 ( $.isArray( options.disabled ) && $.inArray( index, options.disabled ) === -1 ) ) { 1000 trigger = true; 1001 } 1002 1003 this._superApply( arguments ); 1004 1005 if ( trigger ) { 1006 this._trigger( "disable", null, this._ui( this.anchors[ index ], this.panels[ index ] ) ); 1007 } 1008 } 1009 }); 1010 1011 // add/remove methods and events 1012 $.widget( "ui.tabs", $.ui.tabs, { 1013 options: { 1014 add: null, 1015 remove: null, 1016 tabTemplate: "<li><a href='#{href}'><span>#{label}</span></a></li>" 1017 }, 1018 1019 add: function( url, label, index ) { 1020 if ( index === undefined ) { 1021 index = this.anchors.length; 1022 } 1023 1024 var doInsertAfter, panel, 1025 options = this.options, 1026 li = $( options.tabTemplate 1027 .replace( /#\{href\}/g, url ) 1028 .replace( /#\{label\}/g, label ) ), 1029 id = !url.indexOf( "#" ) ? 1030 url.replace( "#", "" ) : 1031 this._tabId( li ); 1032 1033 li.addClass( "ui-state-default ui-corner-top" ).data( "ui-tabs-destroy", true ); 1034 li.attr( "aria-controls", id ); 1035 1036 doInsertAfter = index >= this.tabs.length; 1037 1038 // try to find an existing element before creating a new one 1039 panel = this.element.find( "#" + id ); 1040 if ( !panel.length ) { 1041 panel = this._createPanel( id ); 1042 if ( doInsertAfter ) { 1043 if ( index > 0 ) { 1044 panel.insertAfter( this.panels.eq( -1 ) ); 1045 } else { 1046 panel.appendTo( this.element ); 1047 } 1048 } else { 1049 panel.insertBefore( this.panels[ index ] ); 1050 } 1051 } 1052 panel.addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" ).hide(); 1053 1054 if ( doInsertAfter ) { 1055 li.appendTo( this.tablist ); 1056 } else { 1057 li.insertBefore( this.tabs[ index ] ); 1058 } 1059 1060 options.disabled = $.map( options.disabled, function( n ) { 1061 return n >= index ? ++n : n; 1062 }); 1063 1064 this.refresh(); 1065 if ( this.tabs.length === 1 && options.active === false ) { 1066 this.option( "active", 0 ); 1067 } 1068 1069 this._trigger( "add", null, this._ui( this.anchors[ index ], this.panels[ index ] ) ); 1070 return this; 1071 }, 1072 1073 remove: function( index ) { 1074 index = this._getIndex( index ); 1075 var options = this.options, 1076 tab = this.tabs.eq( index ).remove(), 1077 panel = this._getPanelForTab( tab ).remove(); 1078 1079 // If selected tab was removed focus tab to the right or 1080 // in case the last tab was removed the tab to the left. 1081 // We check for more than 2 tabs, because if there are only 2, 1082 // then when we remove this tab, there will only be one tab left 1083 // so we don't need to detect which tab to activate. 1084 if ( tab.hasClass( "ui-tabs-active" ) && this.anchors.length > 2 ) { 1085 this._activate( index + ( index + 1 < this.anchors.length ? 1 : -1 ) ); 1086 } 1087 1088 options.disabled = $.map( 1089 $.grep( options.disabled, function( n ) { 1090 return n !== index; 1091 }), 1092 function( n ) { 1093 return n >= index ? --n : n; 1094 }); 1095 1096 this.refresh(); 1097 1098 this._trigger( "remove", null, this._ui( tab.find( "a" )[ 0 ], panel[ 0 ] ) ); 1099 return this; 1100 } 1101 }); 1102 1103 // length method 1104 $.widget( "ui.tabs", $.ui.tabs, { 1105 length: function() { 1106 return this.anchors.length; 1107 } 1108 }); 1109 1110 // panel ids (idPrefix option + title attribute) 1111 $.widget( "ui.tabs", $.ui.tabs, { 1112 options: { 1113 idPrefix: "ui-tabs-" 1114 }, 1115 1116 _tabId: function( tab ) { 1117 var a = tab.is( "li" ) ? tab.find( "a[href]" ) : tab; 1118 a = a[0]; 1119 return $( a ).closest( "li" ).attr( "aria-controls" ) || 1120 a.title && a.title.replace( /\s/g, "_" ).replace( /[^\w\u00c0-\uFFFF\-]/g, "" ) || 1121 this.options.idPrefix + getNextTabId(); 1122 } 1123 }); 1124 1125 // _createPanel method 1126 $.widget( "ui.tabs", $.ui.tabs, { 1127 options: { 1128 panelTemplate: "<div></div>" 1129 }, 1130 1131 _createPanel: function( id ) { 1132 return $( this.options.panelTemplate ) 1133 .attr( "id", id ) 1134 .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" ) 1135 .data( "ui-tabs-destroy", true ); 1136 } 1137 }); 1138 1139 // selected option 1140 $.widget( "ui.tabs", $.ui.tabs, { 1141 _create: function() { 1142 var options = this.options; 1143 if ( options.active === null && options.selected !== undefined ) { 1144 options.active = options.selected === -1 ? false : options.selected; 1145 } 1146 this._super(); 1147 options.selected = options.active; 1148 if ( options.selected === false ) { 1149 options.selected = -1; 1150 } 1151 }, 1152 1153 _setOption: function( key, value ) { 1154 if ( key !== "selected" ) { 1155 return this._super( key, value ); 1156 } 1157 1158 var options = this.options; 1159 this._super( "active", value === -1 ? false : value ); 1160 options.selected = options.active; 1161 if ( options.selected === false ) { 1162 options.selected = -1; 1163 } 1164 }, 1165 1166 _eventHandler: function() { 1167 this._superApply( arguments ); 1168 this.options.selected = this.options.active; 1169 if ( this.options.selected === false ) { 1170 this.options.selected = -1; 1171 } 1172 } 1173 }); 1174 1175 // show and select event 1176 $.widget( "ui.tabs", $.ui.tabs, { 1177 options: { 1178 show: null, 1179 select: null 1180 }, 1181 _create: function() { 1182 this._super(); 1183 if ( this.options.active !== false ) { 1184 this._trigger( "show", null, this._ui( 1185 this.active.find( ".ui-tabs-anchor" )[ 0 ], 1186 this._getPanelForTab( this.active )[ 0 ] ) ); 1187 } 1188 }, 1189 _trigger: function( type, event, data ) { 1190 var tab, panel, 1191 ret = this._superApply( arguments ); 1192 1193 if ( !ret ) { 1194 return false; 1195 } 1196 1197 if ( type === "beforeActivate" ) { 1198 tab = data.newTab.length ? data.newTab : data.oldTab; 1199 panel = data.newPanel.length ? data.newPanel : data.oldPanel; 1200 ret = this._super( "select", event, { 1201 tab: tab.find( ".ui-tabs-anchor" )[ 0], 1202 panel: panel[ 0 ], 1203 index: tab.closest( "li" ).index() 1204 }); 1205 } else if ( type === "activate" && data.newTab.length ) { 1206 ret = this._super( "show", event, { 1207 tab: data.newTab.find( ".ui-tabs-anchor" )[ 0 ], 1208 panel: data.newPanel[ 0 ], 1209 index: data.newTab.closest( "li" ).index() 1210 }); 1211 } 1212 return ret; 1213 } 1214 }); 1215 1216 // select method 1217 $.widget( "ui.tabs", $.ui.tabs, { 1218 select: function( index ) { 1219 index = this._getIndex( index ); 1220 if ( index === -1 ) { 1221 if ( this.options.collapsible && this.options.selected !== -1 ) { 1222 index = this.options.selected; 1223 } else { 1224 return; 1225 } 1226 } 1227 this.anchors.eq( index ).trigger( this.options.event + this.eventNamespace ); 1228 } 1229 }); 1230 1231 // cookie option 1232 (function() { 1233 1234 var listId = 0; 1235 1236 $.widget( "ui.tabs", $.ui.tabs, { 1237 options: { 1238 cookie: null // e.g. { expires: 7, path: '/', domain: 'jquery.com', secure: true } 1239 }, 1240 _create: function() { 1241 var options = this.options, 1242 active; 1243 if ( options.active == null && options.cookie ) { 1244 active = parseInt( this._cookie(), 10 ); 1245 if ( active === -1 ) { 1246 active = false; 1247 } 1248 options.active = active; 1249 } 1250 this._super(); 1251 }, 1252 _cookie: function( active ) { 1253 var cookie = [ this.cookie || 1254 ( this.cookie = this.options.cookie.name || "ui-tabs-" + (++listId) ) ]; 1255 if ( arguments.length ) { 1256 cookie.push( active === false ? -1 : active ); 1257 cookie.push( this.options.cookie ); 1258 } 1259 return $.cookie.apply( null, cookie ); 1260 }, 1261 _refresh: function() { 1262 this._super(); 1263 if ( this.options.cookie ) { 1264 this._cookie( this.options.active, this.options.cookie ); 1265 } 1266 }, 1267 _eventHandler: function() { 1268 this._superApply( arguments ); 1269 if ( this.options.cookie ) { 1270 this._cookie( this.options.active, this.options.cookie ); 1271 } 1272 }, 1273 _destroy: function() { 1274 this._super(); 1275 if ( this.options.cookie ) { 1276 this._cookie( null, this.options.cookie ); 1277 } 1278 } 1279 }); 1280 1281 })(); 1282 1283 // load event 1284 $.widget( "ui.tabs", $.ui.tabs, { 1285 _trigger: function( type, event, data ) { 1286 var _data = $.extend( {}, data ); 1287 if ( type === "load" ) { 1288 _data.panel = _data.panel[ 0 ]; 1289 _data.tab = _data.tab.find( ".ui-tabs-anchor" )[ 0 ]; 1290 } 1291 return this._super( type, event, _data ); 1292 } 1293 }); 1294 1295 // fx option 1296 // The new animation options (show, hide) conflict with the old show callback. 1297 // The old fx option wins over show/hide anyway (always favor back-compat). 1298 // If a user wants to use the new animation API, they must give up the old API. 1299 $.widget( "ui.tabs", $.ui.tabs, { 1300 options: { 1301 fx: null // e.g. { height: "toggle", opacity: "toggle", duration: 200 } 1302 }, 1303 1304 _getFx: function() { 1305 var hide, show, 1306 fx = this.options.fx; 1307 1308 if ( fx ) { 1309 if ( $.isArray( fx ) ) { 1310 hide = fx[ 0 ]; 1311 show = fx[ 1 ]; 1312 } else { 1313 hide = show = fx; 1314 } 1315 } 1316 1317 return fx ? { show: show, hide: hide } : null; 1318 }, 1319 1320 _toggle: function( event, eventData ) { 1321 var that = this, 1322 toShow = eventData.newPanel, 1323 toHide = eventData.oldPanel, 1324 fx = this._getFx(); 1325 1326 if ( !fx ) { 1327 return this._super( event, eventData ); 1328 } 1329 1330 that.running = true; 1331 1332 function complete() { 1333 that.running = false; 1334 that._trigger( "activate", event, eventData ); 1335 } 1336 1337 function show() { 1338 eventData.newTab.closest( "li" ).addClass( "ui-tabs-active ui-state-active" ); 1339 1340 if ( toShow.length && fx.show ) { 1341 toShow 1342 .animate( fx.show, fx.show.duration, function() { 1343 complete(); 1344 }); 1345 } else { 1346 toShow.show(); 1347 complete(); 1348 } 1349 } 1350 1351 // start out by hiding, then showing, then completing 1352 if ( toHide.length && fx.hide ) { 1353 toHide.animate( fx.hide, fx.hide.duration, function() { 1354 eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" ); 1355 show(); 1356 }); 1357 } else { 1358 eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" ); 1359 toHide.hide(); 1360 show(); 1361 } 1362 } 1363 }); 1364 } 1365 1366 })( jQuery );
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |