From 17d3906c86aad75a53c92a5fcc7196f6775ef0b7 Mon Sep 17 00:00:00 2001 From: Peter Fogg Date: Thu, 4 Jun 2015 15:25:43 -0400 Subject: [PATCH] TNL-1891 Browse Teams: Category Subnavigation Introduces a tabbed view component which displays its subviews and handles Backbone navigation between each tab. This is used to implement the "My Teams" and "Browse" areas of the Teams tab. Right now the content of both tabs is stubbed out with a placeholder view which should be replaced by TNL-1893 and TNL-1892. --- common/test/acceptance/pages/lms/teams.py | 2 +- .../teams/js/spec/teams_factory_spec.js | 8 +- .../teams/static/teams/js/views/teams_tab.js | 31 +++++++- .../teams/templates/teams-tab.underscore | 1 - .../js/components/tabbed/views/tabbed_view.js | 59 ++++++++++++++ .../components/tabbed/tabbed_view_spec.js | 78 +++++++++++++++++++ lms/static/js/spec/main.js | 1 + lms/static/js_test.yml | 2 +- .../components/tabbed/tab.underscore | 1 + .../components/tabbed/tabbed_view.underscore | 5 ++ 10 files changed, 179 insertions(+), 9 deletions(-) delete mode 100644 lms/djangoapps/teams/static/teams/templates/teams-tab.underscore create mode 100644 lms/static/js/components/tabbed/views/tabbed_view.js create mode 100644 lms/static/js/spec/components/tabbed/tabbed_view_spec.js create mode 100644 lms/templates/components/tabbed/tab.underscore create mode 100644 lms/templates/components/tabbed/tabbed_view.underscore diff --git a/common/test/acceptance/pages/lms/teams.py b/common/test/acceptance/pages/lms/teams.py index 486dce4e38..f031f7ee1b 100644 --- a/common/test/acceptance/pages/lms/teams.py +++ b/common/test/acceptance/pages/lms/teams.py @@ -18,4 +18,4 @@ class TeamsPage(CoursePage): def get_body_text(self): """ Returns the current dummy text. This will be changed once there is more content on the page. """ - return self.q(css='.teams-text').text[0] + return self.q(css='.page-content-main').text[0] diff --git a/lms/djangoapps/teams/static/teams/js/spec/teams_factory_spec.js b/lms/djangoapps/teams/static/teams/js/spec/teams_factory_spec.js index 6a4c3b9115..e7b5874aff 100644 --- a/lms/djangoapps/teams/static/teams/js/spec/teams_factory_spec.js +++ b/lms/djangoapps/teams/static/teams/js/spec/teams_factory_spec.js @@ -1,5 +1,5 @@ -define(["jquery", "teams/js/teams_tab_factory"], - function($, TeamsTabFactory) { +define(["jquery", "backbone", "teams/js/teams_tab_factory"], + function($, Backbone, TeamsTabFactory) { 'use strict'; describe("teams django app", function() { @@ -10,6 +10,10 @@ define(["jquery", "teams/js/teams_tab_factory"], teamsTab = new TeamsTabFactory(); }); + afterEach(function() { + Backbone.history.stop(); + }); + it("can load templates", function() { expect($("body").text()).toContain("This is the new Teams tab"); }); diff --git a/lms/djangoapps/teams/static/teams/js/views/teams_tab.js b/lms/djangoapps/teams/static/teams/js/views/teams_tab.js index 27c884d1e8..a8f9ea982c 100644 --- a/lms/djangoapps/teams/static/teams/js/views/teams_tab.js +++ b/lms/djangoapps/teams/static/teams/js/views/teams_tab.js @@ -6,8 +6,8 @@ 'gettext', 'js/components/header/views/header', 'js/components/header/models/header', - 'text!teams/templates/teams-tab.underscore'], - function (Backbone, _, gettext, HeaderView, HeaderModel, teamsTabTemplate) { + 'js/components/tabbed/views/tabbed_view'], + function (Backbone, _, gettext, HeaderView, HeaderModel, TabbedView) { var TeamTabView = Backbone.View.extend({ initialize: function() { this.headerModel = new HeaderModel({ @@ -17,12 +17,35 @@ this.headerView = new HeaderView({ model: this.headerModel }); + // TODO replace this with actual views! + var TempTabView = Backbone.View.extend({ + initialize: function (options) { + this.text = options.text; + }, + + render: function () { + this.$el.text(this.text) + } + }); + this.tabbedView = new TabbedView({ + tabs: [{ + title: gettext('My Teams'), + url: 'teams', + view: new TempTabView({text: 'This is the new Teams tab.'}) + }, { + title: gettext('Browse'), + url: 'browse', + view: new TempTabView({text: 'Browse team topics here.'}) + }] + }); + Backbone.history.start(); }, render: function() { - this.$el.html(_.template(teamsTabTemplate, {})); - this.$el.prepend(this.headerView.$el); + this.$el.append(this.headerView.$el); this.headerView.render(); + this.$el.append(this.tabbedView.$el); + this.tabbedView.render(); } }); diff --git a/lms/djangoapps/teams/static/teams/templates/teams-tab.underscore b/lms/djangoapps/teams/static/teams/templates/teams-tab.underscore deleted file mode 100644 index e9411c5ab0..0000000000 --- a/lms/djangoapps/teams/static/teams/templates/teams-tab.underscore +++ /dev/null @@ -1 +0,0 @@ -

This is the new Teams tab.

diff --git a/lms/static/js/components/tabbed/views/tabbed_view.js b/lms/static/js/components/tabbed/views/tabbed_view.js new file mode 100644 index 0000000000..a3c4dfd8c5 --- /dev/null +++ b/lms/static/js/components/tabbed/views/tabbed_view.js @@ -0,0 +1,59 @@ +;(function (define) { + 'use strict'; + define(['backbone', + 'underscore', + 'jquery', + 'text!templates/components/tabbed/tabbed_view.underscore', + 'text!templates/components/tabbed/tab.underscore'], + function (Backbone, _, $, tabbedViewTemplate, tabTemplate) { + var TabbedView = Backbone.View.extend({ + events: { + 'click .nav-item': '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. + */ + initialize: function (options) { + this.router = new Backbone.Router(); + this.$el.html(this.template({})); + var self = this; + this.tabs = options.tabs; + _.each(this.tabs, function(tabInfo, index) { + var tabEl = $(_.template(tabTemplate, { + index: index, + title: tabInfo.title + })); + self.$('.page-content-nav').append(tabEl); + + self.router.route(tabInfo.url, function () { + self.setActiveTab(index); + }); + }); + this.setActiveTab(0); + }, + + setActiveTab: function (index) { + this.$('a.is-active').removeClass('is-active').attr('aria-selected', 'false'); + this.$('a[data-index='+index+']').addClass('is-active').attr('aria-selected', 'true'); + var view = this.tabs[index].view; + view.render(); + this.$('.page-content-main').html(view.$el.html()); + this.$('.sr-is-focusable').focus(); + }, + + switchTab: function (event) { + event.preventDefault(); + this.setActiveTab($(event.currentTarget).data('index')); + } + }); + return TabbedView; + }); +}).call(this, define || RequireJS.define); diff --git a/lms/static/js/spec/components/tabbed/tabbed_view_spec.js b/lms/static/js/spec/components/tabbed/tabbed_view_spec.js new file mode 100644 index 0000000000..69a18e045e --- /dev/null +++ b/lms/static/js/spec/components/tabbed/tabbed_view_spec.js @@ -0,0 +1,78 @@ +(function (define) { + 'use strict'; + + define(['jquery', + 'underscore', + 'backbone', + 'js/components/tabbed/views/tabbed_view' + ], + function($, _, Backbone, TabbedView) { + var view, + TestSubview = Backbone.View.extend({ + initialize: function (options) { + this.text = options.text; + }, + + render: function () { + this.$el.text(this.text); + } + }); + + describe('TabbedView component', function () { + beforeEach(function () { + spyOn(Backbone.history, 'navigate').andCallThrough(); + Backbone.history.start(); + view = new TabbedView({ + tabs: [{ + url: 'test 1', + title: 'Test 1', + view: new TestSubview({text: 'this is test text'}) + }, { + url: 'test 2', + title: 'Test 2', + view: new TestSubview({text: 'other text'}) + }] + }); + }); + + afterEach(function () { + Backbone.history.stop(); + }); + + it('can render itself', function () { + expect(view.$el.html()).toContain('