diff --git a/common/test/acceptance/pages/lms/login_and_register.py b/common/test/acceptance/pages/lms/login_and_register.py
index b6e2191f98..7700be8a61 100644
--- a/common/test/acceptance/pages/lms/login_and_register.py
+++ b/common/test/acceptance/pages/lms/login_and_register.py
@@ -356,10 +356,10 @@ class CombinedLoginAndRegisterPage(PageObject):
"""Wait for a status message to be visible following third_party registration, then return it."""
def _check_func():
"""Return third party auth status notice message."""
- for selector in ['.already-authenticated-msg p', '.status p']:
- msg_element = self.q(css=selector)
- if msg_element.visible:
- return (True, msg_element.text[0])
+ selector = '.js-auth-warning p'
+ msg_element = self.q(css=selector)
+ if msg_element.visible:
+ return (True, msg_element.text[0])
return (False, None)
return Promise(_check_func, "Result of third party auth is visible").fulfill()
diff --git a/lms/static/js/financial-assistance/views/financial_assistance_form_view.js b/lms/static/js/financial-assistance/views/financial_assistance_form_view.js
index 7b03e112f8..5ebe1eaffc 100644
--- a/lms/static/js/financial-assistance/views/financial_assistance_form_view.js
+++ b/lms/static/js/financial-assistance/views/financial_assistance_form_view.js
@@ -31,6 +31,8 @@
tpl: formViewTpl,
fieldTpl: formFieldTpl,
formType: 'financial-assistance',
+ successTpl: successTpl,
+ defaultFormErrorsTitle: gettext('Unable to submit application'),
requiredStr: '',
submitButton: '.js-submit-form',
@@ -81,7 +83,7 @@
},
renderSuccess: function() {
- this.$el.html(_.template(successTpl)({
+ this.$el.html(_.template(this.successTpl)({
course: this.model.get('course'),
dashboard_url: this.context.dashboard_url
}));
@@ -102,8 +104,7 @@
}
this.errors = ['
' + msg + ''];
- this.setErrors();
- this.element.hide(this.$resetSuccess);
+ this.renderErrors(this.defaultFormErrorsTitle, this.errors);
this.toggleDisableButton(false);
},
@@ -112,9 +113,7 @@
},
validateCountry: function() {
- var $submissionContainer = $('.submission-error'),
- $errorMessageContainer = $submissionContainer.find('.message-copy'),
- $countryLabel = $('#user-country-title'),
+ var $countryLabel = $('#user-country-title'),
txt = [
'Please go to your {link_start}profile page{link_end} ',
'and provide your country of residence.'
@@ -130,9 +129,8 @@
if (!this.model.get('country')) {
$countryLabel.addClass('error');
- $errorMessageContainer.append('' + msg + '');
+ this.renderErrors(this.defaultFormErrorsTitle, ['' + msg + '']);
this.toggleDisableButton(true);
- $submissionContainer.removeClass('hidden');
}
},
diff --git a/lms/static/js/spec/financial-assistance/financial_assistance_form_view_spec.js b/lms/static/js/spec/financial-assistance/financial_assistance_form_view_spec.js
index a94a62dfea..219236cd56 100644
--- a/lms/static/js/spec/financial-assistance/financial_assistance_form_view_spec.js
+++ b/lms/static/js/spec/financial-assistance/financial_assistance_form_view_spec.js
@@ -133,23 +133,23 @@ define([
failedSubmission = function() {
expect(view.$('.js-success-message').length).toEqual(0);
- expect(view.$('.submission-error')).toHaveClass('hidden');
+ expect(view.$formFeedback.find('.' + view.formErrorsJsHook).length).toEqual(0);
validSubmission();
view.model.trigger('error', {status: 500});
expect(view.$('.js-success-message').length).toEqual(0);
- expect(view.$('.submission-error')).not.toHaveClass('hidden');
+ expect(view.$formFeedback.find('.' + view.formErrorsJsHook).length).toEqual(1);
};
invalidCountry = function() {
expect(view.$('.js-success-message').length).toEqual(0);
- expect(view.$('.submission-error')).not.toHaveClass('hidden');
+ expect(view.$formFeedback.find('.' + view.formErrorsJsHook).length).toEqual(1);
expect(view.$('#user-country-title')).toHaveClass('error');
expect(view.$('.js-submit-form').prop('disabled')).toBeTruthy();
};
validCountry = function() {
expect(view.$('.js-success-message').length).toEqual(0);
- expect(view.$('.submission-error')).toHaveClass('hidden');
+ expect(view.$formFeedback.find('.' + view.formErrorsJsHook).length).toEqual(0);
expect(view.$('#user-country-title')).not.toHaveClass('error');
expect(view.$('.js-submit-form').prop('disabled')).toBeFalsy();
};
@@ -184,10 +184,10 @@ define([
});
it('should not submit the form if the front end validation fails', function() {
- expect(view.$('.submission-error')).toHaveClass('hidden');
+ expect(view.$formFeedback.find('.' + view.formErrorsJsHook).length).toEqual(0);
view.$('.js-submit-form').click();
expect(view.model.save).not.toHaveBeenCalled();
- expect(view.$('.submission-error')).not.toHaveClass('hidden');
+ expect(view.$formFeedback.find('.' + view.formErrorsJsHook).length).toEqual(1);
});
it('should submit the form data and additional data if validation passes', function() {
diff --git a/lms/static/js/spec/student_account/login_spec.js b/lms/static/js/spec/student_account/login_spec.js
index 7ecc50ee7b..5f5da06c48 100644
--- a/lms/static/js/spec/student_account/login_spec.js
+++ b/lms/static/js/spec/student_account/login_spec.js
@@ -191,10 +191,10 @@
});
AjaxHelpers.expectRequest(
- requests, 'POST',
- FORM_DESCRIPTION.submit_url,
- $.param(expectedData)
- );
+ requests, 'POST',
+ FORM_DESCRIPTION.submit_url,
+ $.param(expectedData)
+ );
});
it('displays third-party auth login buttons', function() {
@@ -212,6 +212,21 @@
expect($('.forgot-password')).toBeVisible();
});
+ it('displays password reset success message after password reset request', function() {
+ createLoginView(this);
+
+ // Verify that the success message is not visible
+ expect(view.$formFeedback.find('.' + view.passwordResetSuccessJsHook).length).toEqual(0);
+
+ /* After a successful password reset request, the resetModel will trigger a 'sync'
+ * event, which lets the LoginView know to render the password reset success message.
+ */
+ view.resetModel.trigger('sync');
+
+ // Verify that the success message is visible
+ expect(view.$formFeedback.find('.' + view.passwordResetSuccessJsHook).length).toEqual(1);
+ });
+
it('validates login form fields', function() {
createLoginView(this);
@@ -229,10 +244,11 @@
submitForm(false);
// Verify that submission errors are visible
- expect(view.$errors).not.toHaveClass('hidden');
+ expect(view.$formFeedback.find('.' + view.formErrorsJsHook).length).toEqual(1);
// Expect auth complete NOT to have been triggered
expect(authComplete).toBe(false);
+
// Form button should be re-enabled when errors occur
expect(view.$submitButton).not.toHaveAttr('disabled');
});
@@ -247,8 +263,10 @@
AjaxHelpers.respondWithError(requests);
// Expect that an error is displayed and that auth complete is not triggered
- expect(view.$errors).not.toHaveClass('hidden');
+ expect(view.$formFeedback.find('.' + view.formErrorsJsHook).length).toEqual(1);
+
expect(authComplete).toBe(false);
+
// Form button should be re-enabled on server failure.
expect(view.$submitButton).not.toHaveAttr('disabled');
@@ -262,17 +280,18 @@
AjaxHelpers.respondWithJson(requests, {});
// Expect that the error is hidden and auth complete is triggered
- expect(view.$errors).toHaveClass('hidden');
+ expect(view.$formFeedback.find('.' + view.formErrorsJsHook).length).toEqual(0);
expect(authComplete).toBe(true);
});
it('displays an error if there is no internet connection', function() {
var clock,
oldTimeout,
- timeout;
+ timeout,
+ $error;
// We're defining "no internet connection" in this case as the
- // request timing out. We use a combination of the sinon fake
+ // request timing out. We use a combination of the sinon fake
// timer and jQuery.ajaxSetup() to force a request timeout.
clock = sinon.useFakeTimers();
oldTimeout = $.ajaxSetup().timeout;
@@ -288,11 +307,12 @@
clock.tick(timeout + 1);
// Expect that an error is displayed and that auth complete is not triggered
- expect(view.$errors).not.toHaveClass('hidden');
+ $error = view.$formFeedback.find('.' + view.formErrorsJsHook);
+ expect($error.length).toEqual(1);
+ expect($error.text()).toContain(
+ 'An error has occurred. Check your Internet connection and try again.'
+ );
expect(authComplete).toBe(false);
- expect(view.$errors.text()).toContain(
- 'An error has occurred. Check your Internet connection and try again.'
- );
// Finally, restore the old timeout and turn off the fake timer.
$.ajaxSetup({timeout: oldTimeout});
@@ -300,6 +320,7 @@
});
it('displays an error if there is a server error', function() {
+ var $error;
createLoginView(this);
// Submit the form, with successful validation
@@ -309,11 +330,12 @@
AjaxHelpers.respondWithError(requests, 500);
// Expect that an error is displayed and that auth complete is not triggered
- expect(view.$errors).not.toHaveClass('hidden');
+ $error = view.$formFeedback.find('.' + view.formErrorsJsHook);
+ expect($error.length).toEqual(1);
+ expect($error.text()).toContain(
+ 'An error has occurred. Try refreshing the page, or check your Internet connection.'
+ );
expect(authComplete).toBe(false);
- expect(view.$errors.text()).toContain(
- 'An error has occurred. Try refreshing the page, or check your Internet connection.'
- );
});
});
});
diff --git a/lms/static/js/spec/student_account/password_reset_spec.js b/lms/static/js/spec/student_account/password_reset_spec.js
index 2124d6c57b..8846655312 100644
--- a/lms/static/js/spec/student_account/password_reset_spec.js
+++ b/lms/static/js/spec/student_account/password_reset_spec.js
@@ -74,26 +74,32 @@
});
it('allows the user to request a new password', function() {
+ var syncSpy, passwordEmailSentSpy;
+
createPasswordResetView(this);
+ // We expect these events to be triggered upon a successful password reset
+ syncSpy = jasmine.createSpy('syncEvent');
+ passwordEmailSentSpy = jasmine.createSpy('passwordEmailSentEvent');
+ view.listenTo(view.model, 'sync', syncSpy);
+ view.listenTo(view, 'password-email-sent', passwordEmailSentSpy);
+
// Submit the form, with successful validation
submitEmail(true);
// Verify that the client contacts the server with the expected data
AjaxHelpers.expectRequest(
- requests, 'POST',
- FORM_DESCRIPTION.submit_url,
- $.param({email: EMAIL})
- );
+ requests, 'POST',
+ FORM_DESCRIPTION.submit_url,
+ $.param({email: EMAIL})
+ );
// Respond with status code 200
AjaxHelpers.respondWithJson(requests, {});
- // Verify that the success message is visible
- expect($('.js-reset-success')).not.toHaveClass('hidden');
-
- // Verify that login form has loaded
- expect($('#login-form')).not.toHaveClass('hidden');
+ // Verify that the events were triggered
+ expect(syncSpy).toHaveBeenCalled();
+ expect(passwordEmailSentSpy).toHaveBeenCalled();
// Verify that password reset view has been removed
expect($(view.el).html().length).toEqual(0);
@@ -109,7 +115,7 @@
expect(view.validate).toHaveBeenCalledWith($('#password-reset-email')[0]);
// Verify that no submission errors are visible
- expect(view.$errors).toHaveClass('hidden');
+ expect(view.$formFeedback.find('.' + view.formErrorsJsHook).length).toEqual(0);
});
it('displays password reset validation errors', function() {
@@ -119,7 +125,7 @@
submitEmail(false);
// Verify that submission errors are visible
- expect(view.$errors).not.toHaveClass('hidden');
+ expect(view.$formFeedback.find('.' + view.formErrorsJsHook).length).toEqual(1);
});
it('displays an error if the server returns an error while sending a password reset email', function() {
@@ -130,7 +136,7 @@
AjaxHelpers.respondWithError(requests);
// Expect that an error is displayed
- expect(view.$errors).not.toHaveClass('hidden');
+ expect(view.$formFeedback.find('.' + view.formErrorsJsHook).length).toEqual(1);
// If we try again and succeed, the error should go away
submitEmail();
@@ -139,7 +145,7 @@
AjaxHelpers.respondWithJson(requests, {});
// Expect that the error is hidden
- expect(view.$errors).toHaveClass('hidden');
+ expect(view.$formFeedback.find('.' + view.formErrorsJsHook).length).toEqual(0);
});
});
});
diff --git a/lms/static/js/spec/student_account/register_spec.js b/lms/static/js/spec/student_account/register_spec.js
index 1b5b614224..3947aceee8 100644
--- a/lms/static/js/spec/student_account/register_spec.js
+++ b/lms/static/js/spec/student_account/register_spec.js
@@ -243,16 +243,17 @@
// Verify that the client contacts the server with the expected data
AjaxHelpers.expectRequest(
- requests, 'POST',
- FORM_DESCRIPTION.submit_url,
- $.param(USER_DATA)
- );
+ requests, 'POST',
+ FORM_DESCRIPTION.submit_url,
+ $.param(USER_DATA)
+ );
// Respond with status code 200
AjaxHelpers.respondWithJson(requests, {});
// Verify that auth complete is triggered
expect(authComplete).toBe(true);
+
// Form button should be disabled on success.
expect(view.$submitButton).toHaveAttr('disabled');
});
@@ -278,10 +279,10 @@
$.extend(expectedData, USER_DATA);
AjaxHelpers.expectRequest(
- requests, 'POST',
- FORM_DESCRIPTION.submit_url,
- $.param(expectedData)
- );
+ requests, 'POST',
+ FORM_DESCRIPTION.submit_url,
+ $.param(expectedData)
+ );
});
it('displays third-party auth registration buttons', function() {
@@ -305,7 +306,8 @@
expect(view.validate).toHaveBeenCalledWith($('#register-password')[0]);
// Verify that no submission errors are visible
- expect(view.$errors).toHaveClass('hidden');
+ expect(view.$formFeedback.find('.' + view.formErrorsJsHook).length).toEqual(0);
+
// Form button should be disabled on success.
expect(view.$submitButton).toHaveAttr('disabled');
});
@@ -317,10 +319,11 @@
submitForm(false);
// Verify that submission errors are visible
- expect(view.$errors).not.toHaveClass('hidden');
+ expect(view.$formFeedback.find('.' + view.formErrorsJsHook).length).toEqual(1);
// Expect that auth complete is NOT triggered
expect(authComplete).toBe(false);
+
// Form button should be re-enabled on error.
expect(view.$submitButton).not.toHaveAttr('disabled');
});
@@ -335,7 +338,7 @@
AjaxHelpers.respondWithError(requests);
// Expect that an error is displayed and that auth complete is NOT triggered
- expect(view.$errors).not.toHaveClass('hidden');
+ expect(view.$formFeedback.find('.' + view.formErrorsJsHook).length).toEqual(1);
expect(authComplete).toBe(false);
// If we try again and succeed, the error should go away
@@ -345,8 +348,9 @@
AjaxHelpers.respondWithJson(requests, {});
// Expect that the error is hidden and that auth complete is triggered
- expect(view.$errors).toHaveClass('hidden');
+ expect(view.$formFeedback.find('.' + view.formErrorsJsHook).length).toEqual(0);
expect(authComplete).toBe(true);
+
// Form button should be disabled on success.
expect(view.$submitButton).toHaveAttr('disabled');
});
diff --git a/lms/static/js/student_account/views/FormView.js b/lms/static/js/student_account/views/FormView.js
index 7bd52379c3..719288d4bd 100644
--- a/lms/static/js/student_account/views/FormView.js
+++ b/lms/static/js/student_account/views/FormView.js
@@ -4,9 +4,11 @@
'jquery',
'underscore',
'backbone',
- 'common/js/utils/edx.utils.validate'
+ 'common/js/utils/edx.utils.validate',
+ 'edx-ui-toolkit/js/utils/html-utils',
+ 'text!templates/student_account/form_errors.underscore'
],
- function($, _, Backbone, EdxUtilsValidate) {
+ function($, _, Backbone, EdxUtilsValidate, HtmlUtils, formErrorsTpl) {
return Backbone.View.extend({
tagName: 'form',
@@ -16,6 +18,12 @@
fieldTpl: '#form_field-tpl',
+ formErrorsTpl: formErrorsTpl,
+
+ formErrorsJsHook: 'js-form-errors',
+
+ defaultFormErrorsTitle: gettext('An error occurred.'),
+
events: {},
errors: [],
@@ -66,7 +74,7 @@
postRender: function() {
var $container = $(this.el);
this.$form = $container.find('form');
- this.$errors = $container.find('.submission-error');
+ this.$formFeedback = $container.find('.js-form-feedback');
this.$submitButton = $container.find(this.submitButton);
},
@@ -126,21 +134,6 @@
return obj;
},
- focusFirstError: function() {
- var $error = this.$form.find('.error').first(),
- $field = {},
- $parent = {};
-
- if ($error.is('label')) {
- $parent = $error.parent('.form-field');
- $error = $parent.find('input') || $parent.find('select');
- } else {
- $field = $error;
- }
-
- $error.focus();
- },
-
forgotPassword: function(event) {
event.preventDefault();
@@ -185,32 +178,34 @@
saveError: function(error) {
this.errors = ['' + error.responseText + ''];
- this.setErrors();
+ this.renderErrors(this.defaultFormErrorsTitle, this.errors);
this.toggleDisableButton(false);
},
- setErrors: function() {
- var $msg = this.$errors.find('.message-copy'),
- html = [],
- errors = this.errors,
- i,
- len = errors.length;
+ /* Wrapper for renderFormFeedback provided for convenience since the majority of
+ * our calls to renderFormFeedback are for rendering error messages.
+ */
+ renderErrors: function(title, errorMessages) {
+ this.clearFormErrors();
- for (i = 0; i < len; i++) {
- html.push(errors[i]);
- }
+ this.renderFormFeedback(this.formErrorsTpl, {
+ jsHook: this.formErrorsJsHook,
+ title: title,
+ messagesHtml: HtmlUtils.HTML(errorMessages.join(''))
+ });
+ },
- $msg.html(html.join(''));
+ renderFormFeedback: function(template, context) {
+ var tpl = HtmlUtils.template(template);
+ HtmlUtils.prepend(this.$formFeedback, tpl(context));
- this.element.show(this.$errors);
-
- // Scroll to error messages
+ // Scroll to feedback container
$('html,body').animate({
- scrollTop: this.$errors.offset().top
+ scrollTop: this.$formFeedback.offset().top
}, 'slow');
- // Focus on first error field
- this.focusFirstError();
+ // Focus on the feedback container to ensure screen readers see the messages.
+ this.$formFeedback.focus();
},
/* Allows extended views to add non-form attributes
@@ -233,9 +228,10 @@
data = this.setExtraData(data);
this.model.set(data);
this.model.save();
- this.toggleErrorMsg(false);
+ this.clearFormErrors();
} else {
- this.toggleErrorMsg(true);
+ this.renderErrors(this.defaultFormErrorsTitle, this.errors);
+ this.toggleDisableButton(false);
}
this.postFormSubmission();
@@ -248,12 +244,15 @@
return true;
},
- toggleErrorMsg: function(show) {
- if (show) {
- this.setErrors();
- this.toggleDisableButton(false);
- } else {
- this.element.hide(this.$errors);
+ clearFormErrors: function() {
+ var query = '.' + this.formErrorsJsHook;
+ this.clearFormFeedbackItems(query);
+ },
+
+ clearFormFeedbackItems: function(query) {
+ var $items = this.$formFeedback.find(query);
+ if ($items.length > 0) {
+ $items.remove();
}
},
diff --git a/lms/static/js/student_account/views/LoginView.js b/lms/static/js/student_account/views/LoginView.js
index b12d079b51..274b579868 100644
--- a/lms/static/js/student_account/views/LoginView.js
+++ b/lms/static/js/student_account/views/LoginView.js
@@ -5,9 +5,11 @@
'underscore',
'gettext',
'edx-ui-toolkit/js/utils/html-utils',
- 'js/student_account/views/FormView'
+ 'js/student_account/views/FormView',
+ 'text!templates/student_account/form_success.underscore',
+ 'text!templates/student_account/form_status.underscore'
],
- function($, _, gettext, HtmlUtils, FormView) {
+ function($, _, gettext, HtmlUtils, FormView, formSuccessTpl, formStatusTpl) {
return FormView.extend({
el: '#login-form',
tpl: '#login-tpl',
@@ -19,12 +21,17 @@
formType: 'login',
requiredStr: '',
submitButton: '.js-login',
+ formSuccessTpl: formSuccessTpl,
+ formStatusTpl: formStatusTpl,
+ authWarningJsHook: 'js-auth-warning',
+ passwordResetSuccessJsHook: 'js-password-reset-success',
+ defaultFormErrorsTitle: gettext('We couldn\'t sign you in.'),
preRender: function(data) {
this.providers = data.thirdPartyAuth.providers || [];
this.hasSecondaryProviders = (
- data.thirdPartyAuth.secondaryProviders && data.thirdPartyAuth.secondaryProviders.length
- );
+ data.thirdPartyAuth.secondaryProviders && data.thirdPartyAuth.secondaryProviders.length
+ );
this.currentProvider = data.thirdPartyAuth.currentProvider || '';
this.errorMessage = data.thirdPartyAuth.errorMessage || '';
this.platformName = data.platformName;
@@ -44,7 +51,6 @@
context: {
fields: fields,
currentProvider: this.currentProvider,
- errorMessage: this.errorMessage,
providers: this.providers,
hasSecondaryProviders: this.hasSecondaryProviders,
platformName: this.platformName
@@ -57,19 +63,23 @@
},
postRender: function() {
+ var formErrorsTitle;
this.$container = $(this.el);
-
this.$form = this.$container.find('form');
- this.$errors = this.$container.find('.submission-error');
- this.$resetSuccess = this.$container.find('.js-reset-success');
- this.$authError = this.$container.find('.already-authenticated-msg');
+ this.$formFeedback = this.$container.find('.js-form-feedback');
this.$submitButton = this.$container.find(this.submitButton);
- /* If we're already authenticated with a third-party
- * provider, try logging in. The easiest way to do this
- * is to simply submit the form.
- */
- if (this.currentProvider) {
+ if (this.errorMessage) {
+ formErrorsTitle = _.sprintf(
+ gettext('An error occurred when signing you in to %s.'),
+ this.platformName
+ );
+ this.renderErrors(formErrorsTitle, [this.errorMessage]);
+ } else if (this.currentProvider) {
+ /* If we're already authenticated with a third-party
+ * provider, try logging in. The easiest way to do this
+ * is to simply submit the form.
+ */
this.model.save();
}
},
@@ -78,37 +88,39 @@
event.preventDefault();
this.trigger('password-help');
- this.element.hide(this.$resetSuccess);
+ this.clearPasswordResetSuccess();
},
postFormSubmission: function() {
- this.element.hide(this.$resetSuccess);
+ this.clearPasswordResetSuccess();
},
resetEmail: function() {
var email = $('#password-reset-email').val(),
- successMessage;
- this.element.hide(this.$errors);
- this.resetMessage = this.$resetSuccess.find('.message-copy');
+ successTitle = gettext('Check Your Email'),
+ successMessageHtml = HtmlUtils.interpolateHtml(
+ gettext('{paragraphStart}You entered {boldStart}{email}{boldEnd}. If this email address is associated with your {platform_name} account, we will send a message with password reset instructions to this email address.{paragraphEnd}' + // eslint-disable-line max-len
+ '{paragraphStart}If you do not receive a password reset message, verify that you entered the correct email address, or check your spam folder.{paragraphEnd}' + // eslint-disable-line max-len
+ '{paragraphStart}If you need further assistance, {anchorStart}contact technical support{anchorEnd}.{paragraphEnd}'), { // eslint-disable-line max-len
+ boldStart: HtmlUtils.HTML(''),
+ boldEnd: HtmlUtils.HTML(''),
+ paragraphStart: HtmlUtils.HTML(''),
+ paragraphEnd: HtmlUtils.HTML('
'),
+ email: email,
+ platform_name: this.platformName,
+ anchorStart: HtmlUtils.HTML(''),
+ anchorEnd: HtmlUtils.HTML('')
+ }
+ );
- successMessage = HtmlUtils.interpolateHtml(
- gettext('{paragraphStart}You entered {boldStart}{email}{boldEnd}. If this email address is associated with your {platform_name} account, we will send a message with password reset instructions to this email address.{paragraphEnd}' + // eslint-disable-line max-len
- '{paragraphStart}If you do not receive a password reset message, verify that you entered the correct email address, or check your spam folder.{paragraphEnd}' + // eslint-disable-line max-len
- '{paragraphStart}If you need further assistance, {anchorStart}contact technical support{anchorEnd}.{paragraphEnd}'), { // eslint-disable-line max-len
- boldStart: HtmlUtils.HTML(''),
- boldEnd: HtmlUtils.HTML(''),
- paragraphStart: HtmlUtils.HTML(''),
- paragraphEnd: HtmlUtils.HTML('
'),
- email: email,
- platform_name: this.platformName,
- anchorStart: HtmlUtils.HTML(''),
- anchorEnd: HtmlUtils.HTML('')
- });
+ this.clearFormErrors();
+ this.clearPasswordResetSuccess();
- if (this.resetMessage.find('p').length === 0) {
- this.resetMessage.append(HtmlUtils.joinHtml(successMessage).toString());
- }
- this.element.show(this.$resetSuccess);
+ this.renderFormFeedback(this.formSuccessTpl, {
+ jsHook: this.passwordResetSuccessJsHook,
+ title: successTitle,
+ messageHtml: successMessageHtml
+ });
},
thirdPartyAuth: function(event) {
@@ -121,7 +133,7 @@
saveSuccess: function() {
this.trigger('auth-complete');
- this.element.hide(this.$resetSuccess);
+ this.clearPasswordResetSuccess();
},
saveError: function(error) {
@@ -132,8 +144,7 @@
msg = gettext('An error has occurred. Try refreshing the page, or check your Internet connection.');
}
this.errors = ['' + msg + ''];
- this.setErrors();
- this.element.hide(this.$resetSuccess);
+ this.clearPasswordResetSuccess();
/* If we've gotten a 403 error, it means that we've successfully
* authenticated with a third-party provider, but we haven't
@@ -144,13 +155,37 @@
if (error.status === 403 &&
error.responseText === 'third-party-auth' &&
this.currentProvider) {
- this.element.show(this.$authError);
- this.element.hide(this.$errors);
+ this.clearFormErrors();
+ this.renderAuthWarning();
} else {
- this.element.hide(this.$authError);
- this.element.show(this.$errors);
+ this.renderErrors(this.defaultFormErrorsTitle, this.errors);
}
this.toggleDisableButton(false);
+ },
+
+ renderAuthWarning: function() {
+ var message = _.sprintf(
+ gettext('You have successfully signed into %(currentProvider)s, but your %(currentProvider)s' +
+ ' account does not have a linked %(platformName)s account. To link your accounts,' +
+ ' sign in now using your %(platformName)s password.'),
+ {currentProvider: this.currentProvider, platformName: this.platformName}
+ );
+
+ this.clearAuthWarning();
+ this.renderFormFeedback(this.formStatusTpl, {
+ jsHook: this.authWarningJsHook,
+ message: message
+ });
+ },
+
+ clearPasswordResetSuccess: function() {
+ var query = '.' + this.passwordResetSuccessJsHook;
+ this.clearFormFeedbackItems(query);
+ },
+
+ clearAuthWarning: function() {
+ var query = '.' + this.authWarningJsHook;
+ this.clearFormFeedbackItems(query);
}
});
});
diff --git a/lms/static/js/student_account/views/PasswordResetView.js b/lms/static/js/student_account/views/PasswordResetView.js
index 32a0ee9956..a9b0baf70d 100644
--- a/lms/static/js/student_account/views/PasswordResetView.js
+++ b/lms/static/js/student_account/views/PasswordResetView.js
@@ -26,15 +26,6 @@
this.listenTo(this.model, 'sync', this.saveSuccess);
},
- toggleErrorMsg: function(show) {
- if (show) {
- this.setErrors();
- this.toggleDisableButton(false);
- } else {
- this.element.hide(this.$errors);
- }
- },
-
saveSuccess: function() {
this.trigger('password-email-sent');
diff --git a/lms/static/js/student_account/views/RegisterView.js b/lms/static/js/student_account/views/RegisterView.js
index 8b03015732..db54ce14f7 100644
--- a/lms/static/js/student_account/views/RegisterView.js
+++ b/lms/static/js/student_account/views/RegisterView.js
@@ -3,9 +3,11 @@
define([
'jquery',
'underscore',
- 'js/student_account/views/FormView'
+ 'gettext',
+ 'js/student_account/views/FormView',
+ 'text!templates/student_account/form_status.underscore'
],
- function($, _, FormView) {
+ function($, _, gettext, FormView, formStatusTpl) {
return FormView.extend({
el: '#register-form',
@@ -18,13 +20,19 @@
formType: 'register',
+ formStatusTpl: formStatusTpl,
+
+ authWarningJsHook: 'js-auth-warning',
+
+ defaultFormErrorsTitle: gettext('We couldn\'t create your account.'),
+
submitButton: '.js-register',
preRender: function(data) {
this.providers = data.thirdPartyAuth.providers || [];
this.hasSecondaryProviders = (
- data.thirdPartyAuth.secondaryProviders && data.thirdPartyAuth.secondaryProviders.length
- );
+ data.thirdPartyAuth.secondaryProviders && data.thirdPartyAuth.secondaryProviders.length
+ );
this.currentProvider = data.thirdPartyAuth.currentProvider || '';
this.errorMessage = data.thirdPartyAuth.errorMessage || '';
this.platformName = data.platformName;
@@ -34,7 +42,8 @@
},
render: function(html) {
- var fields = html || '';
+ var fields = html || '',
+ formErrorsTitle = gettext('An error occurred.');
$(this.el).html(_.template(this.tpl)({
/* We pass the context object to the template so that
@@ -43,7 +52,6 @@
context: {
fields: fields,
currentProvider: this.currentProvider,
- errorMessage: this.errorMessage,
providers: this.providers,
hasSecondaryProviders: this.hasSecondaryProviders,
platformName: this.platformName
@@ -52,6 +60,13 @@
this.postRender();
+ // Must be called after postRender, since postRender sets up $formFeedback.
+ if (this.errorMessage) {
+ this.renderErrors(formErrorsTitle, [this.errorMessage]);
+ } else if (this.currentProvider) {
+ this.renderAuthWarning();
+ }
+
if (this.autoSubmit) {
$(this.el).hide();
$('#register-honor_code').prop('checked', true);
@@ -76,18 +91,18 @@
saveError: function(error) {
$(this.el).show(); // Show in case the form was hidden for auto-submission
this.errors = _.flatten(
- _.map(
- // Something is passing this 'undefined'. Protect against this.
- JSON.parse(error.responseText || '[]'),
- function(error_list) {
- return _.map(
- error_list,
- function(error) { return '' + error.user_message + ''; }
- );
- }
- )
- );
- this.setErrors();
+ _.map(
+ // Something is passing this 'undefined'. Protect against this.
+ JSON.parse(error.responseText || '[]'),
+ function(errorList) {
+ return _.map(
+ errorList,
+ function(errorItem) { return '' + errorItem.user_message + ''; }
+ );
+ }
+ )
+ );
+ this.renderErrors(this.defaultFormErrorsTitle, this.errors);
this.toggleDisableButton(false);
},
@@ -96,6 +111,22 @@
// The form did not get submitted due to validation errors.
$(this.el).show(); // Show in case the form was hidden for auto-submission
}
+ },
+
+ renderAuthWarning: function() {
+ var msgPart1 = gettext('You\'ve successfully signed into %(currentProvider)s.'),
+ msgPart2 = gettext(
+ 'We just need a little more information before you start learning with %(platformName)s.'
+ ),
+ fullMsg = _.sprintf(
+ msgPart1 + ' ' + msgPart2,
+ {currentProvider: this.currentProvider, platformName: this.platformName}
+ );
+
+ this.renderFormFeedback(this.formStatusTpl, {
+ jsHook: this.authWarningJsHook,
+ message: fullMsg
+ });
}
});
});
diff --git a/lms/templates/financial-assistance/financial_assessment_form.underscore b/lms/templates/financial-assistance/financial_assessment_form.underscore
index ee970977bf..859a2fc586 100644
--- a/lms/templates/financial-assistance/financial_assessment_form.underscore
+++ b/lms/templates/financial-assistance/financial_assessment_form.underscore
@@ -7,10 +7,8 @@