diff options
Diffstat (limited to 'ui/jquery.ui.tabs.js')
-rw-r--r-- | ui/jquery.ui.tabs.js | 267 |
1 files changed, 165 insertions, 102 deletions
diff --git a/ui/jquery.ui.tabs.js b/ui/jquery.ui.tabs.js index 026c50993..a693899da 100644 --- a/ui/jquery.ui.tabs.js +++ b/ui/jquery.ui.tabs.js @@ -25,7 +25,7 @@ function isLocal( anchor ) { // if it's manually set, i.e., a.href = "#foo" kills the normalization anchor = anchor.cloneNode( false ); return anchor.hash.length > 1 && - anchor.href.replace( rhash, "" ) === location.href.replace( rhash, "" ); + anchor.href.replace( rhash, "" ) === location.href.replace( rhash, "" ); } $.widget( "ui.tabs", { @@ -34,7 +34,8 @@ $.widget( "ui.tabs", { active: null, collapsible: false, event: "click", - fx: null, // e.g. { height: 'toggle', opacity: 'toggle', duration: 200 } + hide: null, + show: null, // callbacks activate: null, @@ -46,20 +47,20 @@ $.widget( "ui.tabs", { _create: function() { var panel, that = this, - options = that.options, + options = this.options, active = options.active; - that.running = false; + this.running = false; - that.element.addClass( "ui-tabs ui-widget ui-widget-content ui-corner-all" ); + this.element.addClass( "ui-tabs ui-widget ui-widget-content ui-corner-all" ); - that._processTabs(); + this._processTabs(); if ( active === null ) { // check the fragment identifier in the URL if ( location.hash ) { - that.anchors.each(function( i, tab ) { - if ( tab.hash === location.hash ) { + this.anchors.each(function( i, anchor ) { + if ( anchor.hash === location.hash ) { active = i; return false; } @@ -68,12 +69,12 @@ $.widget( "ui.tabs", { // check for a tab marked active via a class if ( active === null ) { - active = that.lis.filter( ".ui-tabs-active" ).index(); + active = this.lis.filter( ".ui-tabs-active" ).index(); } // no active tab, set to false if ( active === null || active === -1 ) { - active = that.lis.length ? 0 : false; + active = this.lis.length ? 0 : false; } } @@ -101,8 +102,6 @@ $.widget( "ui.tabs", { ) ).sort(); } - this._setupFx( options.fx ); - this._refresh(); // highlight selected tab @@ -111,7 +110,7 @@ $.widget( "ui.tabs", { // check for length avoids error when initializing empty list if ( options.active !== false && this.anchors.length ) { this.active = this._findActive( options.active ); - panel = that._getPanelForTab( this.active ); + panel = this._getPanelForTab( this.active ); panel.show(); this.lis.eq( options.active ).addClass( "ui-tabs-active ui-state-active" ); @@ -151,14 +150,10 @@ $.widget( "ui.tabs", { if ( key === "event" ) { this._setupEvents( value ); } - - if ( key === "fx" ) { - this._setupFx( value ); - } }, - _tabId: function( a ) { - return $( a ).attr( "aria-controls" ) || "ui-tabs-" + getNextTabId(); + _tabId: function( tab ) { + return tab.attr( "aria-controls" ) || "ui-tabs-" + getNextTabId(); }, _sanitizeSelector: function( hash ) { @@ -192,7 +187,7 @@ $.widget( "ui.tabs", { // was active, active tab still exists } else { // make sure active index is correct - options.active = this.anchors.index( this.active ); + options.active = this.lis.index( this.active ); } }, @@ -202,6 +197,7 @@ $.widget( "ui.tabs", { this.element.toggleClass( "ui-tabs-collapsible", options.collapsible ); this.list.addClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" ); this.lis.addClass( "ui-state-default ui-corner-top" ); + this.anchors.addClass( "ui-tabs-anchor" ); this.panels.addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" ); this._setupDisabled( options.disabled ); @@ -224,7 +220,8 @@ $.widget( "ui.tabs", { this.panels = $(); this.anchors.each(function( i, a ) { - var selector, panel, id; + var selector, panel, id, + tab = $( a ).closest( "li" ); // inline tab if ( isLocal( a ) ) { @@ -232,7 +229,7 @@ $.widget( "ui.tabs", { panel = that.element.find( that._sanitizeSelector( selector ) ); // remote tab } else { - id = that._tabId( a ); + id = that._tabId( tab ); selector = "#" + id; panel = that.element.find( selector ); if ( !panel.length ) { @@ -244,7 +241,7 @@ $.widget( "ui.tabs", { if ( panel.length) { that.panels = that.panels.add( panel ); } - $( a ).attr( "aria-controls", selector.substring( 1 ) ); + tab.attr( "aria-controls", selector.substring( 1 ) ); }); }, @@ -278,49 +275,34 @@ $.widget( "ui.tabs", { this.options.disabled = disabled; }, - _setupFx: function( fx ) { - // set up animations - if ( fx ) { - if ( $.isArray( fx ) ) { - this.hideFx = fx[ 0 ]; - this.showFx = fx[ 1 ]; - } else { - this.hideFx = this.showFx = fx; - } - } - }, - _setupEvents: function( event ) { - // attach tab event handler, unbind to avoid duplicates from former tabifying... - this.anchors.unbind( ".tabs" ); - - // TODO: use event delegation via _bind() + var events = { + click: function( event ) { + event.preventDefault(); + } + }; if ( event ) { - this.anchors.bind( event.split( " " ).join( ".tabs " ) + ".tabs", - $.proxy( this, "_eventHandler" ) ); + $.each( event.split(" "), function( index, eventName ) { + events[ eventName ] = "_eventHandler"; + }); } - - // TODO: use event delegation via _bind() - // disable click in any case - this.anchors.bind( "click.tabs", function( event ){ - event.preventDefault(); - }); + this.anchors.unbind( ".tabs" ); + this._bind( this.anchors, events ); }, _eventHandler: function( event ) { - var that = this, - options = that.options, - active = that.active, - clicked = $( event.currentTarget ), - clickedIsActive = clicked[ 0 ] === active[ 0 ], + var options = this.options, + active = this.active, + anchor = $( event.currentTarget ), + tab = anchor.closest( "li" ), + clickedIsActive = tab[ 0 ] === active[ 0 ], collapsing = clickedIsActive && options.collapsible, - toShow = collapsing ? $() : that._getPanelForTab( clicked ), - toHide = !active.length ? $() : that._getPanelForTab( active ), - tab = clicked.closest( "li" ), + toShow = collapsing ? $() : this._getPanelForTab( tab ), + toHide = !active.length ? $() : this._getPanelForTab( active ), eventData = { oldTab: active, oldPanel: toHide, - newTab: collapsing ? $() : clicked, + newTab: collapsing ? $() : tab, newPanel: toShow }; @@ -330,32 +312,30 @@ $.widget( "ui.tabs", { // tab is already loading tab.hasClass( "ui-tabs-loading" ) || // can't switch durning an animation - that.running || + this.running || // click on active header, but not collapsible ( clickedIsActive && !options.collapsible ) || // allow canceling activation - ( that._trigger( "beforeActivate", event, eventData ) === false ) ) { - clicked[ 0 ].blur(); + ( this._trigger( "beforeActivate", event, eventData ) === false ) ) { return; } - options.active = collapsing ? false : that.anchors.index( clicked ); + options.active = collapsing ? false : this.lis.index( tab ); - that.active = clickedIsActive ? $() : clicked; - if ( that.xhr ) { - that.xhr.abort(); + this.active = clickedIsActive ? $() : tab; + if ( this.xhr ) { + this.xhr.abort(); } if ( !toHide.length && !toShow.length ) { - jQuery.error( "jQuery UI Tabs: Mismatching fragment identifier." ); + $.error( "jQuery UI Tabs: Mismatching fragment identifier." ); } if ( toShow.length ) { // TODO make passing in node possible - that.load( that.anchors.index( clicked ), event ); - clicked[ 0 ].blur(); + this.load( this.lis.index( tab ), event ); } - that._toggle( event, eventData ); + this._toggle( event, eventData ); }, // handles show/hide for selecting tabs @@ -364,7 +344,7 @@ $.widget( "ui.tabs", { toShow = eventData.newPanel, toHide = eventData.oldPanel; - that.running = true; + this.running = true; function complete() { that.running = false; @@ -374,11 +354,8 @@ $.widget( "ui.tabs", { function show() { eventData.newTab.closest( "li" ).addClass( "ui-tabs-active ui-state-active" ); - if ( toShow.length && that.showFx ) { - toShow - .animate( that.showFx, that.showFx.duration || "normal", function() { - complete(); - }); + if ( toShow.length && that.options.show ) { + that._show( toShow, that.options.show, complete ); } else { toShow.show(); complete(); @@ -386,8 +363,8 @@ $.widget( "ui.tabs", { } // start out by hiding, then showing, then completing - if ( toHide.length && that.hideFx ) { - toHide.animate( that.hideFx, that.hideFx.duration || "normal", function() { + if ( toHide.length && this.options.hide ) { + this._hide( toHide, this.options.hide, function() { eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" ); show(); }); @@ -399,31 +376,39 @@ $.widget( "ui.tabs", { }, _activate: function( index ) { - var active = this._findActive( index )[ 0 ]; + var anchor, + active = this._findActive( index ); // trying to activate the already active panel - if ( active === this.active[ 0 ] ) { + if ( active[ 0 ] === this.active[ 0 ] ) { return; } // trying to collapse, simulate a click on the current active header - active = active || this.active[ 0 ]; + if ( !active.length ) { + active = this.active; + } + anchor = active.find( ".ui-tabs-anchor" )[ 0 ]; this._eventHandler({ - target: active, - currentTarget: active, + target: anchor, + currentTarget: anchor, preventDefault: $.noop }); }, _findActive: function( selector ) { - return typeof selector === "number" ? this.anchors.eq( selector ) : - typeof selector === "string" ? this.anchors.filter( "[href$='" + selector + "']" ) : $(); + if ( typeof selector === "number" ) { + return this.lis.eq( selector ); + } + if ( typeof selector === "string" ) { + return this.anchors.filter( "[href$='" + selector + "']" ).closest( "li" ); + } + return $(); }, _getIndex: function( index ) { // meta-function to give users option to provide a href string instead of a numerical index. - // also sanitizes numerical indexes to valid values. if ( typeof index === "string" ) { index = this.anchors.index( this.anchors.filter( "[href$='" + index + "']" ) ); } @@ -441,6 +426,7 @@ $.widget( "ui.tabs", { this.list.removeClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" ); this.anchors + .removeClass( "ui-tabs-anchor" ) .unbind( ".tabs" ) .removeData( "href.tabs" ) .removeData( "load.tabs" ); @@ -513,10 +499,11 @@ $.widget( "ui.tabs", { load: function( index, event ) { index = this._getIndex( index ); var that = this, - anchor = this.anchors.eq( index ), - panel = that._getPanelForTab( anchor ), + tab = this.lis.eq( index ), + anchor = tab.find( ".ui-tabs-anchor" ), + panel = this._getPanelForTab( tab ), eventData = { - tab: anchor, + tab: tab, panel: panel }; @@ -533,27 +520,30 @@ $.widget( "ui.tabs", { } }); - if ( this.xhr ) { - this.lis.eq( index ).addClass( "ui-tabs-loading" ); + // support: jQuery <1.8 + // jQuery <1.8 returns false if the request is canceled in beforeSend, + // but as of 1.8, $.ajax() always returns a jqXHR object. + if ( this.xhr && this.xhr.statusText !== "canceled" ) { + tab.addClass( "ui-tabs-loading" ); this.xhr .success(function( response ) { - // TODO: IE resolves cached XHRs immediately - // remove when core #10467 is fixed + // support: jQuery <1.8 + // http://bugs.jquery.com/ticket/11778 setTimeout(function() { panel.html( response ); that._trigger( "load", event, eventData ); }, 1 ); }) .complete(function( jqXHR, status ) { - // TODO: IE resolves cached XHRs immediately - // remove when core #10467 is fixed + // support: jQuery <1.8 + // http://bugs.jquery.com/ticket/11778 setTimeout(function() { if ( status === "abort" ) { that.panels.stop( false, true ); } - that.lis.eq( index ).removeClass( "ui-tabs-loading" ); + tab.removeClass( "ui-tabs-loading" ); if ( jqXHR === that.xhr ) { delete that.xhr; @@ -740,10 +730,10 @@ if ( $.uiBackCompat !== false ) { .replace( /#\{label\}/g, label ) ), id = !url.indexOf( "#" ) ? url.replace( "#", "" ) : - this._tabId( li.find( "a" )[ 0 ] ); + this._tabId( li ); li.addClass( "ui-state-default ui-corner-top" ).data( "ui-tabs-destroy", true ); - li.find( "a" ).attr( "aria-controls", id ); + li.attr( "aria-controls", id ); doInsertAfter = index >= this.lis.length; @@ -786,7 +776,7 @@ if ( $.uiBackCompat !== false ) { index = this._getIndex( index ); var options = this.options, tab = this.lis.eq( index ).remove(), - panel = this._getPanelForTab( tab.find( "a[aria-controls]" ) ).remove(); + panel = this._getPanelForTab( tab ).remove(); // If selected tab was removed focus tab to the right or // in case the last tab was removed the tab to the left. @@ -825,8 +815,10 @@ if ( $.uiBackCompat !== false ) { idPrefix: "ui-tabs-" }, - _tabId: function( a ) { - return $( a ).attr( "aria-controls" ) || + _tabId: function( tab ) { + var a = tab.is( "li" ) ? tab.find( "a[href]" ) : tab; + a = a[0]; + return $( a ).closest( "li" ).attr( "aria-controls" ) || a.title && a.title.replace( /\s/g, "_" ).replace( /[^\w\u00c0-\uFFFF\-]/g, "" ) || this.options.idPrefix + getNextTabId(); } @@ -892,7 +884,8 @@ if ( $.uiBackCompat !== false ) { this._super(); if ( this.options.active !== false ) { this._trigger( "show", null, this._ui( - this.active[ 0 ], this._getPanelForTab( this.active )[ 0 ] ) ); + this.active.find( ".ui-tabs-anchor" )[ 0 ], + this._getPanelForTab( this.active )[ 0 ] ) ); } }, _trigger: function( type, event, data ) { @@ -902,13 +895,13 @@ if ( $.uiBackCompat !== false ) { } if ( type === "beforeActivate" && data.newTab.length ) { ret = this._super( "select", event, { - tab: data.newTab[ 0], + tab: data.newTab.find( ".ui-tabs-anchor" )[ 0], panel: data.newPanel[ 0 ], index: data.newTab.closest( "li" ).index() }); } else if ( type === "activate" && data.newTab.length ) { ret = this._super( "show", event, { - tab: data.newTab[ 0 ], + tab: data.newTab.find( ".ui-tabs-anchor" )[ 0 ], panel: data.newPanel[ 0 ], index: data.newTab.closest( "li" ).index() }); @@ -990,11 +983,81 @@ if ( $.uiBackCompat !== false ) { var _data = $.extend( {}, data ); if ( type === "load" ) { _data.panel = _data.panel[ 0 ]; - _data.tab = _data.tab[ 0 ]; + _data.tab = _data.tab.find( ".ui-tabs-anchor" )[ 0 ]; } return this._super( type, event, _data ); } }); + + // fx option + // The new animation options (show, hide) conflict with the old show callback. + // The old fx option wins over show/hide anyway (always favor back-compat). + // If a user wants to use the new animation API, they must give up the old API. + $.widget( "ui.tabs", $.ui.tabs, { + options: { + fx: null // e.g. { height: "toggle", opacity: "toggle", duration: 200 } + }, + + _getFx: function() { + var hide, show, + fx = this.options.fx; + + if ( fx ) { + if ( $.isArray( fx ) ) { + hide = fx[ 0 ]; + show = fx[ 1 ]; + } else { + hide = show = fx; + } + } + + return fx ? { show: show, hide: hide } : null; + }, + + _toggle: function( event, eventData ) { + var that = this, + toShow = eventData.newPanel, + toHide = eventData.oldPanel, + fx = this._getFx(); + + if ( !fx ) { + return this._super( event, eventData ); + } + + that.running = true; + + function complete() { + that.running = false; + that._trigger( "activate", event, eventData ); + } + + function show() { + eventData.newTab.closest( "li" ).addClass( "ui-tabs-active ui-state-active" ); + + if ( toShow.length && fx.show ) { + toShow + .animate( fx.show, fx.show.duration, function() { + complete(); + }); + } else { + toShow.show(); + complete(); + } + } + + // start out by hiding, then showing, then completing + if ( toHide.length && fx.hide ) { + toHide.animate( fx.hide, fx.hide.duration, function() { + eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" ); + show(); + }); + } else { + eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" ); + toHide.hide(); + show(); + } + } + }); } })( jQuery ); |