;(function (define) { 'use strict'; define(['backbone', 'underscore', 'jquery', 'text!templates/components/tabbed/tabbed_view.underscore', 'text!templates/components/tabbed/tab.underscore', 'text!templates/components/tabbed/tabpanel.underscore', ], function ( Backbone, _, $, tabbedViewTemplate, tabTemplate, tabPanelTemplate ) { var getTabPanelId = function (id) { return 'tabpanel-' + id; }; var TabPanelView = Backbone.View.extend({ template: _.template(tabPanelTemplate), initialize: function (options) { this.url = options.url; this.view = options.view; }, render: function () { var tabPanelHtml = this.template({tabId: getTabPanelId(this.url)}); this.setElement($(tabPanelHtml)); this.$el.append(this.view.render().el); return this; } }); var TabbedView = Backbone.View.extend({ events: { 'click .nav-item.tab': 'switchTab' }, template: _.template(tabbedViewTemplate), /** * View for a tabbed interface. Expects a list of tabs * in its options object, each of which should contain the * following properties: * view (Backbone.View): the view to render for this tab. * title (string): The title to display for this tab. * url (string): The URL fragment which will * navigate to this tab when a router is * provided. * If a router is passed in (via options.router), * use that router to keep track of history between * tabs. Backbone.history.start() must be called * by the router's instatiator after this view is * initialized. */ initialize: function (options) { this.router = options.router || null; this.tabs = options.tabs; // Convert each view into a TabPanelView _.each(this.tabs, function (tabInfo) { tabInfo.view = new TabPanelView({url: tabInfo.url, view: tabInfo.view}); }, this); this.urlMap = _.reduce(this.tabs, function (map, value) { map[value.url] = value; return map; }, {}); }, render: function () { var self = this; this.$el.html(this.template({})); _.each(this.tabs, function(tabInfo, index) { var tabEl = $(_.template(tabTemplate, { index: index, title: tabInfo.title, url: tabInfo.url, tabPanelId: getTabPanelId(tabInfo.url) })), tabContainerEl = this.$('.tabs'); self.$('.page-content-nav').append(tabEl); // Render and append the current tab panel tabContainerEl.append(tabInfo.view.render().$el); }, this); // Re-display the default (first) tab if the // current route does not belong to one of the // tabs. Otherwise continue displaying the tab // corresponding to the current URL. if (!(Backbone.history.getHash() in this.urlMap)) { this.setActiveTab(0); } return this; }, setActiveTab: function (index) { var tabMeta = this.getTabMeta(index), tab = tabMeta.tab, tabEl = tabMeta.element, view = tab.view; // Hide old tab/tabpanel this.$('button.is-active').removeClass('is-active').attr('aria-expanded', 'false'); this.$('.tabpanel[aria-expanded="true"]').attr('aria-expanded', 'false').addClass('is-hidden'); // Show new tab/tabpanel tabEl.addClass('is-active').attr('aria-expanded', 'true'); view.$el.attr('aria-expanded', 'true').removeClass('is-hidden'); // This bizarre workaround makes focus work in Chrome. _.defer(function () { view.$('.sr-is-focusable.' + getTabPanelId(tab.url)).focus(); }); if (this.router) { this.router.navigate(tab.url, {replace: true}); } }, switchTab: function (event) { event.preventDefault(); this.setActiveTab($(event.currentTarget).data('index')); }, /** * Get the tab by name or index. Returns an object * encapsulating the tab object and its element. */ getTabMeta: function (tabNameOrIndex) { var tab, element; if (typeof tabNameOrIndex === 'string') { tab = this.urlMap[tabNameOrIndex]; element = this.$('button[data-url='+tabNameOrIndex+']'); } else { tab = this.tabs[tabNameOrIndex]; element = this.$('button[data-index='+tabNameOrIndex+']'); } return {'tab': tab, 'element': element}; } }); return TabbedView; }); }).call(this, define || RequireJS.define);