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.
This commit is contained in:
@@ -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]
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<p class="teams-text">This is the new Teams tab.</p>
|
||||
59
lms/static/js/components/tabbed/views/tabbed_view.js
Normal file
59
lms/static/js/components/tabbed/views/tabbed_view.js
Normal file
@@ -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);
|
||||
78
lms/static/js/spec/components/tabbed/tabbed_view_spec.js
Normal file
78
lms/static/js/spec/components/tabbed/tabbed_view_spec.js
Normal file
@@ -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('<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 @@
|
||||
// Run the LMS tests
|
||||
'lms/include/teams/js/spec/teams_factory_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/staff_debug_actions_spec.js',
|
||||
'lms/include/js/spec/views/notification_spec.js',
|
||||
|
||||
@@ -92,10 +92,10 @@ fixture_paths:
|
||||
- templates/verify_student
|
||||
- templates/file-upload.underscore
|
||||
- templates/components/header
|
||||
- templates/components/tabbed
|
||||
- js/fixtures/edxnotes
|
||||
- js/fixtures/search
|
||||
- templates/search
|
||||
- teams/templates
|
||||
- templates/discovery
|
||||
|
||||
requirejs:
|
||||
|
||||
1
lms/templates/components/tabbed/tab.underscore
Normal file
1
lms/templates/components/tabbed/tab.underscore
Normal file
@@ -0,0 +1 @@
|
||||
<a class="nav-item" href="" data-index="<%= index %>" role="tab" aria-selected="false"><%- title %></a>
|
||||
5
lms/templates/components/tabbed/tabbed_view.underscore
Normal file
5
lms/templates/components/tabbed/tabbed_view.underscore
Normal file
@@ -0,0 +1,5 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user