Use Backbone for student account and profile JS.

Validate student account and profile form fields. Use RequireJS for Jasmine tests of account and profile JS.
This commit is contained in:
Renzo Lucioni
2014-09-29 09:16:38 -04:00
parent 56d51c4f75
commit 00d976b85d
26 changed files with 1202 additions and 449 deletions

View File

@@ -212,6 +212,14 @@
},
// LMS class loaded explicitly until they are converted to use RequireJS
'js/student_account/account': {
exports: 'js/student_account/account',
deps: ['jquery', 'underscore', 'backbone', 'gettext', 'jquery.cookie']
},
'js/student_profile/profile': {
exports: 'js/student_profile/profile',
deps: ['jquery', 'underscore', 'backbone', 'gettext', 'jquery.cookie']
},
'js/verify_student/photocapture': {
exports: 'js/verify_student/photocapture'
},
@@ -261,6 +269,8 @@
'lms/include/js/spec/staff_debug_actions_spec.js',
'lms/include/js/spec/views/notification_spec.js',
'lms/include/js/spec/dashboard/donation.js',
'lms/include/js/spec/student_account/account.js',
'lms/include/js/spec/student_profile/profile.js'
]);
}).call(this, requirejs, define);

View File

@@ -0,0 +1,196 @@
define(['js/student_account/account'],
function() {
describe("edx.student.account.AccountModel", function() {
'use strict';
var account = null;
var assertValid = function(fields, isValid, expectedErrors) {
account.set(fields);
var errors = account.validate(account.attributes);
if (isValid) {
expect(errors).toBe(undefined);
} else {
expect(errors).toEqual(expectedErrors);
}
};
var EXPECTED_ERRORS = {
email: {
email: "Please enter a valid email address"
},
password: {
password: "Please enter a valid password"
}
};
beforeEach(function() {
account = new edx.student.account.AccountModel();
account.set({
email: "bob@example.com",
password: "password"
});
});
it("accepts valid email addresses", function() {
assertValid({email: "bob@example.com"}, true);
assertValid({email: "bob+smith@example.com"}, true);
assertValid({email: "bob+smith@example.com"}, true);
assertValid({email: "bob+smith@example.com"}, true);
assertValid({email: "bob@test.example.com"}, true);
assertValid({email: "bob@test-example.com"}, true);
});
it("rejects blank email addresses", function() {
assertValid({email: ""}, false, EXPECTED_ERRORS.email);
assertValid({email: " "}, false, EXPECTED_ERRORS.email);
});
it("rejects invalid email addresses", function() {
assertValid({email: "bob"}, false, EXPECTED_ERRORS.email);
assertValid({email: "bob@example"}, false, EXPECTED_ERRORS.email);
assertValid({email: "@"}, false, EXPECTED_ERRORS.email);
assertValid({email: "@example.com"}, false, EXPECTED_ERRORS.email);
// The server will reject emails with non-ASCII unicode
// Technically these are valid email addresses, but the email validator
// in Django 1.4 will reject them anyway, so we should too.
assertValid({email: "fŕáńḱ@example.com"}, false, EXPECTED_ERRORS.email);
assertValid({email: "frank@éxáḿṕĺé.com"}, false, EXPECTED_ERRORS.email);
});
it("rejects a long email address", function() {
// Construct an email exactly one character longer than the maximum length
var longEmail = new Array(account.EMAIL_MAX_LENGTH - 10).join("e") + "@example.com";
assertValid({email: longEmail}, false, EXPECTED_ERRORS.email);
});
it("accepts a valid password", function() {
assertValid({password: "password-test123"}, true, EXPECTED_ERRORS.password);
});
it("rejects a short password", function() {
assertValid({password: ""}, false, EXPECTED_ERRORS.password);
assertValid({password: "a"}, false, EXPECTED_ERRORS.password);
assertValid({password: "aa"}, true, EXPECTED_ERRORS.password);
});
it("rejects a long password", function() {
// Construct a password exactly one character longer than the maximum length
var longPassword = new Array(account.PASSWORD_MAX_LENGTH + 2).join("a");
assertValid({password: longPassword}, false, EXPECTED_ERRORS.password);
});
});
describe("edx.student.account.AccountView", function() {
var view = null,
ajaxSuccess = true;
var requestEmailChange = function(email, password) {
var fakeEvent = {preventDefault: function() {}};
view.model.set({
email: email,
password: password
});
view.submit(fakeEvent);
};
var assertAjax = function(url, method, data) {
expect($.ajax).toHaveBeenCalled();
var ajaxArgs = $.ajax.mostRecentCall.args[0];
expect(ajaxArgs.url).toEqual(url);
expect(ajaxArgs.type).toEqual(method);
expect(ajaxArgs.data).toEqual(data);
expect(ajaxArgs.headers.hasOwnProperty("X-CSRFToken")).toBe(true);
};
var assertEmailStatus = function(success, expectedStatus) {
if (!success) {
expect(view.$emailStatus).toHaveClass("validation-error");
} else {
expect(view.$emailStatus).not.toHaveClass("validation-error");
}
expect(view.$emailStatus.text()).toEqual(expectedStatus);
};
var assertPasswordStatus = function(success, expectedStatus) {
if (!success) {
expect(view.$passwordStatus).toHaveClass("validation-error");
} else {
expect(view.$passwordStatus).not.toHaveClass("validation-error");
}
expect(view.$passwordStatus.text()).toEqual(expectedStatus);
};
var assertRequestStatus = function(success, expectedStatus) {
if (!success) {
expect(view.$requestStatus).toHaveClass("error");
} else {
expect(view.$requestStatus).not.toHaveClass("error");
}
expect(view.$requestStatus.text()).toEqual(expectedStatus);
};
beforeEach(function() {
var fixture = readFixtures("templates/student_account/account.underscore");
setFixtures("<div id=\"account-tpl\">" + fixture + "</div>");
view = new edx.student.account.AccountView().render();
// Stub Ajax cals to return success/failure
spyOn($, "ajax").andCallFake(function() {
return $.Deferred(function(defer) {
if (ajaxSuccess) {
defer.resolve();
} else {
defer.reject();
}
}).promise();
});
});
it("requests an email address change", function() {
requestEmailChange("bob@example.com", "password");
assertAjax("email", "POST", {
email: "bob@example.com",
password: "password"
});
assertRequestStatus(true, "Please check your email to confirm the change");
});
it("displays email validation errors", function() {
// Invalid email should display an error
requestEmailChange("invalid", "password");
assertEmailStatus(false, "Please enter a valid email address");
// Once the error is fixed, the status should return to normal
requestEmailChange("bob@example.com", "password");
assertEmailStatus(true, "");
});
it("displays an invalid password error", function() {
// Password cannot be empty
requestEmailChange("bob@example.com", "");
assertPasswordStatus(false, "Please enter a valid password");
// Once the error is fixed, the status should return to normal
requestEmailChange("bob@example.com", "password");
assertPasswordStatus(true, "");
});
it("displays server errors", function() {
// Simulate an error from the server
ajaxSuccess = false;
requestEmailChange("bob@example.com", "password");
assertRequestStatus(false, "The data could not be saved.");
// On retry, it should succeed
ajaxSuccess = true;
requestEmailChange("bob@example.com", "password");
assertRequestStatus(true, "Please check your email to confirm the change");
});
});
}
);

View File

@@ -0,0 +1,178 @@
define(['js/student_profile/profile'],
function() {
describe("edx.student.profile.ProfileModel", function() {
'use strict';
var profile = null;
beforeEach(function() {
profile = new edx.student.profile.ProfileModel();
});
it("validates the full name field", function() {
// Full name cannot be blank
profile.set("fullName", "");
var errors = profile.validate(profile.attributes);
expect(errors).toEqual({
fullName: "Full name cannot be blank"
});
// Fill in the name and expect that the model is valid
profile.set("fullName", "Bob");
errors = profile.validate(profile.attributes);
expect(errors).toBe(undefined);
});
});
describe("edx.student.profile.PreferencesModel", function() {
var preferences = null;
beforeEach(function() {
preferences = new edx.student.profile.PreferencesModel();
});
it("validates the language field", function() {
// Language cannot be blank
preferences.set("language", "");
var errors = preferences.validate(preferences.attributes);
expect(errors).toEqual({
language: "Language cannot be blank"
});
// Fill in the language and expect that the model is valid
preferences.set("language", "eo");
errors = preferences.validate(preferences.attributes);
expect(errors).toBe(undefined);
});
});
describe("edx.student.profile.ProfileView", function() {
var view = null,
ajaxSuccess = true;
var updateProfile = function(fields) {
view.profileModel.set(fields);
view.clearStatus();
view.profileModel.save();
};
var updatePreferences = function(fields) {
view.preferencesModel.set(fields);
view.clearStatus();
view.preferencesModel.save();
};
var assertAjax = function(url, method, data) {
expect($.ajax).toHaveBeenCalled();
var ajaxArgs = $.ajax.mostRecentCall.args[0];
expect(ajaxArgs.url).toEqual(url);
expect(ajaxArgs.type).toEqual(method);
expect(ajaxArgs.data).toEqual(data)
expect(ajaxArgs.headers.hasOwnProperty("X-CSRFToken")).toBe(true);
};
var assertSubmitStatus = function(success, expectedStatus) {
if (!success) {
expect(view.$submitStatus).toHaveClass("error");
} else {
expect(view.$submitStatus).not.toHaveClass("error");
}
expect(view.$submitStatus.text()).toEqual(expectedStatus);
};
var assertValidationError = function(expectedError, selection) {
if (expectedError === null) {
expect(selection).not.toHaveClass("validation-error");
expect(selection.text()).toEqual("");
} else {
expect(selection).toHaveClass("validation-error");
expect(selection.text()).toEqual(expectedError);
}
};
beforeEach(function() {
var profileFixture = readFixtures("templates/student_profile/profile.underscore"),
languageFixture = readFixtures("templates/student_profile/languages.underscore");
setFixtures("<div id=\"profile-tpl\">" + profileFixture + "</div>");
appendSetFixtures("<div id=\"languages-tpl\">" + languageFixture + "</div>");
// Stub AJAX calls to return success / failure
spyOn($, "ajax").andCallFake(function() {
return $.Deferred(function(defer) {
if (ajaxSuccess) {
defer.resolve();
} else {
defer.reject();
}
}).promise();
});
var json = {
preferredLanguage: {code: 'eo', name: 'Dummy language'},
languages: [{code: 'eo', name: 'Dummy language'}]
};
spyOn($, "getJSON").andCallFake(function() {
return $.Deferred(function(defer) {
if (ajaxSuccess) {
defer.resolveWith(this, [json]);
} else {
defer.reject();
}
}).promise();
});
// Stub location.reload() to prevent test suite from reloading repeatedly
spyOn(edx.student.profile, "reloadPage").andCallFake(function() {
return true;
});
view = new edx.student.profile.ProfileView().render();
});
it("updates the student profile", function() {
updateProfile({fullName: "John Smith"});
assertAjax("", "PUT", {fullName: "John Smith"});
assertSubmitStatus(true, "Saved");
});
it("updates the student preferences", function() {
updatePreferences({language: "eo"});
assertAjax("preferences", "PUT", {language: "eo"});
assertSubmitStatus(true, "Saved");
});
it("displays full name validation errors", function() {
// Blank name should display a validation error
updateProfile({fullName: ""});
assertValidationError("Full name cannot be blank", view.$nameStatus);
// If we fix the problem and resubmit, the error should go away
updateProfile({fullName: "John Smith"});
assertValidationError(null, view.$nameStatus);
});
it("displays language validation errors", function() {
// Blank language should display a validation error
updatePreferences({language: ""});
assertValidationError("Language cannot be blank", view.$languageStatus);
// If we fix the problem and resubmit, the error should go away
updatePreferences({language: "eo"});
assertValidationError(null, view.$languageStatus);
});
it("displays an error if the sync fails", function() {
// If we get an error status on the AJAX request, display an error
ajaxSuccess = false;
updateProfile({fullName: "John Smith"});
assertSubmitStatus(false, "The data could not be saved.");
// If we try again and succeed, the error should go away
ajaxSuccess = true;
updateProfile({fullName: "John Smith"});
assertSubmitStatus(true, "Saved");
});
});
}
);

View File

@@ -1,141 +1,155 @@
var edx = edx || {};
(function($) {
(function($, _, Backbone, gettext) {
'use strict';
edx.student = edx.student || {};
edx.student.account = {};
edx.student.account = (function() {
var _fn = {
init: function() {
_fn.ajax.init();
_fn.eventHandlers.init();
},
edx.student.account.AccountModel = Backbone.Model.extend({
// These should be the same length limits enforced by the server
EMAIL_MIN_LENGTH: 3,
EMAIL_MAX_LENGTH: 254,
PASSWORD_MIN_LENGTH: 2,
PASSWORD_MAX_LENGTH: 75,
eventHandlers: {
init: function() {
_fn.eventHandlers.submit();
},
// This is the same regex used to validate email addresses in Django 1.4
EMAIL_REGEX: new RegExp(
"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" +
'|^"([\\001-\\010\\013\\014\\016-\\037!#-\\[\\]-\\177]|\\\\[\\001-\\011\\013\\014\\016-\\177])*"' +
')@((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\\.)+[A-Z]{2,6}\\.?$)' +
'|\\[(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\]$',
'i'
),
submit: function() {
$('#email-change-form').submit( _fn.form.submit );
}
},
defaults: {
email: '',
password: ''
},
ajax: {
init: function() {
var csrftoken = _fn.cookie.get( 'csrftoken' );
urlRoot: 'email',
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if ( settings.type === 'PUT' ) {
xhr.setRequestHeader( 'X-CSRFToken', csrftoken );
}
}
});
},
sync: function(method, model) {
var headers = {
'X-CSRFToken': $.cookie('csrftoken')
};
put: function( url, data ) {
$.ajax({
url: url,
type: 'PUT',
data: data
});
}
},
$.ajax({
url: model.urlRoot,
type: 'POST',
data: model.attributes,
headers: headers
})
.done(function() {
model.trigger('sync');
})
.fail(function() {
var error = gettext("The data could not be saved.");
model.trigger('error', error);
});
},
cookie: {
get: function( name ) {
return $.cookie(name);
}
},
validate: function(attrs) {
var errors = {};
form: {
isValid: true,
if (attrs.email.length < this.EMAIL_MIN_LENGTH ||
attrs.email.length > this.EMAIL_MAX_LENGTH ||
!this.EMAIL_REGEX.test(attrs.email)
) { errors.email = gettext("Please enter a valid email address"); }
submit: function( event ) {
var $email = $('#new-email'),
$password = $('#password'),
data = {
new_email: $email.val(),
password: $password.val()
};
event.preventDefault();
_fn.form.validate( $('#email-change-form') );
if ( _fn.form.isValid ) {
_fn.ajax.put( 'email_change_request', data );
}
},
validate: function( $form ) {
_fn.form.isValid = true;
$form.find('input').each( _fn.valid.input );
}
},
regex: {
email: function() {
// taken from http://parsleyjs.org/
return /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i;
}
},
valid: {
email: function( str ) {
var valid = false,
len = str ? str.length : 0,
regex = _fn.regex.email();
if ( 0 < len && len < 254 ) {
valid = regex.test( str );
}
return valid;
},
input: function() {
var $el = $(this),
validation = $el.data('validate'),
value = $el.val(),
valid = true;
if ( validation && validation.length > 0 ) {
$el.removeClass('error')
.css('border-color', '#c8c8c8'); // temp. for development
// Required field
if ( validation.indexOf('required') > -1 ) {
valid = _fn.valid.required( value );
}
// Email address
if ( valid && validation.indexOf('email') > -1 ) {
valid = _fn.valid.email( value );
}
if ( !valid ) {
$el.addClass('error')
.css('border-color', '#f00'); // temp. for development
_fn.form.isValid = false;
}
}
},
required: function( str ) {
return ( str && str.length > 0 ) ? true : false;
}
if (attrs.password.length < this.PASSWORD_MIN_LENGTH || attrs.password.length > this.PASSWORD_MAX_LENGTH) {
errors.password = gettext("Please enter a valid password");
}
};
return {
init: _fn.init
};
})();
if (!$.isEmptyObject(errors)) {
return errors;
}
}
});
edx.student.account.init();
edx.student.account.AccountView = Backbone.View.extend({
})(jQuery);
events: {
'submit': 'submit',
'change': 'change'
},
initialize: function() {
_.bindAll(this, 'render', 'submit', 'change', 'clearStatus', 'invalid', 'error', 'sync');
this.model = new edx.student.account.AccountModel();
this.model.on('invalid', this.invalid);
this.model.on('error', this.error);
this.model.on('sync', this.sync);
},
render: function() {
this.$el.html(_.template($('#account-tpl').html(), {}));
this.$email = $('#new-email', this.$el);
this.$password = $('#password', this.$el);
this.$emailStatus = $('#new-email-status', this.$el);
this.$passwordStatus = $('#password-status', this.$el);
this.$requestStatus = $('#request-email-status', this.$el);
return this;
},
submit: function(event) {
event.preventDefault();
this.clearStatus();
this.model.save();
},
change: function() {
this.model.set({
email: this.$email.val(),
password: this.$password.val()
});
},
invalid: function(model) {
var errors = model.validationError;
if (errors.hasOwnProperty('email')) {
this.$emailStatus
.addClass('validation-error')
.text(errors.email);
}
if (errors.hasOwnProperty('password')) {
this.$passwordStatus
.addClass('validation-error')
.text(errors.password);
}
},
error: function(error) {
this.$requestStatus
.addClass('error')
.text(error);
},
sync: function() {
this.$requestStatus
.addClass('success')
.text(gettext("Please check your email to confirm the change"));
},
clearStatus: function() {
this.$emailStatus
.removeClass('validation-error')
.text("");
this.$passwordStatus
.removeClass('validation-error')
.text("");
this.$requestStatus
.removeClass('error')
.text("");
},
});
return new edx.student.account.AccountView({
el: $('#account-container')
}).render();
})(jQuery, _, Backbone, gettext);

View File

@@ -1,97 +1,205 @@
var edx = edx || {};
(function($) {
(function($, _, Backbone, gettext) {
'use strict';
edx.student = edx.student || {};
edx.student.profile = {};
edx.student.profile = (function() {
var syncErrorMessage = gettext("The data could not be saved.");
var _fn = {
init: function() {
_fn.ajax.init();
_fn.eventHandlers.init();
},
edx.student.profile.reloadPage = function() {
location.reload();
};
eventHandlers: {
init: function() {
_fn.eventHandlers.submit();
_fn.eventHandlers.click();
},
edx.student.profile.ProfileModel = Backbone.Model.extend({
defaults: {
fullName: ''
},
submit: function() {
$('#name-change-form').on( 'submit', _fn.update.name );
},
urlRoot: '',
click: function() {
$('#language-change-form .submit-button').on( 'click', _fn.update.language );
}
},
sync: function(method, model) {
var headers = {
'X-CSRFToken': $.cookie('csrftoken')
};
update: {
name: function( event ) {
_fn.form.submit( event, '#new-name', 'new_name', 'name_change' );
},
$.ajax({
url: model.urlRoot,
type: 'PUT',
data: model.attributes,
headers: headers
})
.done(function() {
model.trigger('sync');
})
.fail(function() {
model.trigger('error', syncErrorMessage);
});
},
language: function( event ) {
/**
* The onSuccess argument here means: take `window.location.reload`
* and return a function that will use `window.location` as the
* `this` reference inside `reload()`.
*/
_fn.form.submit( event, '#new-language', 'new_language', 'language_change', window.location.reload.bind(window.location) );
}
},
validate: function(attrs) {
var errors = {};
if (attrs.fullName.length < 1) {
errors.fullName = gettext("Full name cannot be blank");
}
form: {
submit: function( event, idSelector, key, url, onSuccess ) {
var $selection = $(idSelector),
data = {};
if (!$.isEmptyObject(errors)) {
return errors;
}
}
});
data[key] = $selection.val();
edx.student.profile.PreferencesModel = Backbone.Model.extend({
defaults: {
language: 'en'
},
event.preventDefault();
_fn.ajax.put( url, data, onSuccess );
}
},
urlRoot: 'preferences',
ajax: {
init: function() {
var csrftoken = _fn.cookie.get( 'csrftoken' );
sync: function(method, model) {
var headers = {
'X-CSRFToken': $.cookie('csrftoken')
};
$.ajaxSetup({
beforeSend: function( xhr, settings ) {
if ( settings.type === 'PUT' ) {
xhr.setRequestHeader( 'X-CSRFToken', csrftoken );
}
}
});
},
$.ajax({
url: model.urlRoot,
type: 'PUT',
data: model.attributes,
headers: headers
})
.done(function() {
model.trigger('sync');
edx.student.profile.reloadPage();
})
.fail(function() {
model.trigger('error', syncErrorMessage);
});
},
put: function( url, data, onSuccess ) {
$.ajax({
url: url,
type: 'PUT',
data: data,
success: onSuccess ? onSuccess : ''
});
}
},
validate: function(attrs) {
var errors = {};
if (attrs.language.length < 1) {
errors.language = gettext("Language cannot be blank");
}
cookie: {
get: function( name ) {
return $.cookie(name);
}
},
if (!$.isEmptyObject(errors)) {
return errors;
}
}
});
};
edx.student.profile.ProfileView = Backbone.View.extend({
return {
init: _fn.init
};
events: {
'submit': 'submit',
'change': 'change'
},
})();
initialize: function() {
_.bindAll(this, 'render', 'change', 'submit', 'invalidProfile', 'invalidPreference', 'error', 'sync', 'clearStatus');
this.profileModel = new edx.student.profile.ProfileModel();
this.profileModel.on('invalid', this.invalidProfile);
this.profileModel.on('error', this.error);
this.profileModel.on('sync', this.sync);
edx.student.profile.init();
this.preferencesModel = new edx.student.profile.PreferencesModel();
this.preferencesModel.on('invalid', this.invalidPreference);
this.preferencesModel.on('error', this.error);
this.preferencesModel.on('sync', this.sync);
},
})(jQuery);
render: function() {
this.$el.html(_.template($('#profile-tpl').html()));
this.$nameField = $('#profile-name', this.$el);
this.$nameStatus = $('#profile-name-status', this.$el);
this.$languageChoices = $('#preference-language', this.$el);
this.$languageStatus = $('#preference-language-status', this.$el);
this.$submitStatus = $('#submit-status', this.$el);
var self = this;
$.getJSON('preferences/languages')
.done(function(json) {
/** Asynchronously populate the language choices. */
self.$languageChoices.html(_.template($('#languages-tpl').html(), {languageInfo: json}));
})
.fail(function() {
self.$languageStatus
.addClass('language-list-error')
.text(gettext("We couldn't populate the list of language choices."));
});
return this;
},
change: function() {
this.profileModel.set({
fullName: this.$nameField.val()
});
this.preferencesModel.set({
language: this.$languageChoices.val()
});
},
submit: function(event) {
event.preventDefault();
this.clearStatus();
this.profileModel.save();
this.preferencesModel.save();
},
invalidProfile: function(model) {
var errors = model.validationError;
if (errors.hasOwnProperty('fullName')) {
this.$nameStatus
.addClass('validation-error')
.text(errors.fullName);
}
},
invalidPreference: function(model) {
var errors = model.validationError;
if (errors.hasOwnProperty('language')) {
this.$languageStatus
.addClass('validation-error')
.text(errors.language);
}
},
error: function(error) {
this.$submitStatus
.addClass('error')
.text(error);
},
sync: function() {
this.$submitStatus
.addClass('success')
.text(gettext("Saved"));
},
clearStatus: function() {
this.$nameStatus
.removeClass('validation-error')
.text("");
this.$languageStatus
.removeClass('validation-error')
.text("");
this.$submitStatus
.removeClass('error')
.text("");
}
});
return new edx.student.profile.ProfileView({
el: $('#profile-container')
}).render();
})(jQuery, _, Backbone, gettext);

View File

@@ -72,6 +72,8 @@ spec_paths:
fixture_paths:
- templates/instructor/instructor_dashboard_2
- templates/dashboard
- templates/student_account
- templates/student_profile
requirejs:
paths: