1/* 2 * jQuery UI Tabs @VERSION 3 * 4 * Copyright (c) 2007, 2008 Klaus Hartl (stilbuero.de) 5 * Dual licensed under the MIT (MIT-LICENSE.txt) 6 * and GPL (GPL-LICENSE.txt) licenses. 7 * 8 * http://docs.jquery.com/UI/Tabs 9 * 10 * Depends: 11 * ui.core.js 12 */ 13(function($) { 14 15$.widget("ui.tabs", { 16 _init: function() { 17 // create tabs 18 this._tabify(true); 19 }, 20 _setData: function(key, value) { 21 if ((/^selected/).test(key)) 22 this.select(value); 23 else { 24 this.options[key] = value; 25 this._tabify(); 26 } 27 }, 28 length: function() { 29 return this.$tabs.length; 30 }, 31 _tabId: function(a) { 32 return a.title && a.title.replace(/\s/g, '_').replace(/[^A-Za-z0-9\-_:\.]/g, '') 33 || this.options.idPrefix + $.data(a); 34 }, 35 ui: function(tab, panel) { 36 return { 37 options: this.options, 38 tab: tab, 39 panel: panel, 40 index: this.$tabs.index(tab) 41 }; 42 }, 43 _sanitizeSelector: function(hash) { 44 return hash.replace(/:/g, '\\:'); // we need this because an id may contain a ":" 45 }, 46 _cookie: function() { 47 var cookie = this.cookie || (this.cookie = 'ui-tabs-' + $.data(this.element[0])); 48 return $.cookie.apply(null, [cookie].concat($.makeArray(arguments))); 49 }, 50 _tabify: function(init) { 51 52 this.$lis = $('li:has(a[href])', this.element); 53 this.$tabs = this.$lis.map(function() { return $('a', this)[0]; }); 54 this.$panels = $([]); 55 56 var self = this, o = this.options; 57 58 this.$tabs.each(function(i, a) { 59 // inline tab 60 if (a.hash && a.hash.replace('#', '')) // Safari 2 reports '#' for an empty hash 61 self.$panels = self.$panels.add(self._sanitizeSelector(a.hash)); 62 // remote tab 63 else if ($(a).attr('href') != '#') { // prevent loading the page itself if href is just "#" 64 $.data(a, 'href.tabs', a.href);
64 // required for restore on destroy 65 $.data(a, 'load.tabs', a.href); // mutable 66 var id = self._tabId(a); 67 a.href = '#' + id; 68 var $panel = $('#' + id); 69 if (!$panel.length) { 70 $panel = $(o.panelTemplate).attr('id', id).addClass(o.panelClass) 71 .insertAfter(self.$panels[i - 1] || self.element); 72 $panel.data('destroy.tabs', true); 73 } 74 self.$panels = self.$panels.add($panel); 75 } 76 // invalid tab href 77 else 78 o.disabled.push(i + 1); 79 }); 80 81 // initialization from scratch 82 if (init) { 83 84 // attach necessary classes for styling if not present 85 this.element.addClass(o.navClass); 86 this.$panels.addClass(o.panelClass); 87 88 // Selected tab 89 // use "selected" option or try to retrieve: 90 // 1. from fragment identifier in url 91 // 2. from cookie 92 // 3. from selected class attribute on <li> 93 if (o.selected === undefined) { 94 if (location.hash) { 95 this.$tabs.each(function(i, a) { 96 if (a.hash == location.hash) { 97 o.selected = i; 98 return false; // break 99 } 100 }); 101 } 102 else if (o.cookie) { 103 var index = parseInt(self._cookie(), 10); 104 if (index && self.$tabs[index]) o.selected = index; 105 } 106 else if (self.$lis.filter('.' + o.selectedClass).length) 107 o.selected = self.$lis.index( self.$lis.filter('.' + o.selectedClass)[0] ); 108 } 109 o.selected = o.selected === null || o.selected !== undefined ? o.selected : 0; // first tab selected by default 110 111 // Take disabling tabs via class attribute from HTML 112 // into account and update option properly. 113 // A selected tab cannot become disabled. 114 o.disabled = $.unique(o.disabled.concat( 115 $.map(this.$lis.filter('.' + o.disabledClass), 116 function(n, i) { return self.$lis.index(n); } ) 117 )).sort(); 118 if ($.inArray(o.selected, o.disabled) != -1) 119 o.disabled.splice($.inArray(o.selected, o.disabled), 1); 120 121 // highlight selected tab 122 this.$panels.addClass(o.hideClass); 123 this.$lis.removeClass(o.selectedClass); 124 if (o.selected !== null) { 125 this.$panels.eq(o.selected).removeClass(o.hideClass); 126 var classes = [o.selectedClass]; 127 if (o.deselectable) classes.push(o.deselectableClass);
128 this.$lis.eq(o.selected).addClass(classes.join(' ')); 129 130 // seems to be expected behavior that the show callback is fired 131 var onShow = function() { 132 self._trigger('show', null, 133 self.ui(self.$tabs[o.selected], self.$panels[o.selected])); 134 }; 135 136 // load if remote tab 137 if ($.data(this.$tabs[o.selected], 'load.tabs')) 138 this.load(o.selected, onShow); 139 // just trigger show event 140 else onShow(); 141 } 142 143 // clean up to avoid memory leaks in certain versions of IE 6 144 $(window).bind('unload', function() { 145 self.$tabs.unbind('.tabs'); 146 self.$lis = self.$tabs = self.$panels = null; 147 }); 148 149 } 150 // update selected after add/remove 151 else 152 o.selected = this.$lis.index( this.$lis.filter('.' + o.selectedClass)[0] ); 153 154 // set or update cookie after init and add/remove respectively 155 if (o.cookie) this._cookie(o.selected, o.cookie); 156 157 // disable tabs 158 for (var i = 0, li; li = this.$lis[i]; i++) 159 $(li)[$.inArray(i, o.disabled) != -1 && !$(li).hasClass(o.selectedClass) ? 'addClass' : 'removeClass'](o.disabledClass); 160 161 // reset cache if switching from cached to not cached 162 if (o.cache === false) this.$tabs.removeData('cache.tabs'); 163 164 // set up animations 165 var hideFx, showFx; 166 if (o.fx) { 167 if (o.fx.constructor == Array) { 168 hideFx = o.fx[0]; 169 showFx = o.fx[1]; 170 } 171 else hideFx = showFx = o.fx; 172 } 173 174 // Reset certain styles left over from animation 175 // and prevent IE's ClearType bug... 176 function resetStyle($el, fx) { 177 $el.css({ display: '' }); 178 if ($.browser.msie && fx.opacity) $el[0].style.removeAttribute('filter'); 179 } 180 181 // Show a tab... 182 var showTab = showFx ? 183 function(clicked, $show) { 184 $show.animate(showFx, showFx.duration || 'normal', function() { 185 $show.removeClass(o.hideClass); 186 resetStyle($show, showFx); 187 self._trigger('show', null, self.ui(clicked, $show[0])); 188 }); 189 } : 190 function(clicked, $show) { 191 $show.removeClass(o.hideClass); 192 self._trigger('show', null, self.ui(clicked, $show[0])); 193 }; 194 195 // Hide a tab, $show is optional... 196 var hideTab = hideFx ? 197 function(clicked, $hide, $show) { 198 $hide.animate(hideFx, hideFx.duration || 'normal', function() { 199 $hide.addClass(o.hideClass); 200 resetStyle($hide, hideFx); 201 if ($show) showTab(clicked, $show, $hide); 202 }); 203 } : 204 function(clicked, $hide, $show) { 205 $hide.addClass(o.hideClass); 206 if ($show) showTab(clicked, $show); 207 }; 208 209 // Switch a tab... 210 function switchTab(clicked, $li, $hide, $show) { 211 var classes = [o.selectedClass]; 212 if (o.deselectable) classes.push(o.deselectableClass);
213 $li.addClass(classes.join(' ')).siblings().removeClass(classes.join(' ')); 214 hideTab(clicked, $hide, $show); 215 } 216 217 // attach tab event handler, unbind to avoid duplicates from former tabifying... 218 this.$tabs.unbind('.tabs').bind(o.event + '.tabs', function() { 219 220 //var trueClick = e.clientX; // add to history only if true click occured, not a triggered click 221 var $li = $(this).parents('li:eq(0)'), 222 $hide = self.$panels.filter(':visible'), 223 $show = $(self._sanitizeSelector(this.hash)); 224 225 // If tab is already selected and not deselectable or tab disabled or 226 // or is already loading or click callback returns false stop here. 227 // Check if click handler returns false last so that it is not executed 228 // for a disabled or loading tab! 229 if (($li.hasClass(o.selectedClass) && !o.deselectable) 230 || $li.hasClass(o.disabledClass) 231 || $(this).hasClass(o.loadingClass) 232 || self._trigger('select', null, self.ui(this, $show[0])) === false 233 ) { 234 this.blur(); 235 return false; 236 } 237 238 o.selected = self.$tabs.index(this); 239 240 // if tab may be closed 241 if (o.deselectable) { 242 if ($li.hasClass(o.selectedClass)) { 243 self.options.selected = null; 244 $li.removeClass([o.selectedClass, o.deselectableClass].join(' ')); 245 self.$panels.stop(); 246 hideTab(this, $hide); 247 this.blur(); 248 return false; 249 } else if (!$hide.length) { 250 self.$panels.stop(); 251 var a = this; 252 self.load(self.$tabs.index(this), function() { 253 $li.addClass([o.selectedClass, o.deselectableClass].join(' ')); 254 showTab(a, $show); 255 }); 256 this.blur(); 257 return false; 258 } 259 } 260
261 if (o.cookie) self._cookie(o.selected, o.cookie); 262 263 // stop possibly running animations 264 self.$panels.stop(); 265 266 // show new tab 267 if ($show.length) { 268 var a = this; 269 self.load(self.$tabs.index(this), $hide.length ? 270 function() { 271 switchTab(a, $li, $hide, $show); 272 } : 273 function() { 274 $li.addClass(o.selectedClass); 275 showTab(a, $show); 276 } 277 ); 278 } else 279 throw 'jQuery UI Tabs: Mismatching fragment identifier.'; 280 281 // Prevent IE from keeping other link focussed when using the back button 282 // and remove dotted border from clicked link. This is controlled via CSS 283 // in modern browsers; blur() removes focus from address bar in Firefox 284 // which can become a usability and annoying problem with tabs('rotate'). 285 if ($.browser.msie) this.blur(); 286 287 return false; 288 289 }); 290 291 // disable click if event is configured to something else 292 if (o.event != 'click') this.$tabs.bind('click.tabs', function(){return false;}); 293 294 }, 295 add: function(url, label, index) { 296 if (index == undefined) 297 index = this.$tabs.length; // append by default 298 299 var o = this.options; 300 var $li = $(o.tabTemplate.replace(/#\{href\}/g, url).replace(/#\{label\}/g, label)); 301 $li.data('destroy.tabs', true); 302 303 var id = url.indexOf('#') == 0 ? url.replace('#', '') : this._tabId( $('a:first-child', $li)[0] ); 304 305 // try to find an existing element before creating a new one 306 var $panel = $('#' + id); 307 if (!$panel.length) { 308 $panel = $(o.panelTemplate).attr('id', id) 309 .addClass(o.hideClass) 310 .data('destroy.tabs', true); 311 } 312 $panel.addClass(o.panelClass); 313 if (index >= this.$lis.length) { 314 $li.appendTo(this.element); 315 $panel.appendTo(this.element[0].parentNode); 316 } else { 317 $li.insertBefore(this.$lis[index]); 318 $panel.insertBefore(this.$panels[index]); 319 } 320 321 o.disabled = $.map(o.disabled, 322 function(n, i) { return n >= index ? ++n : n }); 323 324 this._tabify(); 325 326 if (this.$tabs.length == 1) { 327 $li.addClass(o.selectedClass); 328 $panel.removeClass(o.hideClass); 329 var href = $.data(this.$tabs[0], 'load.tabs'); 330 if (href) 331 this.load(index, href); 332 } 333 334 // callback 335 this._trigger('add', null, this.ui(this.$tabs[index], this.$panels[index])); 336 }, 337 remove: function(index) { 338 var o = this.options, $li = this.$lis.eq(index).remove(), 339 $panel = this.$panels.eq(index).remove(); 340 341 // If selected tab was removed focus tab to the right or 342 // in case the last tab was removed the tab to the left. 343 if ($li.hasClass(o.selectedClass) && this.$tabs.length > 1) 344 this.select(index + (index + 1 < this.$tabs.length ? 1 : -1)); 345 346 o.disabled = $.map($.grep(o.disabled, function(n, i) { return n != index; }), 347 function(n, i) { return n >= index ? --n : n }); 348 349 this._tabify(); 350 351 // callback 352 this._trigger('remove', null, this.ui($li.find('a')[0], $panel[0])); 353 }, 354 enable: function(index) { 355 var o = this.options; 356 if ($.inArray(index, o.disabled) == -1) 357 return; 358 359 var $li = this.$lis.eq(index).removeClass(o.disabledClass); 360 if ($.browser.safari) { // fix disappearing tab (that used opacity indicating disabling) after enabling in Safari 2... 361 $li.css('display', 'inline-block'); 362 setTimeout(function() { 363 $li.css('display', 'block'); 364 }, 0); 365 } 366 367 o.disabled = $.grep(o.disabled, function(n, i) { return n != index; }); 368 369 // callback 370 this._trigger('enable', null, this.ui(this.$tabs[index], this.$panels[index])); 371 }, 372 disable: function(index) { 373 var self = this, o = this.options; 374 if (index != o.selected) { // cannot disable already selected tab 375 this.$lis.eq(index).addClass(o.disabledClass); 376 377 o.disabled.push(index); 378 o.disabled.sort(); 379 380 // callback 381 this._trigger('disable', null, this.ui(this.$tabs[index], this.$panels[index])); 382 } 383 }, 384 select: function(index) { 385 // TODO make null as argument work 386 if (typeof index == 'string') 387 index = this.$tabs.index( this.$tabs.filter('[href$=' + index + ']')[0] ); 388 this.$tabs.eq(index).trigger(this.options.event + '.tabs'); 389 }, 390 load: function(index, callback) { // callback is for internal usage only 391 392 var self = this, o = this.options, $a = this.$tabs.eq(index), a = $a[0], 393 bypassCache = callback == undefined || callback === false, url = $a.data('load.tabs'); 394 395 callback = callback || function() {}; 396 397 // no remote or from cache - just finish with callback 398 if (!url || !bypassCache && $.data(a, 'cache.tabs')) { 399 callback(); 400 return; 401 } 402 403 // load remote from here on 404 405 var inner = function(parent) { 406 var $parent = $(parent), $inner = $parent.find('*:last'); 407 return $inner.length && $inner.is(':not(img)') && $inner || $parent; 408 }; 409 var cleanup = function() { 410 self.$tabs.filter('.' + o.loadingClass).removeClass(o.loadingClass) 411 .each(function() { 412 if (o.spinner) 413 inner(this).parent().html(inner(this).data('label.tabs')); 414 }); 415 self.xhr = null; 416 }; 417
418 if (o.spinner) { 419 var label = inner(a).html(); 420 inner(a).wrapInner('<em></em>') 421 .find('em').data('label.tabs', label).html(o.spinner); 422 } 423 424 var ajaxOptions = $.extend({}, o.ajaxOptions, { 425 url: url, 426 success: function(r, s) { 427 $(self._sanitizeSelector(a.hash)).html(r); 428 cleanup(); 429 430 if (o.cache) 431 $.data(a, 'cache.tabs', true); // if loaded once do not load them again 432 433 // callbacks 434 self._trigger('load', null, self.ui(self.$tabs[index], self.$panels[index])); 435 try { 436 o.ajaxOptions.success(r, s); 437 } 438 catch (e) {} 439 440 // This callback is required because the switch has to take 441 // place after loading has completed. Call last in order to 442 // fire load before show callback... 443 callback(); 444 } 445 }); 446 if (this.xhr) { 447 // terminate pending requests from other tabs and restore tab label 448 this.xhr.abort(); 449 cleanup(); 450 } 451 $a.addClass(o.loadingClass); 452 self.xhr = $.ajax(ajaxOptions); 453 }, 454 url: function(index, url) { 455 this.$tabs.eq(index).removeData('cache.tabs').data('load.tabs', url); 456 }, 457 destroy: function() { 458 var o = this.options; 459 this.element.unbind('.tabs') 460 .removeClass(o.navClass).removeData('tabs'); 461 this.$tabs.each(function() { 462 var href = $.data(this, 'href.tabs'); 463 if (href) 464 this.href = href; 465 var $this = $(this).unbind('.tabs'); 466 $.each(['href', 'load', 'cache'], function(i, prefix) { 467 $this.removeData(prefix + '.tabs'); 468 }); 469 }); 470 this.$lis.add(this.$panels).each(function() { 471 if ($.data(this, 'destroy.tabs')) 472 $(this).remove(); 473 else 474 $(this).removeClass([o.selectedClass, o.deselectableClass, 475 o.disabledClass, o.panelClass, o.hideClass].join(' ')); 476 }); 477 if (o.cookie) 478 this._cookie(null, o.cookie); 479 } 480}); 481 482$.extend($.ui.tabs, { 483 version: '@VERSION', 484 getter: 'length', 485 defaults: { 486 // basic setup 487 deselectable: false, 488 event: 'click', 489 disabled: [], 490 cookie: null, // e.g. { expires: 7, path: '/', domain: 'jquery.com', secure: true } 491 // Ajax 492 spinner: 'Loading…', 493 cache: false, 494 idPrefix: 'ui-tabs-', 495 ajaxOptions: null, 496 // animations 497 fx: null, // e.g. { height: 'toggle', opacity: 'toggle', duration: 200 } 498 // templates 499 tabTemplate: '<li><a href="#{href}"><span>#{label}</span></a></li>', 500 panelTemplate: '<div></div>', 501 // CSS class names 502 navClass: 'ui-tabs-nav', 503 selectedClass: 'ui-tabs-selected', 504 deselectableClass: 'ui-tabs-deselectable', 505 disabledClass: 'ui-tabs-disabled', 506 panelClass: 'ui-tabs-panel', 507 hideClass: 'ui-tabs-hide', 508 loadingClass: 'ui-tabs-loading' 509 } 510}); 511 512/* 513 * Tabs Extensions 514 */ 515 516/* 517 * Rotate 518 */ 519$.extend($.ui.tabs.prototype, { 520 rotation: null, 521 rotate: function(ms, continuing) { 522 523 continuing = continuing || false; 524 525 var self = this, t = this.options.selected; 526 527 function start() { 528 self.rotation = setInterval(function() { 529 t = ++t < self.$tabs.length ? t : 0; 530 self.select(t); 531 }, ms); 532 } 533 534 function stop(e) { 535 if (!e || e.clientX) { // only in case of a true click 536 clearInterval(self.rotation); 537 } 538 } 539 540 // start interval 541 if (ms) { 542 start(); 543 if (!continuing) 544 this.$tabs.bind(this.options.event + '.tabs', stop); 545 else 546 this.$tabs.bind(this.options.event + '.tabs', function() { 547 stop(); 548 t = self.options.selected; 549 start(); 550 }); 551 } 552 // stop interval 553 else { 554 stop(); 555 this.$tabs.unbind(this.options.event + '.tabs', stop); 556 } 557 } 558}); 559 560})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.