/*! * jQuery UI Tabs - v1.10.4 * http://jqueryui.com * * Copyright 2014 jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license * * http://api.jqueryui.com/tabs/ */ define([ 'jquery', 'jquery-ui-modules/core', 'jquery-ui-modules/widget' ], function ($, undefined) { var tabId = 0, rhash = /#.*$/; function getNextTabId() { return ++tabId; } function isLocal(anchor) { // support: IE7 // IE7 doesn't normalize the href property when set via script (#9317) anchor = anchor.cloneNode(false); return anchor.hash.length > 1 && decodeURIComponent(anchor.href.replace(rhash, "")) === decodeURIComponent(location.href.replace(rhash, "")); } $.widget("ui.tabs", { version: "1.10.4", delay: 300, options: { active: null, collapsible: false, event: "click", heightStyle: "content", hide: null, show: null, // callbacks activate: null, beforeActivate: null, beforeLoad: null, load: null }, _create: function () { var that = this, options = this.options; this.running = false; this.element .addClass("ui-tabs ui-widget ui-widget-content ui-corner-all") .toggleClass("ui-tabs-collapsible", options.collapsible) // Prevent users from focusing disabled tabs via click .delegate(".ui-tabs-nav > li", "mousedown" + this.eventNamespace, function (event) { if ($(this).is(".ui-state-disabled")) { event.preventDefault(); } }) // support: IE <9 // Preventing the default action in mousedown doesn't prevent IE // from focusing the element, so if the anchor gets focused, blur. // We don't have to worry about focusing the previously focused // element since clicking on a non-focusable element should focus // the body anyway. .delegate(".ui-tabs-anchor", "focus" + this.eventNamespace, function () { if ($(this).closest("li").is(".ui-state-disabled")) { this.blur(); } }); this._processTabs(); options.active = this._initialActive(); // Take disabling tabs via class attribute from HTML // into account and update option properly. if ($.isArray(options.disabled)) { options.disabled = $.unique(options.disabled.concat( $.map(this.tabs.filter(".ui-state-disabled"), function (li) { return that.tabs.index(li); }) )).sort(); } // check for length avoids error when initializing empty list if (this.options.active !== false && this.anchors.length) { this.active = this._findActive(options.active); } else { this.active = $(); } this._refresh(); if (this.active.length) { this.load(options.active); } }, _initialActive: function () { var active = this.options.active, collapsible = this.options.collapsible, locationHash = location.hash.substring(1); if (active === null) { // check the fragment identifier in the URL if (locationHash) { this.tabs.each(function (i, tab) { if ($(tab).attr("aria-controls") === locationHash) { active = i; return false; } }); } // check for a tab marked active via a class if (active === null) { active = this.tabs.index(this.tabs.filter(".ui-tabs-active")); } // no active tab, set to false if (active === null || active === -1) { active = this.tabs.length ? 0 : false; } } // handle numbers: negative, out of range if (active !== false) { active = this.tabs.index(this.tabs.eq(active)); if (active === -1) { active = collapsible ? false : 0; } } // don't allow collapsible: false and active: false if (!collapsible && active === false && this.anchors.length) { active = 0; } return active; }, _getCreateEventData: function () { return { tab: this.active, panel: !this.active.length ? $() : this._getPanelForTab(this.active) }; }, _tabKeydown: function (event) { var focusedTab = $(this.document[0].activeElement).closest("li"), selectedIndex = this.tabs.index(focusedTab), goingForward = true; if (this._handlePageNav(event)) { return; } switch (event.keyCode) { case $.ui.keyCode.RIGHT: case $.ui.keyCode.DOWN: selectedIndex++; break; case $.ui.keyCode.UP: case $.ui.keyCode.LEFT: goingForward = false; selectedIndex--; break; case $.ui.keyCode.END: selectedIndex = this.anchors.length - 1; break; case $.ui.keyCode.HOME: selectedIndex = 0; break; case $.ui.keyCode.SPACE: // Activate only, no collapsing event.preventDefault(); clearTimeout(this.activating); this._activate(selectedIndex); return; case $.ui.keyCode.ENTER: // Toggle (cancel delayed activation, allow collapsing) event.preventDefault(); clearTimeout(this.activating); // Determine if we should collapse or activate this._activate(selectedIndex === this.options.active ? false : selectedIndex); return; default: return; } // Focus the appropriate tab, based on which key was pressed event.preventDefault(); clearTimeout(this.activating); selectedIndex = this._focusNextTab(selectedIndex, goingForward); // Navigating with control key will prevent automatic activation if (!event.ctrlKey) { // Update aria-selected immediately so that AT think the tab is already selected. // Otherwise AT may confuse the user by stating that they need to activate the tab, // but the tab will already be activated by the time the announcement finishes. focusedTab.attr("aria-selected", "false"); this.tabs.eq(selectedIndex).attr("aria-selected", "true"); this.activating = this._delay(function () { this.option("active", selectedIndex); }, this.delay); } }, _panelKeydown: function (event) { if (this._handlePageNav(event)) { return; } // Ctrl+up moves focus to the current tab if (event.ctrlKey && event.keyCode === $.ui.keyCode.UP) { event.preventDefault(); this.active.focus(); } }, // Alt+page up/down moves focus to the previous/next tab (and activates) _handlePageNav: function (event) { if (event.altKey && event.keyCode === $.ui.keyCode.PAGE_UP) { this._activate(this._focusNextTab(this.options.active - 1, false)); return true; } if (event.altKey && event.keyCode === $.ui.keyCode.PAGE_DOWN) { this._activate(this._focusNextTab(this.options.active + 1, true)); return true; } }, _findNextTab: function (index, goingForward) { var lastTabIndex = this.tabs.length - 1; function constrain() { if (index > lastTabIndex) { index = 0; } if (index < 0) { index = lastTabIndex; } return index; } while ($.inArray(constrain(), this.options.disabled) !== -1) { index = goingForward ? index + 1 : index - 1; } return index; }, _focusNextTab: function (index, goingForward) { index = this._findNextTab(index, goingForward); this.tabs.eq(index).focus(); return index; }, _setOption: function (key, value) { if (key === "active") { // _activate() will handle invalid values and update this.options this._activate(value); return; } if (key === "disabled") { // don't use the widget factory's disabled handling this._setupDisabled(value); return; } this._super(key, value); if (key === "collapsible") { this.element.toggleClass("ui-tabs-collapsible", value); // Setting collapsible: false while collapsed; open first panel if (!value && this.options.active === false) { this._activate(0); } } if (key === "event") { this._setupEvents(value); } if (key === "heightStyle") { this._setupHeightStyle(value); } }, _tabId: function (tab) { return tab.attr("aria-controls") || "ui-tabs-" + getNextTabId(); }, _sanitizeSelector: function (hash) { return hash ? hash.replace(/[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g, "\\$&") : ""; }, refresh: function () { var options = this.options, lis = this.tablist.children(":has(a[href])"); // get disabled tabs from class attribute from HTML // this will get converted to a boolean if needed in _refresh() options.disabled = $.map(lis.filter(".ui-state-disabled"), function (tab) { return lis.index(tab); }); this._processTabs(); // was collapsed or no tabs if (options.active === false || !this.anchors.length) { options.active = false; this.active = $(); // was active, but active tab is gone } else if (this.active.length && !$.contains(this.tablist[0], this.active[0])) { // all remaining tabs are disabled if (this.tabs.length === options.disabled.length) { options.active = false; this.active = $(); // activate previous tab } else { this._activate(this._findNextTab(Math.max(0, options.active - 1), false)); } // was active, active tab still exists } else { // make sure active index is correct options.active = this.tabs.index(this.active); } this._refresh(); }, _refresh: function () { this._setupDisabled(this.options.disabled); this._setupEvents(this.options.event); this._setupHeightStyle(this.options.heightStyle); this.tabs.not(this.active).attr({ "aria-selected": "false", tabIndex: -1 }); this.panels.not(this._getPanelForTab(this.active)) .hide() .attr({ "aria-expanded": "false", "aria-hidden": "true" }); // Make sure one tab is in the tab order if (!this.active.length) { this.tabs.eq(0).attr("tabIndex", 0); } else { this.active .addClass("ui-tabs-active ui-state-active") .attr({ "aria-selected": "true", tabIndex: 0 }); this._getPanelForTab(this.active) .show() .attr({ "aria-expanded": "true", "aria-hidden": "false" }); } }, _processTabs: function () { var that = this; this.tablist = this._getList() .addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all") .attr("role", "tablist"); this.tabs = this.tablist.find("> li:has(a[href])") .addClass("ui-state-default ui-corner-top") .attr({ role: "tab", tabIndex: -1 }); this.anchors = this.tabs.map(function () { return $("a", this)[0]; }) .addClass("ui-tabs-anchor") .attr({ role: "presentation", tabIndex: -1 }); this.panels = $(); this.anchors.each(function (i, anchor) { var selector, panel, panelId, anchorId = $(anchor).uniqueId().attr("id"), tab = $(anchor).closest("li"), originalAriaControls = tab.attr("aria-controls"); // inline tab if (isLocal(anchor)) { selector = anchor.hash; panel = that.element.find(that._sanitizeSelector(selector)); // remote tab } else { panelId = that._tabId(tab); selector = "#" + panelId; panel = that.element.find(selector); if (!panel.length) { panel = that._createPanel(panelId); panel.insertAfter(that.panels[i - 1] || that.tablist); } panel.attr("aria-live", "polite"); } if (panel.length) { that.panels = that.panels.add(panel); } if (originalAriaControls) { tab.data("ui-tabs-aria-controls", originalAriaControls); } tab.attr({ "aria-controls": selector.substring(1), "aria-labelledby": anchorId }); panel.attr("aria-labelledby", anchorId); }); this.panels .addClass("ui-tabs-panel ui-widget-content ui-corner-bottom") .attr("role", "tabpanel"); }, // allow overriding how to find the list for rare usage scenarios (#7715) _getList: function () { return this.tablist || this.element.find("ol,ul").eq(0); }, _createPanel: function (id) { return $("