Rev 162 | Blame | Compare with Previous | Last modification | View Log | RSS feed
/** jQuery UI Tabs 1.8.1** Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)* Dual licensed under the MIT (MIT-LICENSE.txt)* and GPL (GPL-LICENSE.txt) licenses.** http://docs.jquery.com/UI/Tabs** Depends:* jquery.ui.core.js* jquery.ui.widget.js*/(function($) {var tabId = 0,listId = 0;$.widget("ui.tabs", {options: {add: null,ajaxOptions: null,cache: false,cookie: null, // e.g. { expires: 7, path: '/', domain: 'jquery.com', secure: true }collapsible: false,disable: null,disabled: [],enable: null,event: 'click',fx: null, // e.g. { height: 'toggle', opacity: 'toggle', duration: 200 }idPrefix: 'ui-tabs-',load: null,panelTemplate: '<div></div>',remove: null,select: null,show: null,spinner: '<em>Loading…</em>',tabTemplate: '<li><a href="#{href}"><span>#{label}</span></a></li>'},_create: function() {this._tabify(true);},_setOption: function(key, value) {if (key == 'selected') {if (this.options.collapsible && value == this.options.selected) {return;}this.select(value);}else {this.options[key] = value;this._tabify();}},_tabId: function(a) {return a.title && a.title.replace(/\s/g, '_').replace(/[^A-Za-z0-9\-_:\.]/g, '') ||this.options.idPrefix + (++tabId);},_sanitizeSelector: function(hash) {return hash.replace(/:/g, '\\:'); // we need this because an id may contain a ":"},_cookie: function() {var cookie = this.cookie || (this.cookie = this.options.cookie.name || 'ui-tabs-' + (++listId));return $.cookie.apply(null, [cookie].concat($.makeArray(arguments)));},_ui: function(tab, panel) {return {tab: tab,panel: panel,index: this.anchors.index(tab)};},_cleanup: function() {// restore all former loading tabs labelsthis.lis.filter('.ui-state-processing').removeClass('ui-state-processing').find('span:data(label.tabs)').each(function() {var el = $(this);el.html(el.data('label.tabs')).removeData('label.tabs');});},_tabify: function(init) {this.list = this.element.find('ol,ul').eq(0);this.lis = $('li:has(a[href])', this.list);this.anchors = this.lis.map(function() { return $('a', this)[0]; });this.panels = $([]);var self = this, o = this.options;var fragmentId = /^#.+/; // Safari 2 reports '#' for an empty hashthis.anchors.each(function(i, a) {var href = $(a).attr('href');// For dynamically created HTML that contains a hash as href IE < 8 expands// such href to the full page url with hash and then misinterprets tab as ajax.// Same consideration applies for an added tab with a fragment identifier// since a[href=#fragment-identifier] does unexpectedly not match.// Thus normalize href attribute...var hrefBase = href.split('#')[0], baseEl;if (hrefBase && (hrefBase === location.toString().split('#')[0] ||(baseEl = $('base')[0]) && hrefBase === baseEl.href)) {href = a.hash;a.href = href;}// inline tabif (fragmentId.test(href)) {self.panels = self.panels.add(self._sanitizeSelector(href));}// remote tabelse if (href != '#') { // prevent loading the page itself if href is just "#"$.data(a, 'href.tabs', href); // required for restore on destroy// TODO until #3808 is fixed strip fragment identifier from url// (IE fails to load from such url)$.data(a, 'load.tabs', href.replace(/#.*$/, '')); // mutable datavar id = self._tabId(a);a.href = '#' + id;var $panel = $('#' + id);if (!$panel.length) {$panel = $(o.panelTemplate).attr('id', id).addClass('ui-tabs-panel ui-widget-content ui-corner-bottom').insertAfter(self.panels[i - 1] || self.list);$panel.data('destroy.tabs', true);}self.panels = self.panels.add($panel);}// invalid tab hrefelse {o.disabled.push(i);}});// initialization from scratchif (init) {// attach necessary classes for stylingthis.element.addClass('ui-tabs ui-widget ui-widget-content ui-corner-all');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.panels.addClass('ui-tabs-panel ui-widget-content ui-corner-bottom');// Selected tab// use "selected" option or try to retrieve:// 1. from fragment identifier in url// 2. from cookie// 3. from selected class attribute on <li>if (o.selected === undefined) {if (location.hash) {this.anchors.each(function(i, a) {if (a.hash == location.hash) {o.selected = i;return false; // break}});}if (typeof o.selected != 'number' && o.cookie) {o.selected = parseInt(self._cookie(), 10);}if (typeof o.selected != 'number' && this.lis.filter('.ui-tabs-selected').length) {o.selected = this.lis.index(this.lis.filter('.ui-tabs-selected'));}o.selected = o.selected || (this.lis.length ? 0 : -1);}else if (o.selected === null) { // usage of null is deprecated, TODO remove in next releaseo.selected = -1;}// sanity check - default to first tab...o.selected = ((o.selected >= 0 && this.anchors[o.selected]) || o.selected < 0) ? o.selected : 0;// Take disabling tabs via class attribute from HTML// into account and update option properly.// A selected tab cannot become disabled.o.disabled = $.unique(o.disabled.concat($.map(this.lis.filter('.ui-state-disabled'),function(n, i) { return self.lis.index(n); } ))).sort();if ($.inArray(o.selected, o.disabled) != -1) {o.disabled.splice($.inArray(o.selected, o.disabled), 1);}// highlight selected tabthis.panels.addClass('ui-tabs-hide');this.lis.removeClass('ui-tabs-selected ui-state-active');if (o.selected >= 0 && this.anchors.length) { // check for length avoids error when initializing empty listthis.panels.eq(o.selected).removeClass('ui-tabs-hide');this.lis.eq(o.selected).addClass('ui-tabs-selected ui-state-active');// seems to be expected behavior that the show callback is firedself.element.queue("tabs", function() {self._trigger('show', null, self._ui(self.anchors[o.selected], self.panels[o.selected]));});this.load(o.selected);}// clean up to avoid memory leaks in certain versions of IE 6$(window).bind('unload', function() {self.lis.add(self.anchors).unbind('.tabs');self.lis = self.anchors = self.panels = null;});}// update selected after add/removeelse {o.selected = this.lis.index(this.lis.filter('.ui-tabs-selected'));}// update collapsiblethis.element[o.collapsible ? 'addClass' : 'removeClass']('ui-tabs-collapsible');// set or update cookie after init and add/remove respectivelyif (o.cookie) {this._cookie(o.selected, o.cookie);}// disable tabsfor (var i = 0, li; (li = this.lis[i]); i++) {$(li)[$.inArray(i, o.disabled) != -1 &&!$(li).hasClass('ui-tabs-selected') ? 'addClass' : 'removeClass']('ui-state-disabled');}// reset cache if switching from cached to not cachedif (o.cache === false) {this.anchors.removeData('cache.tabs');}// remove all handlers before, tabify may run on existing tabs after add or option changethis.lis.add(this.anchors).unbind('.tabs');if (o.event != 'mouseover') {var addState = function(state, el) {if (el.is(':not(.ui-state-disabled)')) {el.addClass('ui-state-' + state);}};var removeState = function(state, el) {el.removeClass('ui-state-' + state);};this.lis.bind('mouseover.tabs', function() {addState('hover', $(this));});this.lis.bind('mouseout.tabs', function() {removeState('hover', $(this));});this.anchors.bind('focus.tabs', function() {addState('focus', $(this).closest('li'));});this.anchors.bind('blur.tabs', function() {removeState('focus', $(this).closest('li'));});}// set up animationsvar hideFx, showFx;if (o.fx) {if ($.isArray(o.fx)) {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($el, fx) {$el.css({ display: '' });if (!$.support.opacity && fx.opacity) {$el[0].style.removeAttribute('filter');}}// Show a tab...var showTab = showFx ?function(clicked, $show) {$(clicked).closest('li').addClass('ui-tabs-selected ui-state-active');$show.hide().removeClass('ui-tabs-hide') // avoid flicker that way.animate(showFx, showFx.duration || 'normal', function() {resetStyle($show, showFx);self._trigger('show', null, self._ui(clicked, $show[0]));});} :function(clicked, $show) {$(clicked).closest('li').addClass('ui-tabs-selected ui-state-active');$show.removeClass('ui-tabs-hide');self._trigger('show', null, self._ui(clicked, $show[0]));};// Hide a tab, $show is optional...var hideTab = hideFx ?function(clicked, $hide) {$hide.animate(hideFx, hideFx.duration || 'normal', function() {self.lis.removeClass('ui-tabs-selected ui-state-active');$hide.addClass('ui-tabs-hide');resetStyle($hide, hideFx);self.element.dequeue("tabs");});} :function(clicked, $hide, $show) {self.lis.removeClass('ui-tabs-selected ui-state-active');$hide.addClass('ui-tabs-hide');self.element.dequeue("tabs");};// attach tab event handler, unbind to avoid duplicates from former tabifying...this.anchors.bind(o.event + '.tabs', function() {var el = this, $li = $(this).closest('li'), $hide = self.panels.filter(':not(.ui-tabs-hide)'),$show = $(self._sanitizeSelector(this.hash));// If tab is already selected and not collapsible 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 (($li.hasClass('ui-tabs-selected') && !o.collapsible) ||$li.hasClass('ui-state-disabled') ||$li.hasClass('ui-state-processing') ||self._trigger('select', null, self._ui(this, $show[0])) === false) {this.blur();return false;}o.selected = self.anchors.index(this);self.abort();// if tab may be closedif (o.collapsible) {if ($li.hasClass('ui-tabs-selected')) {o.selected = -1;if (o.cookie) {self._cookie(o.selected, o.cookie);}self.element.queue("tabs", function() {hideTab(el, $hide);}).dequeue("tabs");this.blur();return false;}else if (!$hide.length) {if (o.cookie) {self._cookie(o.selected, o.cookie);}self.element.queue("tabs", function() {showTab(el, $show);});self.load(self.anchors.index(this)); // TODO make passing in node possible, see also http://dev.jqueryui.com/ticket/3171this.blur();return false;}}if (o.cookie) {self._cookie(o.selected, o.cookie);}// show new tabif ($show.length) {if ($hide.length) {self.element.queue("tabs", function() {hideTab(el, $hide);});}self.element.queue("tabs", function() {showTab(el, $show);});self.load(self.anchors.index(this));}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 ($.browser.msie) {this.blur();}});// disable click in any casethis.anchors.bind('click.tabs', function(){return false;});},destroy: function() {var o = this.options;this.abort();this.element.unbind('.tabs').removeClass('ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible').removeData('tabs');this.list.removeClass('ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all');this.anchors.each(function() {var href = $.data(this, 'href.tabs');if (href) {this.href = href;}var $this = $(this).unbind('.tabs');$.each(['href', 'load', 'cache'], function(i, prefix) {$this.removeData(prefix + '.tabs');});});this.lis.unbind('.tabs').add(this.panels).each(function() {if ($.data(this, 'destroy.tabs')) {$(this).remove();}else {$(this).removeClass(['ui-state-default','ui-corner-top','ui-tabs-selected','ui-state-active','ui-state-hover','ui-state-focus','ui-state-disabled','ui-tabs-panel','ui-widget-content','ui-corner-bottom','ui-tabs-hide'].join(' '));}});if (o.cookie) {this._cookie(null, o.cookie);}return this;},add: function(url, label, index) {if (index === undefined) {index = this.anchors.length; // append by default}var self = this, o = this.options,$li = $(o.tabTemplate.replace(/#\{href\}/g, url).replace(/#\{label\}/g, label)),id = !url.indexOf('#') ? url.replace('#', '') : this._tabId($('a', $li)[0]);$li.addClass('ui-state-default ui-corner-top').data('destroy.tabs', true);// try to find an existing element before creating a new onevar $panel = $('#' + id);if (!$panel.length) {$panel = $(o.panelTemplate).attr('id', id).data('destroy.tabs', true);}$panel.addClass('ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide');if (index >= this.lis.length) {$li.appendTo(this.list);$panel.appendTo(this.list[0].parentNode);}else {$li.insertBefore(this.lis[index]);$panel.insertBefore(this.panels[index]);}o.disabled = $.map(o.disabled,function(n, i) { return n >= index ? ++n : n; });this._tabify();if (this.anchors.length == 1) { // after tabifyo.selected = 0;$li.addClass('ui-tabs-selected ui-state-active');$panel.removeClass('ui-tabs-hide');this.element.queue("tabs", function() {self._trigger('show', null, self._ui(self.anchors[0], self.panels[0]));});this.load(0);}// callbackthis._trigger('add', null, this._ui(this.anchors[index], this.panels[index]));return this;},remove: function(index) {var o = this.options, $li = this.lis.eq(index).remove(),$panel = this.panels.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 ($li.hasClass('ui-tabs-selected') && this.anchors.length > 1) {this.select(index + (index + 1 < this.anchors.length ? 1 : -1));}o.disabled = $.map($.grep(o.disabled, function(n, i) { return n != index; }),function(n, i) { return n >= index ? --n : n; });this._tabify();// callbackthis._trigger('remove', null, this._ui($li.find('a')[0], $panel[0]));return this;},enable: function(index) {var o = this.options;if ($.inArray(index, o.disabled) == -1) {return;}this.lis.eq(index).removeClass('ui-state-disabled');o.disabled = $.grep(o.disabled, function(n, i) { return n != index; });// callbackthis._trigger('enable', null, this._ui(this.anchors[index], this.panels[index]));return this;},disable: function(index) {var self = this, o = this.options;if (index != o.selected) { // cannot disable already selected tabthis.lis.eq(index).addClass('ui-state-disabled');o.disabled.push(index);o.disabled.sort();// callbackthis._trigger('disable', null, this._ui(this.anchors[index], this.panels[index]));}return this;},select: function(index) {if (typeof index == 'string') {index = this.anchors.index(this.anchors.filter('[href$=' + index + ']'));}else if (index === null) { // usage of null is deprecated, TODO remove in next releaseindex = -1;}if (index == -1 && this.options.collapsible) {index = this.options.selected;}this.anchors.eq(index).trigger(this.options.event + '.tabs');return this;},load: function(index) {var self = this, o = this.options, a = this.anchors.eq(index)[0], url = $.data(a, 'load.tabs');this.abort();// not remote or from cacheif (!url || this.element.queue("tabs").length !== 0 && $.data(a, 'cache.tabs')) {this.element.dequeue("tabs");return;}// load remote from here onthis.lis.eq(index).addClass('ui-state-processing');if (o.spinner) {var span = $('span', a);span.data('label.tabs', span.html()).html(o.spinner);}this.xhr = $.ajax($.extend({}, o.ajaxOptions, {url: url,success: function(r, s) {$(self._sanitizeSelector(a.hash)).html(r);// take care of tab labelsself._cleanup();if (o.cache) {$.data(a, 'cache.tabs', true); // if loaded once do not load them again}// callbacksself._trigger('load', null, self._ui(self.anchors[index], self.panels[index]));try {o.ajaxOptions.success(r, s);}catch (e) {}},error: function(xhr, s, e) {// take care of tab labelsself._cleanup();// callbacksself._trigger('load', null, self._ui(self.anchors[index], self.panels[index]));try {// Passing index avoid a race condition when this method is// called after the user has selected another tab.// Pass the anchor that initiated this request allows// loadError to manipulate the tab content panel via $(a.hash)o.ajaxOptions.error(xhr, s, index, a);}catch (e) {}}}));// last, so that load event is fired before show...self.element.dequeue("tabs");return this;},abort: function() {// stop possibly running animationsthis.element.queue([]);this.panels.stop(false, true);// "tabs" queue must not contain more than two elements,// which are the callbacks for the latest clicked tab...this.element.queue("tabs", this.element.queue("tabs").splice(-2, 2));// terminate pending requests from other tabsif (this.xhr) {this.xhr.abort();delete this.xhr;}// take care of tab labelsthis._cleanup();return this;},url: function(index, url) {this.anchors.eq(index).removeData('cache.tabs').data('load.tabs', url);return this;},length: function() {return this.anchors.length;}});$.extend($.ui.tabs, {version: '1.8.1'});/** Tabs Extensions*//** Rotate*/$.extend($.ui.tabs.prototype, {rotation: null,rotate: function(ms, continuing) {var self = this, o = this.options;var rotate = self._rotate || (self._rotate = function(e) {clearTimeout(self.rotation);self.rotation = setTimeout(function() {var t = o.selected;self.select( ++t < self.anchors.length ? t : 0 );}, ms);if (e) {e.stopPropagation();}});var stop = self._unrotate || (self._unrotate = !continuing ?function(e) {if (e.clientX) { // in case of a true clickself.rotate(null);}} :function(e) {t = o.selected;rotate();});// start rotationif (ms) {this.element.bind('tabsshow', rotate);this.anchors.bind(o.event + '.tabs', stop);rotate();}// stop rotationelse {clearTimeout(self.rotation);this.element.unbind('tabsshow', rotate);this.anchors.unbind(o.event + '.tabs', stop);delete this._rotate;delete this._unrotate;}return this;}});})(jQuery);