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:
@@ -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);
|
||||
|
||||
196
lms/static/js/spec/student_account/account.js
Normal file
196
lms/static/js/spec/student_account/account.js
Normal 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");
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
178
lms/static/js/spec/student_profile/profile.js
Normal file
178
lms/static/js/spec/student_profile/profile.js
Normal 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");
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -72,6 +72,8 @@ spec_paths:
|
||||
fixture_paths:
|
||||
- templates/instructor/instructor_dashboard_2
|
||||
- templates/dashboard
|
||||
- templates/student_account
|
||||
- templates/student_profile
|
||||
|
||||
requirejs:
|
||||
paths:
|
||||
|
||||
Reference in New Issue
Block a user