/* * jQuery UI Tabs @VERSION * * Copyright (c) 2007, 2008 Klaus Hartl (stilbuero.de) * Dual licensed under the MIT (MIT-LICENSE.txt) * and GPL (GPL-LICENSE.txt) licenses. * * http://docs.jquery.com/UI/Tabs * * Depends: * ui.core.js */ (function(jQuery) { jQuery.widget("ui.tabs", { _init: function() { // create tabs this._tabify(true); }, _setData: function(key, value) { if ((/^selected/).test(key)) this.select(value); else { this.options[key] = value; this._tabify(); } }, length: function() { return this.jQuerytabs.length; }, _tabId: function(a) { return a.title && a.title.replace(/\s/g, '_').replace(/[^A-Za-z0-9\-_:\.]/g, '') || this.options.idPrefix + jQuery.data(a); }, ui: function(tab, panel) { return { options: this.options, tab: tab, panel: panel, index: this.jQuerytabs.index(tab) }; }, _sanitizeSelector: function(hash) { return hash.replace(/:/g, '\\:'); // we need this because an id may contain a ":" }, _cookie: function() { var cookie = this.cookie || (this.cookie = 'ui-tabs-' + jQuery.data(this.element[0])); return jQuery.cookie.apply(null, [cookie].concat(jQuery.makeArray(arguments))); }, _tabify: function(init) { this.jQuerylis = jQuery('li:has(a[href])', this.element); this.jQuerytabs = this.jQuerylis.map(function() { return jQuery('a', this)[0]; }); this.jQuerypanels = jQuery([]); var self = this, o = this.options; this.jQuerytabs.each(function(i, a) { // inline tab if (a.hash && a.hash.replace('#', '')) // Safari 2 reports '#' for an empty hash self.jQuerypanels = self.jQuerypanels.add(self._sanitizeSelector(a.hash)); // remote tab else if (jQuery(a).attr('href') != '#') { // prevent loading the page itself if href is just "#" jQuery.data(a, 'href.tabs', a.href); // required for restore on destroy jQuery.data(a, 'load.tabs', a.href); // mutable var id = self._tabId(a); a.href = '#' + id; var jQuerypanel = jQuery('#' + id); if (!jQuerypanel.length) { jQuerypanel = jQuery(o.panelTemplate).attr('id', id).addClass(o.panelClass) .insertAfter(self.jQuerypanels[i - 1] || self.element); jQuerypanel.data('destroy.tabs', true); } self.jQuerypanels = self.jQuerypanels.add(jQuerypanel); } // invalid tab href else o.disabled.push(i + 1); }); // initialization from scratch if (init) { // attach necessary classes for styling if not present this.element.addClass(o.navClass); this.jQuerypanels.addClass(o.panelClass); // Selected tab // use "selected" option or try to retrieve: // 1. from fragment identifier in url // 2. from cookie // 3. from selected class attribute on
  • if (o.selected === undefined) { if (location.hash) { this.jQuerytabs.each(function(i, a) { if (a.hash == location.hash) { o.selected = i; return false; // break } }); } else if (o.cookie) { var index = parseInt(self._cookie(), 10); if (index && self.jQuerytabs[index]) o.selected = index; } else if (self.jQuerylis.filter('.' + o.selectedClass).length) o.selected = self.jQuerylis.index( self.jQuerylis.filter('.' + o.selectedClass)[0] ); } o.selected = o.selected === null || o.selected !== undefined ? o.selected : 0; // first tab selected by default // Take disabling tabs via class attribute from HTML // into account and update option properly. // A selected tab cannot become disabled. o.disabled = jQuery.unique(o.disabled.concat( jQuery.map(this.jQuerylis.filter('.' + o.disabledClass), function(n, i) { return self.jQuerylis.index(n); } ) )).sort(); if (jQuery.inArray(o.selected, o.disabled) != -1) o.disabled.splice(jQuery.inArray(o.selected, o.disabled), 1); // highlight selected tab this.jQuerypanels.addClass(o.hideClass); this.jQuerylis.removeClass(o.selectedClass); if (o.selected !== null) { this.jQuerypanels.eq(o.selected).removeClass(o.hideClass); var classes = [o.selectedClass]; if (o.deselectable) classes.push(o.deselectableClass); this.jQuerylis.eq(o.selected).addClass(classes.join(' ')); // seems to be expected behavior that the show callback is fired var onShow = function() { self._trigger('show', null, self.ui(self.jQuerytabs[o.selected], self.jQuerypanels[o.selected])); }; // load if remote tab if (jQuery.data(this.jQuerytabs[o.selected], 'load.tabs')) this.load(o.selected, onShow); // just trigger show event else onShow(); } // clean up to avoid memory leaks in certain versions of IE 6 jQuery(window).bind('unload', function() { self.jQuerytabs.unbind('.tabs'); self.jQuerylis = self.jQuerytabs = self.jQuerypanels = null; }); } // update selected after add/remove else o.selected = this.jQuerylis.index( this.jQuerylis.filter('.' + o.selectedClass)[0] ); // set or update cookie after init and add/remove respectively if (o.cookie) this._cookie(o.selected, o.cookie); // disable tabs for (var i = 0, li; li = this.jQuerylis[i]; i++) jQuery(li)[jQuery.inArray(i, o.disabled) != -1 && !jQuery(li).hasClass(o.selectedClass) ? 'addClass' : 'removeClass'](o.disabledClass); // reset cache if switching from cached to not cached if (o.cache === false) this.jQuerytabs.removeData('cache.tabs'); // set up animations var hideFx, showFx; if (o.fx) { if (o.fx.constructor == Array) { hideFx = o.fx[0]; showFx = o.fx[1]; } else hideFx = showFx = o.fx; } // Reset certain styles left over from animation // and prevent IE's ClearType bug... function resetStyle(jQueryel, fx) { jQueryel.css({ display: '' }); if (jQuery.browser.msie && fx.opacity) jQueryel[0].style.removeAttribute('filter'); } // Show a tab... var showTab = showFx ? function(clicked, jQueryshow) { jQueryshow.animate(showFx, showFx.duration || 'normal', function() { jQueryshow.removeClass(o.hideClass); resetStyle(jQueryshow, showFx); self._trigger('show', null, self.ui(clicked, jQueryshow[0])); }); } : function(clicked, jQueryshow) { jQueryshow.removeClass(o.hideClass); self._trigger('show', null, self.ui(clicked, jQueryshow[0])); }; // Hide a tab, jQueryshow is optional... var hideTab = hideFx ? function(clicked, jQueryhide, jQueryshow) { jQueryhide.animate(hideFx, hideFx.duration || 'normal', function() { jQueryhide.addClass(o.hideClass); resetStyle(jQueryhide, hideFx); if (jQueryshow) showTab(clicked, jQueryshow, jQueryhide); }); } : function(clicked, jQueryhide, jQueryshow) { jQueryhide.addClass(o.hideClass); if (jQueryshow) showTab(clicked, jQueryshow); }; // Switch a tab... function switchTab(clicked, jQueryli, jQueryhide, jQueryshow) { var classes = [o.selectedClass]; if (o.deselectable) classes.push(o.deselectableClass); jQueryli.addClass(classes.join(' ')).siblings().removeClass(classes.join(' ')); hideTab(clicked, jQueryhide, jQueryshow); } // attach tab event handler, unbind to avoid duplicates from former tabifying... this.jQuerytabs.unbind('.tabs').bind(o.event + '.tabs', function() { //var trueClick = e.clientX; // add to history only if true click occured, not a triggered click var jQueryli = jQuery(this).parents('li:eq(0)'), jQueryhide = self.jQuerypanels.filter(':visible'), jQueryshow = jQuery(self._sanitizeSelector(this.hash)); // If tab is already selected and not deselectable or tab disabled or // or is already loading or click callback returns false stop here. // Check if click handler returns false last so that it is not executed // for a disabled or loading tab! if ((jQueryli.hasClass(o.selectedClass) && !o.deselectable) || jQueryli.hasClass(o.disabledClass) || jQuery(this).hasClass(o.loadingClass) || self._trigger('select', null, self.ui(this, jQueryshow[0])) === false ) { this.blur(); return false; } o.selected = self.jQuerytabs.index(this); // if tab may be closed if (o.deselectable) { if (jQueryli.hasClass(o.selectedClass)) { self.options.selected = null; jQueryli.removeClass([o.selectedClass, o.deselectableClass].join(' ')); self.jQuerypanels.stop(); hideTab(this, jQueryhide); this.blur(); return false; } else if (!jQueryhide.length) { self.jQuerypanels.stop(); var a = this; self.load(self.jQuerytabs.index(this), function() { jQueryli.addClass([o.selectedClass, o.deselectableClass].join(' ')); showTab(a, jQueryshow); }); this.blur(); return false; } } if (o.cookie) self._cookie(o.selected, o.cookie); // stop possibly running animations self.jQuerypanels.stop(); // show new tab if (jQueryshow.length) { var a = this; self.load(self.jQuerytabs.index(this), jQueryhide.length ? function() { switchTab(a, jQueryli, jQueryhide, jQueryshow); } : function() { jQueryli.addClass(o.selectedClass); showTab(a, jQueryshow); } ); } else throw 'jQuery UI Tabs: Mismatching fragment identifier.'; // Prevent IE from keeping other link focussed when using the back button // and remove dotted border from clicked link. This is controlled via CSS // in modern browsers; blur() removes focus from address bar in Firefox // which can become a usability and annoying problem with tabs('rotate'). if (jQuery.browser.msie) this.blur(); return false; }); // disable click if event is configured to something else if (o.event != 'click') this.jQuerytabs.bind('click.tabs', function(){return false;}); }, add: function(url, label, index) { if (index == undefined) index = this.jQuerytabs.length; // append by default var o = this.options; var jQueryli = jQuery(o.tabTemplate.replace(/#\{href\}/g, url).replace(/#\{label\}/g, label)); jQueryli.data('destroy.tabs', true); var id = url.indexOf('#') == 0 ? url.replace('#', '') : this._tabId( jQuery('a:first-child', jQueryli)[0] ); // try to find an existing element before creating a new one var jQuerypanel = jQuery('#' + id); if (!jQuerypanel.length) { jQuerypanel = jQuery(o.panelTemplate).attr('id', id) .addClass(o.hideClass) .data('destroy.tabs', true); } jQuerypanel.addClass(o.panelClass); if (index >= this.jQuerylis.length) { jQueryli.appendTo(this.element); jQuerypanel.appendTo(this.element[0].parentNode); } else { jQueryli.insertBefore(this.jQuerylis[index]); jQuerypanel.insertBefore(this.jQuerypanels[index]); } o.disabled = jQuery.map(o.disabled, function(n, i) { return n >= index ? ++n : n }); this._tabify(); if (this.jQuerytabs.length == 1) { jQueryli.addClass(o.selectedClass); jQuerypanel.removeClass(o.hideClass); var href = jQuery.data(this.jQuerytabs[0], 'load.tabs'); if (href) this.load(index, href); } // callback this._trigger('add', null, this.ui(this.jQuerytabs[index], this.jQuerypanels[index])); }, remove: function(index) { var o = this.options, jQueryli = this.jQuerylis.eq(index).remove(), jQuerypanel = this.jQuerypanels.eq(index).remove(); // If selected tab was removed focus tab to the right or // in case the last tab was removed the tab to the left. if (jQueryli.hasClass(o.selectedClass) && this.jQuerytabs.length > 1) this.select(index + (index + 1 < this.jQuerytabs.length ? 1 : -1)); o.disabled = jQuery.map(jQuery.grep(o.disabled, function(n, i) { return n != index; }), function(n, i) { return n >= index ? --n : n }); this._tabify(); // callback this._trigger('remove', null, this.ui(jQueryli.find('a')[0], jQuerypanel[0])); }, enable: function(index) { var o = this.options; if (jQuery.inArray(index, o.disabled) == -1) return; var jQueryli = this.jQuerylis.eq(index).removeClass(o.disabledClass); if (jQuery.browser.safari) { // fix disappearing tab (that used opacity indicating disabling) after enabling in Safari 2... jQueryli.css('display', 'inline-block'); setTimeout(function() { jQueryli.css('display', 'block'); }, 0); } o.disabled = jQuery.grep(o.disabled, function(n, i) { return n != index; }); // callback this._trigger('enable', null, this.ui(this.jQuerytabs[index], this.jQuerypanels[index])); }, disable: function(index) { var self = this, o = this.options; if (index != o.selected) { // cannot disable already selected tab this.jQuerylis.eq(index).addClass(o.disabledClass); o.disabled.push(index); o.disabled.sort(); // callback this._trigger('disable', null, this.ui(this.jQuerytabs[index], this.jQuerypanels[index])); } }, select: function(index) { // TODO make null as argument work if (typeof index == 'string') index = this.jQuerytabs.index( this.jQuerytabs.filter('[hrefjQuery=' + index + ']')[0] ); this.jQuerytabs.eq(index).trigger(this.options.event + '.tabs'); }, load: function(index, callback) { // callback is for internal usage only var self = this, o = this.options, jQuerya = this.jQuerytabs.eq(index), a = jQuerya[0], bypassCache = callback == undefined || callback === false, url = jQuerya.data('load.tabs'); callback = callback || function() {}; // no remote or from cache - just finish with callback if (!url || !bypassCache && jQuery.data(a, 'cache.tabs')) { callback(); return; } // load remote from here on var inner = function(parent) { var jQueryparent = jQuery(parent), jQueryinner = jQueryparent.find('*:last'); return jQueryinner.length && jQueryinner.is(':not(img)') && jQueryinner || jQueryparent; }; var cleanup = function() { self.jQuerytabs.filter('.' + o.loadingClass).removeClass(o.loadingClass) .each(function() { if (o.spinner) inner(this).parent().html(inner(this).data('label.tabs')); }); self.xhr = null; }; if (o.spinner) { var label = inner(a).html(); inner(a).wrapInner('') .find('em').data('label.tabs', label).html(o.spinner); } var ajaxOptions = jQuery.extend({}, o.ajaxOptions, { url: url, success: function(r, s) { jQuery(self._sanitizeSelector(a.hash)).html(r); cleanup(); if (o.cache) jQuery.data(a, 'cache.tabs', true); // if loaded once do not load them again // callbacks self._trigger('load', null, self.ui(self.jQuerytabs[index], self.jQuerypanels[index])); try { o.ajaxOptions.success(r, s); } catch (e) {} // This callback is required because the switch has to take // place after loading has completed. Call last in order to // fire load before show callback... callback(); } }); if (this.xhr) { // terminate pending requests from other tabs and restore tab label this.xhr.abort(); cleanup(); } jQuerya.addClass(o.loadingClass); self.xhr = jQuery.ajax(ajaxOptions); }, url: function(index, url) { this.jQuerytabs.eq(index).removeData('cache.tabs').data('load.tabs', url); }, destroy: function() { var o = this.options; this.element.unbind('.tabs') .removeClass(o.navClass).removeData('tabs'); this.jQuerytabs.each(function() { var href = jQuery.data(this, 'href.tabs'); if (href) this.href = href; var jQuerythis = jQuery(this).unbind('.tabs'); jQuery.each(['href', 'load', 'cache'], function(i, prefix) { jQuerythis.removeData(prefix + '.tabs'); }); }); this.jQuerylis.add(this.jQuerypanels).each(function() { if (jQuery.data(this, 'destroy.tabs')) jQuery(this).remove(); else jQuery(this).removeClass([o.selectedClass, o.deselectableClass, o.disabledClass, o.panelClass, o.hideClass].join(' ')); }); if (o.cookie) this._cookie(null, o.cookie); } }); jQuery.extend(jQuery.ui.tabs, { version: '@VERSION', getter: 'length', defaults: { // basic setup deselectable: false, event: 'click', disabled: [], cookie: null, // e.g. { expires: 7, path: '/', domain: 'jquery.com', secure: true } // Ajax spinner: 'Loading…', cache: false, idPrefix: 'ui-tabs-', ajaxOptions: null, // animations fx: null, // e.g. { height: 'toggle', opacity: 'toggle', duration: 200 } // templates tabTemplate: '
  • #{label}
  • ', panelTemplate: '
    ', // CSS class names navClass: 'ui-tabs-nav', selectedClass: 'ui-tabs-selected', deselectableClass: 'ui-tabs-deselectable', disabledClass: 'ui-tabs-disabled', panelClass: 'ui-tabs-panel', hideClass: 'ui-tabs-hide', loadingClass: 'ui-tabs-loading' } }); /* * Tabs Extensions */ /* * Rotate */ jQuery.extend(jQuery.ui.tabs.prototype, { rotation: null, rotate: function(ms, continuing) { continuing = continuing || false; var self = this, t = this.options.selected; function start() { self.rotation = setInterval(function() { t = ++t < self.jQuerytabs.length ? t : 0; self.select(t); }, ms); } function stop(e) { if (!e || e.clientX) { // only in case of a true click clearInterval(self.rotation); } } // start interval if (ms) { start(); if (!continuing) this.jQuerytabs.bind(this.options.event + '.tabs', stop); else this.jQuerytabs.bind(this.options.event + '.tabs', function() { stop(); t = self.options.selected; start(); }); } // stop interval else { stop(); this.jQuerytabs.unbind(this.options.event + '.tabs', stop); } } }); })(jQuery);