| /* |
| * jQuery UI Tabs 1.8.2 |
| * |
| * 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; |
| |
| function getNextTabId() { |
| return ++tabId; |
| } |
| |
| function getNextListId() { |
| return ++listId; |
| } |
| |
| $.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 + getNextTabId(); |
| }, |
| |
| _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-' + getNextListId()); |
| 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 labels |
| this.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 hash |
| this.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 tab |
| if (fragmentId.test(href)) { |
| self.panels = self.panels.add(self._sanitizeSelector(href)); |
| } |
| |
| // remote tab |
| else 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 data |
| |
| var 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 href |
| else { |
| o.disabled.push(i); |
| } |
| }); |
| |
| // initialization from scratch |
| if (init) { |
| |
| // attach necessary classes for styling |
| this.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 release |
| o.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 tab |
| this.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 list |
| this.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 fired |
| self.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/remove |
| else { |
| o.selected = this.lis.index(this.lis.filter('.ui-tabs-selected')); |
| } |
| |
| // update collapsible |
| this.element[o.collapsible ? 'addClass' : 'removeClass']('ui-tabs-collapsible'); |
| |
| // 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.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 cached |
| if (o.cache === false) { |
| this.anchors.removeData('cache.tabs'); |
| } |
| |
| // remove all handlers before, tabify may run on existing tabs after add or option change |
| this.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 animations |
| var 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 closed |
| if (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/3171 |
| |
| this.blur(); |
| return false; |
| } |
| } |
| |
| if (o.cookie) { |
| self._cookie(o.selected, o.cookie); |
| } |
| |
| // show new tab |
| if ($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 case |
| this.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 one |
| var $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 tabify |
| o.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); |
| } |
| |
| // callback |
| this._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(); |
| |
| // callback |
| this._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; }); |
| |
| // callback |
| this._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 tab |
| this.lis.eq(index).addClass('ui-state-disabled'); |
| |
| o.disabled.push(index); |
| o.disabled.sort(); |
| |
| // callback |
| this._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 release |
| index = -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 cache |
| if (!url || this.element.queue("tabs").length !== 0 && $.data(a, 'cache.tabs')) { |
| this.element.dequeue("tabs"); |
| return; |
| } |
| |
| // load remote from here on |
| this.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 labels |
| self._cleanup(); |
| |
| if (o.cache) { |
| $.data(a, 'cache.tabs', true); // if loaded once do not load them again |
| } |
| |
| // callbacks |
| self._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 labels |
| self._cleanup(); |
| |
| // callbacks |
| self._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 animations |
| this.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 tabs |
| if (this.xhr) { |
| this.xhr.abort(); |
| delete this.xhr; |
| } |
| |
| // take care of tab labels |
| this._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.2' |
| }); |
| |
| /* |
| * 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 click |
| self.rotate(null); |
| } |
| } : |
| function(e) { |
| t = o.selected; |
| rotate(); |
| }); |
| |
| // start rotation |
| if (ms) { |
| this.element.bind('tabsshow', rotate); |
| this.anchors.bind(o.event + '.tabs', stop); |
| rotate(); |
| } |
| // stop rotation |
| else { |
| clearTimeout(self.rotation); |
| this.element.unbind('tabsshow', rotate); |
| this.anchors.unbind(o.event + '.tabs', stop); |
| delete this._rotate; |
| delete this._unrotate; |
| } |
| |
| return this; |
| } |
| }); |
| |
| })(jQuery); |