Commit 69450fce by muhammad-ammar

create a new team

TNL-1899
parent 56da2852
...@@ -80,6 +80,15 @@ class FieldsMixin(object): ...@@ -80,6 +80,15 @@ class FieldsMixin(object):
query = self.q(css='.u-field-{} .u-field-message'.format(field_id)) query = self.q(css='.u-field-{} .u-field-message'.format(field_id))
return query.text[0] if query.present else None return query.text[0] if query.present else None
def message_for_textarea_field(self, field_id):
"""
Return the current message for textarea field.
"""
self.wait_for_field(field_id)
query = self.q(css='.u-field-{} .u-field-message-help'.format(field_id))
return query.text[0] if query.present else None
def wait_for_message(self, field_id, message): def wait_for_message(self, field_id, message):
""" """
Wait for a message to appear in a field. Wait for a message to appear in a field.
...@@ -229,3 +238,10 @@ class FieldsMixin(object): ...@@ -229,3 +238,10 @@ class FieldsMixin(object):
query = self.q(css='.u-field-{} a'.format(field_id)) query = self.q(css='.u-field-{} a'.format(field_id))
if query.present: if query.present:
query.first.click() query.first.click()
def error_for_field(self, field_id):
"""
Returns bool based on the highlighted border for field.
"""
query = self.q(css='.u-field-{}.error'.format(field_id))
return True if query.present else False
...@@ -6,11 +6,14 @@ Teams pages. ...@@ -6,11 +6,14 @@ Teams pages.
from .course_page import CoursePage from .course_page import CoursePage
from ..common.paging import PaginatedUIMixin from ..common.paging import PaginatedUIMixin
from .fields import FieldsMixin
TOPIC_CARD_CSS = 'div.wrapper-card-core' TOPIC_CARD_CSS = 'div.wrapper-card-core'
BROWSE_BUTTON_CSS = 'a.nav-item[data-index="1"]' BROWSE_BUTTON_CSS = 'a.nav-item[data-index="1"]'
TEAMS_LINK_CSS = '.action-view' TEAMS_LINK_CSS = '.action-view'
TEAMS_HEADER_CSS = '.teams-header' TEAMS_HEADER_CSS = '.teams-header'
CREATE_TEAM_LINK_CSS = '.create-team'
class TeamsPage(CoursePage): class TeamsPage(CoursePage):
...@@ -84,6 +87,7 @@ class BrowseTeamsPage(CoursePage, PaginatedUIMixin): ...@@ -84,6 +87,7 @@ class BrowseTeamsPage(CoursePage, PaginatedUIMixin):
def is_browser_on_page(self): def is_browser_on_page(self):
"""Check if we're on the teams list page for a particular topic.""" """Check if we're on the teams list page for a particular topic."""
self.wait_for_element_presence('.team-actions', 'Wait for the bottom links to be present')
has_correct_url = self.url.endswith(self.url_path) has_correct_url = self.url.endswith(self.url_path)
teams_list_view_present = self.q(css='.teams-main').present teams_list_view_present = self.q(css='.teams-main').present
return has_correct_url and teams_list_view_present return has_correct_url and teams_list_view_present
...@@ -102,3 +106,76 @@ class BrowseTeamsPage(CoursePage, PaginatedUIMixin): ...@@ -102,3 +106,76 @@ class BrowseTeamsPage(CoursePage, PaginatedUIMixin):
def team_cards(self): def team_cards(self):
"""Get all the team cards on the page.""" """Get all the team cards on the page."""
return self.q(css='.team-card') return self.q(css='.team-card')
def click_create_team_link(self):
""" Click on create team link."""
query = self.q(css=CREATE_TEAM_LINK_CSS)
if query.present:
query.first.click()
self.wait_for_ajax()
def click_search_team_link(self):
""" Click on create team link."""
query = self.q(css='.search-team-descriptions')
if query.present:
query.first.click()
self.wait_for_ajax()
def click_browse_all_teams_link(self):
""" Click on browse team link."""
query = self.q(css='.browse-teams')
if query.present:
query.first.click()
self.wait_for_ajax()
class CreateTeamPage(CoursePage, FieldsMixin):
"""
Create team page.
"""
def __init__(self, browser, course_id, topic):
"""
Set up `self.url_path` on instantiation, since it dynamically
reflects the current topic. Note that `topic` is a dict
representation of a topic following the same convention as a
course module's topic.
"""
super(CreateTeamPage, self).__init__(browser, course_id)
self.topic = topic
self.url_path = "teams/#topics/{topic_id}/create-team".format(topic_id=self.topic['id'])
def is_browser_on_page(self):
"""Check if we're on the create team page for a particular topic."""
has_correct_url = self.url.endswith(self.url_path)
teams_create_view_present = self.q(css='.team-edit-fields').present
return has_correct_url and teams_create_view_present
@property
def header_page_name(self):
"""Get the page name displayed by the page header"""
return self.q(css='.page-header .page-title')[0].text
@property
def header_page_description(self):
"""Get the page description displayed by the page header"""
return self.q(css='.page-header .page-description')[0].text
@property
def header_page_breadcrumbs(self):
"""Get the page breadcrumb text displayed by the page header"""
return self.q(css='.page-header .breadcrumbs')[0].text
@property
def validation_message_text(self):
"""Get the error message text"""
return self.q(css='.create-team.wrapper-msg .copy')[0].text
def submit_form(self):
"""Click on create team button"""
self.q(css='.create-team .action-primary').first.click()
self.wait_for_ajax()
def cancel_team(self):
"""Click on cancel team button"""
self.q(css='.create-team .action-cancel').first.click()
self.wait_for_ajax()
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
define(['backbone'], function (Backbone) { define(['backbone'], function (Backbone) {
var Team = Backbone.Model.extend({ var Team = Backbone.Model.extend({
defaults: { defaults: {
id: '', id: null,
name: '', name: '',
is_active: null, is_active: null,
course_id: '', course_id: '',
......
define([
'jquery',
'underscore',
'backbone',
'common/js/spec_helpers/ajax_helpers',
'teams/js/views/edit_team'
], function ($, _, Backbone, AjaxHelpers, TeamEditView) {
'use strict';
describe('EditTeam', function () {
var teamEditView,
teamsUrl = '/api/team/v0/teams/',
teamsData = {
id: null,
name: "TeamName",
is_active: null,
course_id: "a/b/c",
topic_id: "awesomeness",
date_created: "",
description: "TeamDescription",
country: "c",
language: "a",
membership: []
},
verifyValidation = function (requests, fieldsData) {
_.each(fieldsData, function (fieldData) {
teamEditView.$(fieldData[0]).val(fieldData[1]);
});
teamEditView.$('.create-team.form-actions .action-primary').click();
var message = teamEditView.$('.wrapper-msg');
expect(message.hasClass('is-hidden')).toBeFalsy();
expect(message.find('.title').text().trim()).toBe("Your team could not be created!");
expect(message.find('.copy').text().trim()).toBe(
"Check the highlighted fields below and try again."
);
_.each(fieldsData, function (fieldData) {
if(fieldData[2] === 'error') {
expect(teamEditView.$(fieldData[0].split(" ")[0] + '.error').length).toBe(1);
} else if(fieldData[2] === 'success') {
expect(teamEditView.$(fieldData[0].split(" ")[0] + '.error').length).toBe(0);
}
});
expect(requests.length).toBe(0);
},
expectContent = function (selector, text) {
expect(teamEditView.$(selector).text().trim()).toBe(text);
},
verifyDropdownData = function (selector, expectedItems) {
var options = teamEditView.$(selector)[0].options;
var renderedItems = $.map(options, function( elem ) {
return [[elem.value, elem.text]];
});
for (var i = 0; i < expectedItems.length; i++) {
expect(renderedItems).toContain(expectedItems[i]);
}
};
beforeEach(function () {
setFixtures('<div class="teams-content"></div>');
spyOn(Backbone.history, 'navigate');
teamEditView = new TeamEditView({
el: $('.teams-content'),
teamParams: {
teamsUrl: teamsUrl,
courseId: "a/b/c",
topicId: 'awesomeness',
topicName: 'Awesomeness',
languages: [['a', 'aaa'], ['b', 'bbb']],
countries: [['c', 'ccc'], ['d', 'ddd']]
}
}).render();
});
it('can render itself correctly', function () {
var fieldClasses = [
'.u-field-name',
'.u-field-description',
'.u-field-optional_description',
'.u-field-language',
'.u-field-country'
];
_.each(fieldClasses, function (fieldClass) {
expect(teamEditView.$el.find(fieldClass).length).toBe(1);
});
expect(teamEditView.$('.create-team.form-actions .action-primary').length).toBe(1);
expect(teamEditView.$('.create-team.form-actions .action-cancel').length).toBe(1);
});
it('can create a team', function () {
var requests = AjaxHelpers.requests(this);
teamEditView.$('.u-field-name input').val(teamsData.name);
teamEditView.$('.u-field-textarea textarea').val(teamsData.description);
teamEditView.$('.u-field-language select').val('a').attr("selected", "selected");
teamEditView.$('.u-field-country select').val('c').attr("selected", "selected");
teamEditView.$('.create-team.form-actions .action-primary').click();
AjaxHelpers.expectJsonRequest(requests, 'POST', teamsUrl, teamsData);
AjaxHelpers.respondWithJson(requests, teamsData);
expect(teamEditView.$('.create-team.wrapper-msg .copy').text().trim().length).toBe(0);
expect(Backbone.history.navigate.calls[0].args).toContain('topics/awesomeness');
});
it('shows validation error message when field is empty', function () {
var requests = AjaxHelpers.requests(this);
verifyValidation(requests, [
['.u-field-name input', 'Name', 'success'],
['.u-field-textarea textarea', '', 'error']
]);
teamEditView.render();
verifyValidation(requests, [
['.u-field-name input', '', 'error'],
['.u-field-textarea textarea', 'description', 'success']
]);
teamEditView.render();
verifyValidation(requests, [
['.u-field-name input', '', 'error'],
['.u-field-textarea textarea', '', 'error']
]);
});
it('shows validation error message when field value length exceeded the limit', function () {
var requests = AjaxHelpers.requests(this);
var teamName = new Array(500 + 1).join( '$' );
var teamDescription = new Array(500 + 1).join( '$' );
verifyValidation(requests, [
['.u-field-name input', teamName, 'error'],
['.u-field-textarea textarea', 'description', 'success']
]);
teamEditView.render();
verifyValidation(requests, [
['.u-field-name input', 'name', 'success'],
['.u-field-textarea textarea', teamDescription, 'error']
]);
teamEditView.render();
verifyValidation(requests, [
['.u-field-name input', teamName, 'error'],
['.u-field-textarea textarea', teamDescription, 'error']
]);
});
it("shows an error message for HTTP 500", function () {
var requests = AjaxHelpers.requests(this);
teamEditView.$('.u-field-name input').val(teamsData.name);
teamEditView.$('.u-field-textarea textarea').val(teamsData.description);
teamEditView.$('.create-team.form-actions .action-primary').click();
teamsData.country = '';
teamsData.language = '';
AjaxHelpers.expectJsonRequest(requests, 'POST', teamsUrl, teamsData);
AjaxHelpers.respondWithError(requests);
expect(teamEditView.$('.wrapper-msg .copy').text().trim()).toBe("An error occurred. Please try again.");
});
it("changes route on cancel click", function () {
teamEditView.$('.create-team.form-actions .action-cancel').click();
expect(Backbone.history.navigate.calls[0].args).toContain('topics/awesomeness');
});
});
});
define([
'jquery',
'backbone',
'teams/js/views/team_actions'
], function ($, Backbone, TeamActionsView) {
'use strict';
describe('TeamActions', function () {
var teamActionsView;
beforeEach(function () {
setFixtures('<div class="teams-content"></div>');
spyOn(Backbone.history, 'navigate');
teamActionsView = new TeamActionsView({
el: $('.teams-content'),
teamParams: {topicId: 'awesomeness'}
}).render();
});
it('can render itself correctly', function () {
expect(teamActionsView.$('.title').text()).toBe('Are you having trouble finding a team to join?');
expect(teamActionsView.$('.copy').text()).toBe(
"Try browsing all teams or searching team descriptions. If you " +
"still can't find a team to join, create a new team in this topic."
);
});
it('can navigate to correct routes', function () {
teamActionsView.$('a.browse-teams').click();
expect(Backbone.history.navigate.calls[0].args).toContain('browse');
teamActionsView.$('a.search-team-descriptions').click();
// TODO! Should be updated once team description search feature is available
expect(Backbone.history.navigate.calls[1].args).toContain('browse');
teamActionsView.$('a.create-team').click();
expect(Backbone.history.navigate.calls[2].args).toContain('topics/awesomeness/create-team');
});
});
});
define([ define([
'teams/js/collections/team', 'teams/js/views/teams' 'backbone', 'teams/js/collections/team', 'teams/js/views/teams'
], function (TeamCollection, TeamsView) { ], function (Backbone, TeamCollection, TeamsView) {
'use strict'; 'use strict';
describe('TeamsView', function () { describe('TeamsView', function () {
var teamsView, teamCollection, initialTeams, var teamsView, teamCollection, initialTeams,
...@@ -32,7 +32,8 @@ define([ ...@@ -32,7 +32,8 @@ define([
); );
teamsView = new TeamsView({ teamsView = new TeamsView({
el: '.teams-container', el: '.teams-container',
collection: teamCollection collection: teamCollection,
teamParams: {}
}).render(); }).render();
}); });
......
;(function (define) {
'use strict';
define(['backbone',
'underscore',
'gettext',
'js/views/fields',
'teams/js/models/team',
'text!teams/templates/edit-team.underscore'],
function (Backbone, _, gettext, FieldViews, TeamModel, edit_team_template) {
return Backbone.View.extend({
maxTeamNameLength: 255,
maxTeamDescriptionLength: 300,
events: {
"click .action-primary": "createTeam",
"click .action-cancel": "goBackToTopic"
},
initialize: function(options) {
this.courseId = options.teamParams.courseId;
this.teamsUrl = options.teamParams.teamsUrl;
this.topicId = options.teamParams.topicId;
this.languages = options.teamParams.languages;
this.countries = options.teamParams.countries;
this.primaryButtonTitle = options.primaryButtonTitle || 'Submit';
_.bindAll(this, "goBackToTopic", "createTeam");
this.teamModel = new TeamModel({});
this.teamModel.url = this.teamsUrl;
this.teamNameField = new FieldViews.TextFieldView({
model: this.teamModel,
title: gettext("Team Name (Required) *"),
valueAttribute: 'name',
helpMessage: gettext("A name that identifies your team (maximum 255 characters).")
});
this.teamDescriptionField = new FieldViews.TextareaFieldView({
model: this.teamModel,
title: gettext("Team Description (Required) *"),
valueAttribute: 'description',
editable: 'always',
showMessages: false,
helpMessage: gettext("A short description of the team to help other learners understand the goals or direction of the team (maximum 300 characters).")
});
this.optionalDescriptionField = new FieldViews.ReadonlyFieldView({
model: this.teamModel,
title: gettext("Optional Characteristics"),
valueAttribute: 'optional_description',
helpMessage: gettext("Help other learners decide whether to join your team by specifying some characteristics for your team. Choose carefully, because fewer people might be interested in joining your team if it seems too restrictive.")
});
this.teamLanguageField = new FieldViews.DropdownFieldView({
model: this.teamModel,
title: gettext("Language"),
valueAttribute: 'language',
required: false,
showMessages: false,
titleIconName: 'fa-comment-o',
options: this.languages,
helpMessage: gettext("The language that team members primarily use to communicate with each other.")
});
this.teamCountryField = new FieldViews.DropdownFieldView({
model: this.teamModel,
title: gettext('Country'),
valueAttribute: 'country',
required: false,
showMessages: false,
titleIconName: 'fa-globe',
options: this.countries,
helpMessage: gettext("The country that team members primarily identify with.")
});
},
render: function() {
this.$el.html(_.template(edit_team_template)({primaryButtonTitle: this.primaryButtonTitle}));
this.set(this.teamNameField, '.team-required-fields');
this.set(this.teamDescriptionField, '.team-required-fields');
this.set(this.optionalDescriptionField, '.team-optional-fields');
this.set(this.teamLanguageField, '.team-optional-fields');
this.set(this.teamCountryField, '.team-optional-fields');
return this;
},
set: function(view, selector) {
var viewEl = view.$el;
if (this.$(selector).has(viewEl).length) {
view.render().setElement(viewEl);
} else {
this.$(selector).append(view.render().$el);
}
},
createTeam: function () {
var teamName = this.teamNameField.fieldValue();
var teamDescription = this.teamDescriptionField.fieldValue();
var teamLanguage = this.teamLanguageField.fieldValue();
var teamCountry = this.teamCountryField.fieldValue();
var data = {
course_id: this.courseId,
topic_id: this.topicId,
name: teamName,
description: teamDescription,
language: _.isNull(teamLanguage) ? '' : teamLanguage,
country: _.isNull(teamCountry) ? '' : teamCountry
};
var validationResult = this.validateTeamData(data);
if (validationResult.status === false) {
this.showMessage(validationResult.message, validationResult.srMessage);
return;
}
var view = this;
var options = {
wait: true,
success: function () {
view.goBackToTopic();
},
error: function () {
var message = gettext('An error occurred. Please try again.');
view.showMessage(message, message);
}
};
this.teamModel.save(data, options);
},
validateTeamData: function (data) {
var status = true,
message = gettext("Check the highlighted fields below and try again.");
var srMessages = [];
this.teamNameField.unhighlightField();
this.teamDescriptionField.unhighlightField();
if (_.isEmpty(data.name.trim()) ) {
status = false;
this.teamNameField.highlightFieldOnError();
srMessages.push(
gettext("Enter team name.")
);
} else if (data.name.length > this.maxTeamNameLength) {
status = false;
this.teamNameField.highlightFieldOnError();
srMessages.push(
gettext("Team name cannot have more than 255 characters.")
);
}
if (_.isEmpty(data.description.trim()) ) {
status = false;
this.teamDescriptionField.highlightFieldOnError();
srMessages.push(
gettext("Enter team description.")
);
} else if (data.description.length > this.maxTeamDescriptionLength) {
status = false;
this.teamDescriptionField.highlightFieldOnError();
srMessages.push(
gettext("Team description cannot have more than 300 characters.")
);
}
return {
status: status,
message: message,
srMessage: srMessages.join(" ")
};
},
showMessage: function (message, screenReaderMessage) {
this.$('.wrapper-msg').removeClass('is-hidden');
this.$('.msg-content .copy p').text(message);
this.$('.wrapper-msg').focus();
if (screenReaderMessage) {
this.$('.screen-reader-message').text(screenReaderMessage);
}
},
goBackToTopic: function () {
Backbone.history.navigate("topics/" + this.topicId, {trigger: true});
}
});
});
}).call(this, define || RequireJS.define);
;(function (define) {
'use strict';
define([
'gettext',
'underscore',
'backbone',
'text!teams/templates/team-actions.underscore'
], function (gettext, _, Backbone, team_actions_template) {
return Backbone.View.extend({
events: {
'click a.browse-teams': 'browseTeams',
'click a.search-team-descriptions': 'searchTeamDescriptions',
'click a.create-team': 'showCreateTeamForm'
},
initialize: function (options) {
this.template = _.template(team_actions_template);
this.teamParams = options.teamParams;
},
render: function () {
var message = interpolate_text(
_.escape(gettext("Try {browse_span_start}browsing all teams{span_end} or {search_span_start}searching team descriptions{span_end}. If you still can't find a team to join, {create_span_start}create a new team in this topic{span_end}.")),
{
'browse_span_start': '<a class="browse-teams" href="">',
'search_span_start': '<a class="search-team-descriptions" href="">',
'create_span_start': '<a class="create-team" href="">',
'span_end': '</a>'
}
);
this.$el.html(this.template({message: message}));
return this;
},
browseTeams: function (event) {
event.preventDefault();
Backbone.history.navigate('browse', {trigger: true});
},
searchTeamDescriptions: function (event) {
event.preventDefault();
// TODO! Will navigate to correct place once required functionality is available
Backbone.history.navigate('browse', {trigger: true});
},
showCreateTeamForm: function (event) {
event.preventDefault();
Backbone.history.navigate('topics/' + this.teamParams.topicId + '/create-team', {trigger: true});
}
});
});
}).call(this, define || RequireJS.define);
;(function (define) { ;(function (define) {
'use strict'; 'use strict';
define([ define([
'backbone',
'teams/js/views/team_card', 'teams/js/views/team_card',
'common/js/components/views/paginated_view' 'common/js/components/views/paginated_view',
], function (TeamCardView, PaginatedView) { 'teams/js/views/team_actions'
], function (Backbone, TeamCardView, PaginatedView, TeamActionsView) {
var TeamsView = PaginatedView.extend({ var TeamsView = PaginatedView.extend({
type: 'teams', type: 'teams',
events: {
'click button.action': '' // entry point for team creation
},
initialize: function (options) { initialize: function (options) {
this.itemViewClass = TeamCardView.extend({ this.itemViewClass = TeamCardView.extend({
router: options.router, router: options.router,
maxTeamSize: options.maxTeamSize maxTeamSize: options.maxTeamSize
}); });
PaginatedView.prototype.initialize.call(this); PaginatedView.prototype.initialize.call(this);
this.teamParams = options.teamParams;
}, },
render: function () { render: function () {
PaginatedView.prototype.render.call(this); PaginatedView.prototype.render.call(this);
this.$el.append( var teamActionsView = new TeamActionsView({
$('<button class="action action-primary">' + gettext('Create new team') + '</button>') teamParams: this.teamParams
); });
this.$el.append(teamActionsView.$el);
teamActionsView.render();
return this; return this;
} }
}); });
......
...@@ -12,9 +12,11 @@ ...@@ -12,9 +12,11 @@
'teams/js/collections/topic', 'teams/js/collections/topic',
'teams/js/views/teams', 'teams/js/views/teams',
'teams/js/collections/team', 'teams/js/collections/team',
'teams/js/views/edit_team',
'text!teams/templates/teams_tab.underscore'], 'text!teams/templates/teams_tab.underscore'],
function (Backbone, _, gettext, HeaderView, HeaderModel, TabbedView, function (Backbone, _, gettext, HeaderView, HeaderModel, TabbedView,
TopicsView, TopicModel, TopicCollection, TeamsView, TeamCollection, teamsTemplate) { TopicsView, TopicModel, TopicCollection, TeamsView, TeamCollection,
TeamEditView, teamsTemplate) {
var ViewWithHeader = Backbone.View.extend({ var ViewWithHeader = Backbone.View.extend({
initialize: function (options) { initialize: function (options) {
this.header = options.header; this.header = options.header;
...@@ -38,6 +40,8 @@ ...@@ -38,6 +40,8 @@
this.topic_url = options.topic_url; this.topic_url = options.topic_url;
this.teams_url = options.teams_url; this.teams_url = options.teams_url;
this.maxTeamSize = options.maxTeamSize; this.maxTeamSize = options.maxTeamSize;
this.languages = options.languages;
this.countries = options.countries;
// This slightly tedious approach is necessary // This slightly tedious approach is necessary
// to use regular expressions within Backbone // to use regular expressions within Backbone
// routes, allowing us to capture which tab // routes, allowing us to capture which tab
...@@ -45,9 +49,10 @@ ...@@ -45,9 +49,10 @@
router = this.router = new Backbone.Router(); router = this.router = new Backbone.Router();
_.each([ _.each([
[':default', _.bind(this.routeNotFound, this)], [':default', _.bind(this.routeNotFound, this)],
['topics/:topic_id', _.bind(this.browseTopic, this)], ['topics/:topic_id(/)', _.bind(this.browseTopic, this)],
[new RegExp('^(browse)$'), _.bind(this.goToTab, this)], ['topics/:topic_id/create-team(/)', _.bind(this.newTeam, this)],
[new RegExp('^(teams)$'), _.bind(this.goToTab, this)] [new RegExp('^(browse)\/?$'), _.bind(this.goToTab, this)],
[new RegExp('^(teams)\/?$'), _.bind(this.goToTab, this)]
], function (route) { ], function (route) {
router.route.apply(router, route); router.route.apply(router, route);
}); });
...@@ -97,6 +102,35 @@ ...@@ -97,6 +102,35 @@
}, },
/** /**
* Render the create new team form.
*/
newTeam: function (topicId) {
var self = this;
this.getTeamsView(topicId).done(function (teamsView) {
self.mainView = new ViewWithHeader({
header: new HeaderView({
model: new HeaderModel({
description: gettext("Create a new team if you can't find existing teams to join, or if you would like to learn with friends you know."),
title: gettext("Create a New Team"),
breadcrumbs: [
{
title: teamsView.main.teamParams.topicName,
url: '#topics/' + teamsView.main.teamParams.topicId
}
]
})
}),
main: new TeamEditView({
tagName: 'create-new-team',
teamParams: teamsView.main.teamParams,
primaryButtonTitle: 'Create'
})
});
self.render();
});
},
/**
* Render the list of teams for the given topic ID. * Render the list of teams for the given topic ID.
*/ */
browseTopic: function (topicID) { browseTopic: function (topicID) {
...@@ -172,6 +206,14 @@ ...@@ -172,6 +206,14 @@
per_page: 10, per_page: 10,
parse: true parse: true
}), }),
teamParams: {
courseId: this.course_id,
teamsUrl: this.teams_url,
topicId: topic.get('id'),
topicName: topic.get('name'),
languages: self.languages,
countries: self.countries
},
maxTeamSize: this.maxTeamSize maxTeamSize: this.maxTeamSize
}) })
}); });
......
<div class="create-team wrapper-msg is-incontext urgency-low warning is-hidden" tabindex="-1">
<div class="msg">
<div class="msg-content">
<h3 class="title"><%- gettext("Your team could not be created!") %></h3>
<span class="screen-reader-message sr"></span>
<div class="copy">
<p></p>
</div>
</div>
</div>
</div>
<div class="form-instructions create-team-instructions">
<p class="copy">
<%- gettext("Enter information to describe your team. You cannot change these details after you create the team.") %></p>
</div>
<div class="team-edit-fields">
<div class="team-required-fields">
</div>
<div class="team-optional-fields">
</div>
</div>
<div class="create-team form-actions">
<button class="action action-primary">
<%=
interpolate_text(
_.escape(gettext("{primaryButtonTitle} {span_start}a new team{span_end}")),
{
'primaryButtonTitle': primaryButtonTitle, 'span_start': '<span class="sr">', 'span_end': '</span>'
}
)
%>
</button>
<button class="action action-cancel">
<%=
interpolate_text(
_.escape(gettext("Cancel {span_start}a new team{span_end}")),
{
'span_start': '<span class="sr">', 'span_end': '</span>'
}
)
%>
</button>
</div>
<div class="team-actions">
<h3 class="title"><%- gettext("Are you having trouble finding a team to join?") %></h3>
<p class="copy"><%= message %></p>
</div>
\ No newline at end of file
...@@ -28,7 +28,9 @@ ...@@ -28,7 +28,9 @@
topics_url: '${ topics_url }', topics_url: '${ topics_url }',
teams_url: '${ teams_url }', teams_url: '${ teams_url }',
maxTeamSize: ${ course.teams_max_size }, maxTeamSize: ${ course.teams_max_size },
course_id: '${ unicode(course.id) }' course_id: '${ unicode(course.id) }',
languages: ${ json.dumps(languages, cls=EscapedEdxJSONEncoder) },
countries: ${ json.dumps(countries, cls=EscapedEdxJSONEncoder) }
}); });
</%static:require_module> </%static:require_module>
</%block> </%block>
...@@ -21,6 +21,7 @@ from rest_framework import permissions ...@@ -21,6 +21,7 @@ from rest_framework import permissions
from django.db.models import Count from django.db.models import Count
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django_countries import countries
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_noop from django.utils.translation import ugettext_noop
...@@ -96,7 +97,9 @@ class TeamsDashboardView(View): ...@@ -96,7 +97,9 @@ class TeamsDashboardView(View):
'topics_detail', kwargs={'topic_id': 'topic_id', 'course_id': str(course_id)}, request=request 'topics_detail', kwargs={'topic_id': 'topic_id', 'course_id': str(course_id)}, request=request
), ),
"topics_url": reverse('topics_list', request=request), "topics_url": reverse('topics_list', request=request),
"teams_url": reverse('teams_list', request=request) "teams_url": reverse('teams_list', request=request),
"languages": settings.ALL_LANGUAGES,
"countries": list(countries),
} }
return render_to_response("teams/teams.html", context) return render_to_response("teams/teams.html", context)
......
...@@ -533,6 +533,8 @@ ...@@ -533,6 +533,8 @@
'lms/include/teams/js/spec/topics_spec.js', 'lms/include/teams/js/spec/topics_spec.js',
'lms/include/teams/js/spec/teams_spec.js', 'lms/include/teams/js/spec/teams_spec.js',
'lms/include/teams/js/spec/teams_tab_spec.js', 'lms/include/teams/js/spec/teams_tab_spec.js',
'lms/include/teams/js/spec/team_actions_spec.js',
'lms/include/teams/js/spec/edit_team_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/components/tabbed/tabbed_view_spec.js',
'lms/include/js/spec/components/card/card_spec.js', 'lms/include/js/spec/components/card/card_spec.js',
......
...@@ -49,7 +49,8 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers ...@@ -49,7 +49,8 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers
var selector = '.u-field-value > select'; var selector = '.u-field-value > select';
var fieldData = FieldViewsSpecHelpers.createFieldData(AccountSettingsFieldViews.DropdownFieldView, { var fieldData = FieldViewsSpecHelpers.createFieldData(AccountSettingsFieldViews.DropdownFieldView, {
valueAttribute: 'language', valueAttribute: 'language',
options: FieldViewsSpecHelpers.SELECT_OPTIONS options: FieldViewsSpecHelpers.SELECT_OPTIONS,
persistChanges: true
}); });
var view = new AccountSettingsFieldViews.LanguagePreferenceFieldView(fieldData).render(); var view = new AccountSettingsFieldViews.LanguagePreferenceFieldView(fieldData).render();
...@@ -92,7 +93,8 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers ...@@ -92,7 +93,8 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers
var selector = '.u-field-value > select'; var selector = '.u-field-value > select';
var fieldData = FieldViewsSpecHelpers.createFieldData(AccountSettingsFieldViews.DropdownFieldView, { var fieldData = FieldViewsSpecHelpers.createFieldData(AccountSettingsFieldViews.DropdownFieldView, {
valueAttribute: 'language_proficiencies', valueAttribute: 'language_proficiencies',
options: FieldViewsSpecHelpers.SELECT_OPTIONS options: FieldViewsSpecHelpers.SELECT_OPTIONS,
persistChanges: true
}); });
fieldData.model.set({'language_proficiencies': [{'code': FieldViewsSpecHelpers.SELECT_OPTIONS[0][0]}]}); fieldData.model.set({'language_proficiencies': [{'code': FieldViewsSpecHelpers.SELECT_OPTIONS[0][0]}]});
......
...@@ -101,7 +101,8 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers ...@@ -101,7 +101,8 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers
placeholderValue: "Tell other edX learners a little about yourself: where you live, " + placeholderValue: "Tell other edX learners a little about yourself: where you live, " +
"what your interests are, why you're taking courses on edX, or what you hope to learn.", "what your interests are, why you're taking courses on edX, or what you hope to learn.",
valueAttribute: "bio", valueAttribute: "bio",
helpMessage: '' helpMessage: '',
messagePosition: 'header'
}) })
]; ];
......
...@@ -126,6 +126,37 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers ...@@ -126,6 +126,37 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers
expectMessageContains(view, "Do not reset this!"); expectMessageContains(view, "Do not reset this!");
}; };
var verifyPersistence = function (fieldClass, requests) {
var fieldData = createFieldData(fieldClass, {
title: 'Username',
valueAttribute: 'username',
helpMessage: 'The username that you use to sign in to edX.',
validValue: 'My Name',
persistChanges: false,
messagePosition: 'header'
});
var view = new fieldClass(fieldData).render();
var valueInputSelector;
switch (fieldClass) {
case FieldViews.TextFieldView:
valueInputSelector = '.u-field-value > input';
break;
case FieldViews.DropdownFieldView:
valueInputSelector = '.u-field-value > select';
_.extend(fieldData, {validValue: SELECT_OPTIONS[0][0]});
break;
case FieldViews.TextareaFieldView:
valueInputSelector = '.u-field-value > textarea';
break;
}
view.$(valueInputSelector).val(fieldData.validValue).change();
expect(view.fieldValue()).toBe(fieldData.validValue);
expectMessageContains(view, view.helpMessage);
expect(requests.length).toBe(0);
};
var verifyEditableField = function (view, data, requests) { var verifyEditableField = function (view, data, requests) {
var request_data = {}; var request_data = {};
var url = view.model.url; var url = view.model.url;
...@@ -230,6 +261,7 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers ...@@ -230,6 +261,7 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers
verifySuccessMessageReset: verifySuccessMessageReset, verifySuccessMessageReset: verifySuccessMessageReset,
verifyEditableField: verifyEditableField, verifyEditableField: verifyEditableField,
verifyTextField: verifyTextField, verifyTextField: verifyTextField,
verifyDropDownField: verifyDropDownField verifyDropDownField: verifyDropDownField,
verifyPersistence: verifyPersistence
}; };
}); });
...@@ -72,7 +72,8 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers ...@@ -72,7 +72,8 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers
var fieldData = FieldViewsSpecHelpers.createFieldData(fieldViewClass, { var fieldData = FieldViewsSpecHelpers.createFieldData(fieldViewClass, {
title: 'Preferred Language', title: 'Preferred Language',
valueAttribute: 'language', valueAttribute: 'language',
helpMessage: 'Your preferred language.' helpMessage: 'Your preferred language.',
persistChanges: true
}); });
var view = new fieldViewClass(fieldData); var view = new fieldViewClass(fieldData);
...@@ -110,7 +111,8 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers ...@@ -110,7 +111,8 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers
var fieldData = FieldViewsSpecHelpers.createFieldData(FieldViews.TextFieldView, { var fieldData = FieldViewsSpecHelpers.createFieldData(FieldViews.TextFieldView, {
title: 'Full Name', title: 'Full Name',
valueAttribute: 'name', valueAttribute: 'name',
helpMessage: 'How are you?' helpMessage: 'How are you?',
persistChanges: true
}); });
var view = new FieldViews.TextFieldView(fieldData).render(); var view = new FieldViews.TextFieldView(fieldData).render();
...@@ -134,7 +136,8 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers ...@@ -134,7 +136,8 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers
title: 'Full Name', title: 'Full Name',
valueAttribute: 'name', valueAttribute: 'name',
helpMessage: 'edX full name', helpMessage: 'edX full name',
editable: 'never' editable: 'never',
persistChanges: true
}); });
var view = new FieldViews.DropdownFieldView(fieldData).render(); var view = new FieldViews.DropdownFieldView(fieldData).render();
...@@ -154,7 +157,8 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers ...@@ -154,7 +157,8 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers
var fieldData = FieldViewsSpecHelpers.createFieldData(FieldViews.DropdownFieldView, { var fieldData = FieldViewsSpecHelpers.createFieldData(FieldViews.DropdownFieldView, {
title: 'Full Name', title: 'Full Name',
valueAttribute: 'name', valueAttribute: 'name',
helpMessage: 'edX full name' helpMessage: 'edX full name',
persistChanges: true
}); });
var view = new FieldViews.DropdownFieldView(fieldData).render(); var view = new FieldViews.DropdownFieldView(fieldData).render();
...@@ -178,7 +182,8 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers ...@@ -178,7 +182,8 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers
title: 'Full Name', title: 'Full Name',
valueAttribute: 'name', valueAttribute: 'name',
helpMessage: 'edX full name', helpMessage: 'edX full name',
editable: 'toggle' editable: 'toggle',
persistChanges: true
}); });
var view = new FieldViews.DropdownFieldView(fieldData).render(); var view = new FieldViews.DropdownFieldView(fieldData).render();
...@@ -205,7 +210,8 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers ...@@ -205,7 +210,8 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers
valueAttribute: 'drop-down', valueAttribute: 'drop-down',
helpMessage: 'edX drop down', helpMessage: 'edX drop down',
editable: editable, editable: editable,
required:true required:true,
persistChanges: true
}); });
var view = new FieldViews.DropdownFieldView(fieldData).render(); var view = new FieldViews.DropdownFieldView(fieldData).render();
...@@ -230,7 +236,9 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers ...@@ -230,7 +236,9 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers
helpMessage: 'Wicked is good', helpMessage: 'Wicked is good',
placeholderValue: "Tell other edX learners a little about yourself: where you live, " + placeholderValue: "Tell other edX learners a little about yourself: where you live, " +
"what your interests are, why you’re taking courses on edX, or what you hope to learn.", "what your interests are, why you’re taking courses on edX, or what you hope to learn.",
editable: 'never' editable: 'never',
persistChanges: true,
messagePosition: 'header'
}); });
// set bio to empty to see the placeholder. // set bio to empty to see the placeholder.
...@@ -259,8 +267,9 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers ...@@ -259,8 +267,9 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers
helpMessage: 'Wicked is good', helpMessage: 'Wicked is good',
placeholderValue: "Tell other edX learners a little about yourself: where you live, " + placeholderValue: "Tell other edX learners a little about yourself: where you live, " +
"what your interests are, why you’re taking courses on edX, or what you hope to learn.", "what your interests are, why you’re taking courses on edX, or what you hope to learn.",
editable: 'toggle' editable: 'toggle',
persistChanges: true,
messagePosition: 'header'
}); });
fieldData.model.set({'bio': ''}); fieldData.model.set({'bio': ''});
...@@ -300,5 +309,30 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers ...@@ -300,5 +309,30 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers
FieldViewsSpecHelpers.expectTitleAndMessageToContain(view, fieldData.title, fieldData.helpMessage, false); FieldViewsSpecHelpers.expectTitleAndMessageToContain(view, fieldData.title, fieldData.helpMessage, false);
expect(view.$('.u-field-value > a .u-field-link-title-' + view.options.valueAttribute).text().trim()).toBe(fieldData.linkTitle); expect(view.$('.u-field-value > a .u-field-link-title-' + view.options.valueAttribute).text().trim()).toBe(fieldData.linkTitle);
}); });
it("correctly renders LinkFieldView", function() {
var fieldData = FieldViewsSpecHelpers.createFieldData(FieldViews.LinkFieldView, {
title: 'Title',
linkTitle: 'Link title',
helpMessage: 'Click the link.',
valueAttribute: 'password-reset'
});
var view = new FieldViews.LinkFieldView(fieldData).render();
FieldViewsSpecHelpers.expectTitleAndMessageToContain(view, fieldData.title, fieldData.helpMessage, false);
expect(view.$('.u-field-value > a .u-field-link-title-' + view.options.valueAttribute).text().trim()).toBe(fieldData.linkTitle);
});
it("can't persist changes if persistChanges is off", function() {
requests = AjaxHelpers.requests(this);
var fieldClasses = [
FieldViews.TextFieldView,
FieldViews.DropdownFieldView,
FieldViews.TextareaFieldView
];
for (var i = 0; i < fieldClasses.length; i++) {
FieldViewsSpecHelpers.verifyPersistence(fieldClasses[i], requests);
}
});
}); });
}); });
...@@ -39,7 +39,8 @@ ...@@ -39,7 +39,8 @@
model: userAccountModel, model: userAccountModel,
title: gettext('Full Name'), title: gettext('Full Name'),
valueAttribute: 'name', valueAttribute: 'name',
helpMessage: gettext('The name that appears on your certificates. Other learners never see your full name.') helpMessage: gettext('The name that appears on your certificates. Other learners never see your full name.'),
persistChanges: true
}) })
}, },
{ {
...@@ -49,7 +50,8 @@ ...@@ -49,7 +50,8 @@
valueAttribute: 'email', valueAttribute: 'email',
helpMessage: interpolate_text( helpMessage: interpolate_text(
gettext('The email address you use to sign in. Communications from {platform_name} and your courses are sent to this address.'), {platform_name: platformName} gettext('The email address you use to sign in. Communications from {platform_name} and your courses are sent to this address.'), {platform_name: platformName}
) ),
persistChanges: true
}) })
}, },
{ {
...@@ -74,7 +76,8 @@ ...@@ -74,7 +76,8 @@
helpMessage: interpolate_text( helpMessage: interpolate_text(
gettext('The language used throughout this site. This site is currently available in a limited number of languages.'), {platform_name: platformName} gettext('The language used throughout this site. This site is currently available in a limited number of languages.'), {platform_name: platformName}
), ),
options: fieldsData.language.options options: fieldsData.language.options,
persistChanges: true
}) })
}, },
{ {
...@@ -83,7 +86,8 @@ ...@@ -83,7 +86,8 @@
required: true, required: true,
title: gettext('Country or Region'), title: gettext('Country or Region'),
valueAttribute: 'country', valueAttribute: 'country',
options: fieldsData['country']['options'] options: fieldsData['country']['options'],
persistChanges: true
}) })
} }
] ]
...@@ -96,7 +100,8 @@ ...@@ -96,7 +100,8 @@
model: userAccountModel, model: userAccountModel,
title: gettext('Education Completed'), title: gettext('Education Completed'),
valueAttribute: 'level_of_education', valueAttribute: 'level_of_education',
options: fieldsData.level_of_education.options options: fieldsData.level_of_education.options,
persistChanges: true
}) })
}, },
{ {
...@@ -104,7 +109,8 @@ ...@@ -104,7 +109,8 @@
model: userAccountModel, model: userAccountModel,
title: gettext('Gender'), title: gettext('Gender'),
valueAttribute: 'gender', valueAttribute: 'gender',
options: fieldsData.gender.options options: fieldsData.gender.options,
persistChanges: true
}) })
}, },
{ {
...@@ -112,7 +118,8 @@ ...@@ -112,7 +118,8 @@
model: userAccountModel, model: userAccountModel,
title: gettext('Year of Birth'), title: gettext('Year of Birth'),
valueAttribute: 'year_of_birth', valueAttribute: 'year_of_birth',
options: fieldsData['year_of_birth']['options'] options: fieldsData['year_of_birth']['options'],
persistChanges: true
}) })
}, },
{ {
...@@ -120,7 +127,8 @@ ...@@ -120,7 +127,8 @@
model: userAccountModel, model: userAccountModel,
title: gettext('Preferred Language'), title: gettext('Preferred Language'),
valueAttribute: 'language_proficiencies', valueAttribute: 'language_proficiencies',
options: fieldsData.preferred_language.options options: fieldsData.preferred_language.options,
persistChanges: true
}) })
} }
] ]
......
...@@ -99,12 +99,13 @@ ...@@ -99,12 +99,13 @@
}, },
saveValue: function () { saveValue: function () {
var attributes = {}, if (this.persistChanges === true) {
value = this.fieldValue() ? [{'code': this.fieldValue()}] : []; var attributes = {},
attributes[this.options.valueAttribute] = value; value = this.fieldValue() ? [{'code': this.fieldValue()}] : [];
this.saveAttributes(attributes); attributes[this.options.valueAttribute] = value;
this.saveAttributes(attributes);
}
} }
}); });
AccountSettingsFieldViews.AuthFieldView = FieldViews.LinkFieldView.extend({ AccountSettingsFieldViews.AuthFieldView = FieldViews.LinkFieldView.extend({
......
...@@ -54,7 +54,8 @@ ...@@ -54,7 +54,8 @@
['all_users', gettext('Full Profile')] ['all_users', gettext('Full Profile')]
], ],
helpMessage: '', helpMessage: '',
accountSettingsPageUrl: options.account_settings_page_url accountSettingsPageUrl: options.account_settings_page_url,
persistChanges: true
}); });
var profileImageFieldView = new LearnerProfileFieldsView.ProfileImageFieldView({ var profileImageFieldView = new LearnerProfileFieldsView.ProfileImageFieldView({
...@@ -87,7 +88,8 @@ ...@@ -87,7 +88,8 @@
placeholderValue: gettext('Add Country'), placeholderValue: gettext('Add Country'),
valueAttribute: "country", valueAttribute: "country",
options: options.country_options, options: options.country_options,
helpMessage: '' helpMessage: '',
persistChanges: true
}), }),
new AccountSettingsFieldViews.LanguageProficienciesFieldView({ new AccountSettingsFieldViews.LanguageProficienciesFieldView({
model: accountSettingsModel, model: accountSettingsModel,
...@@ -100,7 +102,8 @@ ...@@ -100,7 +102,8 @@
placeholderValue: gettext('Add language'), placeholderValue: gettext('Add language'),
valueAttribute: "language_proficiencies", valueAttribute: "language_proficiencies",
options: options.language_options, options: options.language_options,
helpMessage: '' helpMessage: '',
persistChanges: true
}) })
]; ];
...@@ -112,7 +115,9 @@ ...@@ -112,7 +115,9 @@
title: gettext('About me'), title: gettext('About me'),
placeholderValue: gettext("Tell other learners a little about yourself: where you live, what your interests are, why you're taking courses, or what you hope to learn."), placeholderValue: gettext("Tell other learners a little about yourself: where you live, what your interests are, why you're taking courses, or what you hope to learn."),
valueAttribute: "bio", valueAttribute: "bio",
helpMessage: '' helpMessage: '',
persistChanges: true,
messagePosition: 'header'
}) })
]; ];
......
;(function (define, undefined) { ;(function (define, undefined) {
'use strict'; 'use strict';
define([ define([
'gettext', 'jquery', 'underscore', 'backbone', 'backbone-super', 'jquery.fileupload' 'gettext', 'jquery', 'underscore', 'backbone',
], function (gettext, $, _, Backbone) { 'text!templates/fields/field_readonly.underscore',
'text!templates/fields/field_dropdown.underscore',
'text!templates/fields/field_link.underscore',
'text!templates/fields/field_text.underscore',
'text!templates/fields/field_textarea.underscore',
'text!templates/fields/field_image.underscore',
'backbone-super', 'jquery.fileupload'
], function (gettext, $, _, Backbone,
field_readonly_template,
field_dropdown_template,
field_link_template,
field_text_template,
field_textarea_template,
field_image_template
) {
var messageRevertDelay = 6000; var messageRevertDelay = 6000;
var FieldViews = {}; var FieldViews = {};
...@@ -36,7 +50,7 @@ ...@@ -36,7 +50,7 @@
initialize: function () { initialize: function () {
this.template = _.template($(this.templateSelector).text()); this.template = _.template(this.fieldTemplate || '');
this.helpMessage = this.options.helpMessage || ''; this.helpMessage = this.options.helpMessage || '';
this.showMessages = _.isUndefined(this.options.showMessages) ? true : this.options.showMessages; this.showMessages = _.isUndefined(this.options.showMessages) ? true : this.options.showMessages;
...@@ -142,6 +156,7 @@ ...@@ -142,6 +156,7 @@
FieldViews.EditableFieldView = FieldViews.FieldView.extend({ FieldViews.EditableFieldView = FieldViews.FieldView.extend({
initialize: function (options) { initialize: function (options) {
this.persistChanges = _.isUndefined(options.persistChanges) ? false : options.persistChanges;
_.bindAll(this, 'saveAttributes', 'saveSucceeded', 'showDisplayMode', 'showEditMode', _.bindAll(this, 'saveAttributes', 'saveSucceeded', 'showDisplayMode', 'showEditMode',
'startEditing', 'finishEditing' 'startEditing', 'finishEditing'
); );
...@@ -158,20 +173,22 @@ ...@@ -158,20 +173,22 @@
}, },
saveAttributes: function (attributes, options) { saveAttributes: function (attributes, options) {
var view = this; if (this.persistChanges === true) {
var defaultOptions = { var view = this;
contentType: 'application/merge-patch+json', var defaultOptions = {
patch: true, contentType: 'application/merge-patch+json',
wait: true, patch: true,
success: function () { wait: true,
view.saveSucceeded(); success: function () {
}, view.saveSucceeded();
error: function (model, xhr) { },
view.showErrorMessage(xhr); error: function (model, xhr) {
} view.showErrorMessage(xhr);
}; }
this.showInProgressMessage(); };
this.model.save(attributes, _.extend(defaultOptions, options)); this.showInProgressMessage();
this.model.save(attributes, _.extend(defaultOptions, options));
}
}, },
saveSucceeded: function () { saveSucceeded: function () {
...@@ -210,6 +227,7 @@ ...@@ -210,6 +227,7 @@
}, },
finishEditing: function() { finishEditing: function() {
if (this.persistChanges === false) {return;}
if (this.fieldValue() !== this.modelValue()) { if (this.fieldValue() !== this.modelValue()) {
this.saveValue(); this.saveValue();
} else { } else {
...@@ -219,6 +237,14 @@ ...@@ -219,6 +237,14 @@
this.showDisplayMode(true); this.showDisplayMode(true);
} }
} }
},
highlightFieldOnError: function () {
this.$el.addClass('error');
},
unhighlightField: function () {
this.$el.removeClass('error');
} }
}); });
...@@ -226,7 +252,7 @@ ...@@ -226,7 +252,7 @@
fieldType: 'readonly', fieldType: 'readonly',
templateSelector: '#field_readonly-tpl', fieldTemplate: field_readonly_template,
initialize: function (options) { initialize: function (options) {
this._super(options); this._super(options);
...@@ -259,7 +285,7 @@ ...@@ -259,7 +285,7 @@
fieldType: 'text', fieldType: 'text',
templateSelector: '#field_text-tpl', fieldTemplate: field_text_template,
events: { events: {
'change input': 'saveValue' 'change input': 'saveValue'
...@@ -302,7 +328,7 @@ ...@@ -302,7 +328,7 @@
fieldType: 'dropdown', fieldType: 'dropdown',
templateSelector: '#field_dropdown-tpl', fieldTemplate: field_dropdown_template,
events: { events: {
'click': 'startEditing', 'click': 'startEditing',
...@@ -421,7 +447,7 @@ ...@@ -421,7 +447,7 @@
fieldType: 'textarea', fieldType: 'textarea',
templateSelector: '#field_textarea-tpl', fieldTemplate: field_textarea_template,
events: { events: {
'click .wrapper-u-field': 'startEditing', 'click .wrapper-u-field': 'startEditing',
...@@ -451,6 +477,7 @@ ...@@ -451,6 +477,7 @@
mode: this.mode, mode: this.mode,
value: value, value: value,
message: this.helpMessage, message: this.helpMessage,
messagePosition: this.options.messagePosition || 'footer',
placeholderValue: this.options.placeholderValue placeholderValue: this.options.placeholderValue
})); }));
this.delegateEvents(); this.delegateEvents();
...@@ -472,6 +499,7 @@ ...@@ -472,6 +499,7 @@
}, },
adjustTextareaHeight: function() { adjustTextareaHeight: function() {
if (this.persistChanges === false) {return;}
var textarea = this.$('textarea'); var textarea = this.$('textarea');
textarea.css('height', 'auto').css('height', textarea.prop('scrollHeight') + 10); textarea.css('height', 'auto').css('height', textarea.prop('scrollHeight') + 10);
}, },
...@@ -520,7 +548,7 @@ ...@@ -520,7 +548,7 @@
fieldType: 'link', fieldType: 'link',
templateSelector: '#field_link-tpl', fieldTemplate: field_link_template,
events: { events: {
'click a': 'linkClicked' 'click a': 'linkClicked'
...@@ -553,7 +581,7 @@ ...@@ -553,7 +581,7 @@
fieldType: 'image', fieldType: 'image',
templateSelector: '#field_image-tpl', fieldTemplate: field_image_template,
uploadButtonSelector: '.upload-button-input', uploadButtonSelector: '.upload-button-input',
titleAdd: gettext("Upload an image"), titleAdd: gettext("Upload an image"),
......
...@@ -51,6 +51,20 @@ ...@@ -51,6 +51,20 @@
text-align: center; text-align: center;
} }
// Below divider rules are moved here from _instructor_2.scss
// UI: visual dividers
.divider-lv0 {
border-top: ($baseline/5) solid $gray-l4;
}
.divider-lv1 {
border-top: ($baseline/10) solid $gray-l4;
}
.divider-lv2 {
border-top: ($baseline/20) solid $gray-l4;
}
// for verify_student/make_payment_step.underscore // for verify_student/make_payment_step.underscore
.payment-buttons { .payment-buttons {
......
...@@ -120,19 +120,6 @@ ...@@ -120,19 +120,6 @@
} }
} }
} }
// UI: visual dividers
.divider-lv0 {
border-top: ($baseline/5) solid $gray-l4;
}
.divider-lv1 {
border-top: ($baseline/10) solid $gray-l4;
}
.divider-lv2 {
border-top: ($baseline/20) solid $gray-l4;
}
} }
// instructor dashboard 2 // instructor dashboard 2
......
...@@ -472,4 +472,160 @@ ...@@ -472,4 +472,160 @@
padding-left: 2%; padding-left: 2%;
} }
} }
.team-actions {
@extend %ui-well;
margin: 20px 1.2%;
color: $gray;
text-align: center;
.title {
@extend %t-title6;
@extend %t-strong;
margin-bottom: ($baseline/2);
text-align: inherit;
color: inherit;
}
.copy {
text-align: inherit;
color: inherit;
}
}
}
.teams-content {
.teams-main {
.team-edit-fields {
@include clearfix();
.team-required-fields {
@include float(left);
width: 55%;
border-right: 2px solid $gray-l4;;
.u-field.u-field-name {
padding-bottom: $baseline;
.u-field-value {
display: block;
width: 90%;
input {
border-radius: ($baseline/5);
height: ($baseline*2);
}
}
.u-field-message {
@include padding-left(0);
padding-top: ($baseline/4);
width: 100%;
}
}
.u-field.u-field-description {
.u-field-value {
display: block;
width: 100%;
textarea {
height: ($baseline*5);
width: 90%;
border-radius: ($baseline/5)
}
}
.u-field-message {
display: block;
@extend %t-copy-sub1;
@include padding-left(0);
margin-top: ($baseline/4);
color: $gray-l1;
width: 90%;
}
}
.u-field-title {
padding-bottom: ($baseline/4);
color: $base-font-color;
width: 40%;
}
}
.team-optional-fields {
@include float(left);
@include margin-left($baseline);
width: 40%;
.u-field.u-field-optional_description {
margin-bottom: ($baseline/2);
.u-field-title {
color: $base-font-color;
font-weight: $font-semibold;
margin-bottom: ($baseline/5);
width: 100%;
}
.u-field-value {
display: none;
}
}
.u-field.u-field-language {
margin-bottom: ($baseline/5);
}
.u-field-value-display {
display: none;
}
.u-field-value {
width: 90%;
}
.u-field-title {
display: block;
color: $base-font-color;
width: 35%;
}
.u-field-message {
@include padding-left(0);
width: 95%;
}
}
}
.u-field {
padding: 0;
}
.u-field.error {
input, textarea {
border-color: $error-red;
}
.u-field-message-help, .u-field-description-message {
color: $error-red !important;
}
}
.create-team.wrapper-msg {
margin: 0 0 $baseline 0;
}
}
}
.form-instructions {
margin: ($baseline/2) 0 $baseline 0;
}
.create-team.form-actions {
margin-top: $baseline;
} }
<div class="wrapper-u-field"> <div class="wrapper-u-field">
<div class="u-field-header"> <div class="u-field-header">
<label class="u-field-title" for="u-field-textarea-<%- id %>" id="u-field-title-<%- id %>" aria-describedby="u-field-message-help-<%- id %>"></label> <label class="u-field-title" for="u-field-textarea-<%- id %>" id="u-field-title-<%- id %>" aria-describedby="u-field-message-help-<%- id %>"></label>
<span class="u-field-message" id="u-field-message-<%- id %>"> <% if (messagePosition === 'header') { %>
<span class="u-field-message-notification" aria-live="polite"></span> <span class="u-field-message" id="u-field-message-<%- id %>">
<span class="u-field-message-help" id="u-field-message-help-<%- id %>"> <%- message %></span> <span class="u-field-message-notification" aria-live="polite"></span>
</span> <span class="u-field-message-help" id="u-field-message-help-<%- id %>"> <%- message %></span>
</span>
<% }%>
</div> </div>
<div class="u-field-value" id="u-field-value-<%- id %>" aria-labelledby="u-field-title-<%- id %>"><% <div class="u-field-value" id="u-field-value-<%- id %>" aria-labelledby="u-field-title-<%- id %>"><%
if (mode === 'edit') { var textareaDescribedBy = (message ? 'u-field-message-help-' : 'u-field-placeholder-value-') + id;
%><textarea id="u-field-textarea-<%- id %>" rows="4" aria-describedby="u-field-placeholder-value-<%- id %>"><%- value %></textarea><% if (mode === 'edit') {%>
} else { <textarea id="u-field-textarea-<%- id %>" rows="4" aria-describedby="<%- textareaDescribedBy %>"><%- value %></textarea>
<% } else {
%><a href="#"><span class="sr"><%- screenReaderTitle %></span><span class="u-field-value-readonly" aria-hidden="false" aria-describedby="u-field-placeholder-value-<%- id %>"><%- value %></span><span class="sr"><%- gettext('Click to edit') %></span></a><% %><a href="#"><span class="sr"><%- screenReaderTitle %></span><span class="u-field-value-readonly" aria-hidden="false" aria-describedby="u-field-placeholder-value-<%- id %>"><%- value %></span><span class="sr"><%- gettext('Click to edit') %></span></a><%
} }
%><span class="sr" id="u-field-placeholder-value-<%- id %>"><%- placeholderValue %></span> %><span class="sr" id="u-field-placeholder-value-<%- id %>"><%- placeholderValue %></span>
</div> </div>
<div class="u-field-footer">
<% if (messagePosition === 'footer') { %>
<span class="u-field-message" id="u-field-message-<%- id %>">
<span class="u-field-message-notification" aria-live="polite"></span>
<span class="u-field-message-help" id="u-field-message-help-<%- id %>"> <%- message %></span>
</span>
<% } %>
</div>
</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