Commit 17d3906c by Peter Fogg

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.
parent bdd51e67
...@@ -18,4 +18,4 @@ class TeamsPage(CoursePage): ...@@ -18,4 +18,4 @@ class TeamsPage(CoursePage):
def get_body_text(self): def get_body_text(self):
""" Returns the current dummy text. This will be changed once there is more content on the page. """ """ 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]
define(["jquery", "teams/js/teams_tab_factory"], define(["jquery", "backbone", "teams/js/teams_tab_factory"],
function($, TeamsTabFactory) { function($, Backbone, TeamsTabFactory) {
'use strict'; 'use strict';
describe("teams django app", function() { describe("teams django app", function() {
...@@ -10,6 +10,10 @@ define(["jquery", "teams/js/teams_tab_factory"], ...@@ -10,6 +10,10 @@ define(["jquery", "teams/js/teams_tab_factory"],
teamsTab = new TeamsTabFactory(); teamsTab = new TeamsTabFactory();
}); });
afterEach(function() {
Backbone.history.stop();
});
it("can load templates", function() { it("can load templates", function() {
expect($("body").text()).toContain("This is the new Teams tab"); expect($("body").text()).toContain("This is the new Teams tab");
}); });
......
...@@ -6,8 +6,8 @@ ...@@ -6,8 +6,8 @@
'gettext', 'gettext',
'js/components/header/views/header', 'js/components/header/views/header',
'js/components/header/models/header', 'js/components/header/models/header',
'text!teams/templates/teams-tab.underscore'], 'js/components/tabbed/views/tabbed_view'],
function (Backbone, _, gettext, HeaderView, HeaderModel, teamsTabTemplate) { function (Backbone, _, gettext, HeaderView, HeaderModel, TabbedView) {
var TeamTabView = Backbone.View.extend({ var TeamTabView = Backbone.View.extend({
initialize: function() { initialize: function() {
this.headerModel = new HeaderModel({ this.headerModel = new HeaderModel({
...@@ -17,12 +17,35 @@ ...@@ -17,12 +17,35 @@
this.headerView = new HeaderView({ this.headerView = new HeaderView({
model: this.headerModel 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() { render: function() {
this.$el.html(_.template(teamsTabTemplate, {})); this.$el.append(this.headerView.$el);
this.$el.prepend(this.headerView.$el);
this.headerView.render(); this.headerView.render();
this.$el.append(this.tabbedView.$el);
this.tabbedView.render();
} }
}); });
......
<p class="teams-text">This is the new Teams tab.</p>
;(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);
(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('<nav class="page-content-nav" role="tablist">')
});
it('shows its first tab by default', function () {
expect(view.$el.text()).toContain('this is test text');
expect(view.$el.text()).not.toContain('other text');
});
it('displays titles for each tab', function () {
expect(view.$el.text()).toContain('Test 1');
expect(view.$el.text()).toContain('Test 2');
});
it('can switch tabs', function () {
view.$('.nav-item[data-index=1]').click();
expect(view.$el.text()).not.toContain('this is test text');
expect(view.$el.text()).toContain('other text');
});
it('changes tabs on navigation', function () {
expect(view.$('.nav-item.is-active').data('index')).toEqual(0);
Backbone.history.navigate('test 2', {trigger: true});
expect(view.$('.nav-item.is-active').data('index')).toEqual(1);
});
it('marks the active tab as selected using aria attributes', function () {
expect(view.$('.nav-item[data-index=0]')).toHaveAttr('aria-selected', 'true');
expect(view.$('.nav-item[data-index=1]')).toHaveAttr('aria-selected', 'false');
view.$('.nav-item[data-index=1]').click();
expect(view.$('.nav-item[data-index=0]')).toHaveAttr('aria-selected', 'false');
expect(view.$('.nav-item[data-index=1]')).toHaveAttr('aria-selected', 'true');
});
});
}
);
}).call(this, define || RequireJS.define);
...@@ -579,6 +579,7 @@ ...@@ -579,6 +579,7 @@
// Run the LMS tests // Run the LMS tests
'lms/include/teams/js/spec/teams_factory_spec.js', 'lms/include/teams/js/spec/teams_factory_spec.js',
'lms/include/js/spec/components/header/header_spec.js', 'lms/include/js/spec/components/header/header_spec.js',
'lms/include/js/spec/components/tabbed/tabbed_view_spec.js',
'lms/include/js/spec/photocapture_spec.js', 'lms/include/js/spec/photocapture_spec.js',
'lms/include/js/spec/staff_debug_actions_spec.js', 'lms/include/js/spec/staff_debug_actions_spec.js',
'lms/include/js/spec/views/notification_spec.js', 'lms/include/js/spec/views/notification_spec.js',
......
...@@ -92,10 +92,10 @@ fixture_paths: ...@@ -92,10 +92,10 @@ fixture_paths:
- templates/verify_student - templates/verify_student
- templates/file-upload.underscore - templates/file-upload.underscore
- templates/components/header - templates/components/header
- templates/components/tabbed
- js/fixtures/edxnotes - js/fixtures/edxnotes
- js/fixtures/search - js/fixtures/search
- templates/search - templates/search
- teams/templates
- templates/discovery - templates/discovery
requirejs: requirejs:
......
<a class="nav-item" href="" data-index="<%= index %>" role="tab" aria-selected="false"><%- title %></a>
<div class="page-content">
<nav class="page-content-nav" role="tablist"></nav>
<div class="sr-is-focusable" tabindex="-1"></div>
<div class="page-content-main"></div>
</div>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment