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