Merge pull request #14142 from edx/ECOM-6107-fix-error-handling-for-sr
Improve screen reader support on logistration pages
This commit is contained in:
@@ -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()
|
||||
|
||||
|
||||
@@ -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 = ['<li>' + msg + '</li>'];
|
||||
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('<li>' + msg + '</li>');
|
||||
this.renderErrors(this.defaultFormErrorsTitle, ['<li>' + msg + '</li>']);
|
||||
this.toggleDisableButton(true);
|
||||
$submissionContainer.removeClass('hidden');
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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.'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
|
||||
@@ -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 = ['<li>' + error.responseText + '</li>'];
|
||||
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();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -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('<b>'),
|
||||
boldEnd: HtmlUtils.HTML('</b>'),
|
||||
paragraphStart: HtmlUtils.HTML('<p>'),
|
||||
paragraphEnd: HtmlUtils.HTML('</p>'),
|
||||
email: email,
|
||||
platform_name: this.platformName,
|
||||
anchorStart: HtmlUtils.HTML('<a href="' + this.supportURL + '">'),
|
||||
anchorEnd: HtmlUtils.HTML('</a>')
|
||||
}
|
||||
);
|
||||
|
||||
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('<b>'),
|
||||
boldEnd: HtmlUtils.HTML('</b>'),
|
||||
paragraphStart: HtmlUtils.HTML('<p>'),
|
||||
paragraphEnd: HtmlUtils.HTML('</p>'),
|
||||
email: email,
|
||||
platform_name: this.platformName,
|
||||
anchorStart: HtmlUtils.HTML('<a href="' + this.supportURL + '">'),
|
||||
anchorEnd: HtmlUtils.HTML('</a>')
|
||||
});
|
||||
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 = ['<li>' + msg + '</li>'];
|
||||
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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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 '<li>' + error.user_message + '</li>'; }
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
this.setErrors();
|
||||
_.map(
|
||||
// Something is passing this 'undefined'. Protect against this.
|
||||
JSON.parse(error.responseText || '[]'),
|
||||
function(errorList) {
|
||||
return _.map(
|
||||
errorList,
|
||||
function(errorItem) { return '<li>' + errorItem.user_message + '</li>'; }
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
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
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,10 +7,8 @@
|
||||
</div>
|
||||
|
||||
<form class="financial-assistance-form" method="POST">
|
||||
<div class="status submission-error hidden" aria-live="polite">
|
||||
<h4 class="message-title"><%- gettext('Unable to submit application') %></h4>
|
||||
<ul class="message-copy"></ul>
|
||||
</div>
|
||||
<div class="js-form-feedback" aria-live="assertive" tabindex="-1">
|
||||
</div>
|
||||
|
||||
<div class="user-info">
|
||||
<h2><%- gettext('About You') %></h2>
|
||||
|
||||
6
lms/templates/student_account/form_errors.underscore
Normal file
6
lms/templates/student_account/form_errors.underscore
Normal file
@@ -0,0 +1,6 @@
|
||||
<div class="<%- jsHook %> status submission-error">
|
||||
<h4 class="message-title"><%- title %></h4>
|
||||
<ul class="message-copy">
|
||||
<%= HtmlUtils.ensureHtml(messagesHtml) %>
|
||||
</ul>
|
||||
</div>
|
||||
6
lms/templates/student_account/form_status.underscore
Normal file
6
lms/templates/student_account/form_status.underscore
Normal file
@@ -0,0 +1,6 @@
|
||||
<div class="<%- jsHook %> status">
|
||||
<p class="message-copy">
|
||||
<%- message %>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
6
lms/templates/student_account/form_success.underscore
Normal file
6
lms/templates/student_account/form_success.underscore
Normal file
@@ -0,0 +1,6 @@
|
||||
<div class="<%- jsHook %> status submission-success">
|
||||
<h4 class="message-title"><%- title %></h4>
|
||||
<div class="message-copy">
|
||||
<%= HtmlUtils.ensureHtml(messageHtml) %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,31 +1,6 @@
|
||||
<div class="status already-authenticated-msg hidden">
|
||||
<% if (context.currentProvider) { %>
|
||||
<p class="message-copy">
|
||||
<%- _.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."), context ) %>
|
||||
</p>
|
||||
<% } %>
|
||||
<div class="js-form-feedback" aria-live="assertive" tabindex="-1">
|
||||
</div>
|
||||
|
||||
<div aria-live="polite">
|
||||
<div class="js-reset-success status submission-success hidden">
|
||||
<h4 class="message-title"><%- gettext("Check Your Email") %></h4>
|
||||
<div class="message-copy">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="status submission-error hidden">
|
||||
<h4 class="message-title"><%- gettext("We couldn't sign you in.") %></h4>
|
||||
<ul class="message-copy"></ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if (context.errorMessage) { %>
|
||||
<div class="status submission-error">
|
||||
<h4 class="message-title"><%- _.sprintf( gettext("An error occurred when signing you in to %(platformName)s."), context ) %></h4>
|
||||
<ul class="message-copy"><%- context.errorMessage %></ul>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<form id="login" class="login-form" tabindex="-1" method="POST">
|
||||
|
||||
<div class="section-title lines">
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
<div class="status submission-error hidden" aria-live="polite">
|
||||
<h4 class="message-title"><%- gettext("An error occurred.") %></h4>
|
||||
<ul class="message-copy"></ul>
|
||||
<div class="js-form-feedback" aria-live="assertive" tabindex="-1">
|
||||
</div>
|
||||
|
||||
<form id="password-reset" class="password-reset-form" tabindex="-1" method="POST">
|
||||
|
||||
@@ -1,63 +1,48 @@
|
||||
<div class="status submission-error hidden" aria-live="polite">
|
||||
<h4 class="message-title"><%- gettext("We couldn't create your account.") %></h4>
|
||||
<ul class="message-copy"></ul>
|
||||
<div class="js-form-feedback" aria-live="assertive" tabindex="-1">
|
||||
</div>
|
||||
|
||||
<form id="register" class="register-form" autocomplete="off" tabindex="-1" method="POST">
|
||||
<% if (!context.currentProvider) { %>
|
||||
<% if (context.providers.length > 0 || context.hasSecondaryProviders) { %>
|
||||
<div class="login-providers">
|
||||
<div class="section-title lines">
|
||||
<h2>
|
||||
<span class="text"><%- gettext("Create an account using") %></span>
|
||||
</h2>
|
||||
</div>
|
||||
<%
|
||||
_.each( context.providers, function( provider) {
|
||||
if ( provider.registerUrl ) { %>
|
||||
<button type="button" class="button button-primary button-<%- provider.id %> login-provider register-<%- provider.id %>" data-provider-url="<%- provider.registerUrl %>">
|
||||
<div class="icon <% if ( provider.iconClass ) { %>fa <%- provider.iconClass %><% } %>" aria-hidden="true">
|
||||
<% if ( provider.iconImage ) { %>
|
||||
<img class="icon-image" src="<%- provider.iconImage %>" alt="<%- provider.name %> icon" />
|
||||
<% } %>
|
||||
</div>
|
||||
<span aria-hidden="true"><%- provider.name %></span>
|
||||
<span class="sr"><%- _.sprintf( gettext("Create account using %(providerName)s."), {providerName: provider.name} ) %></span>
|
||||
</button>
|
||||
<% }
|
||||
}); %>
|
||||
|
||||
<% if (context.errorMessage) { %>
|
||||
<div class="status submission-error">
|
||||
<h4 class="message-title"><%- gettext("An error occurred.") %></h4>
|
||||
<ul class="message-copy"><%- context.errorMessage %></ul>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<% if (context.currentProvider) { %>
|
||||
<div class="status" aria-hidden="false">
|
||||
<p class="message-copy">
|
||||
<%- _.sprintf( gettext("You've successfully signed into %(currentProvider)s."), context ) %>
|
||||
<%- _.sprintf( gettext("We just need a little more information before you start learning with %(platformName)s."), context ) %>
|
||||
</p>
|
||||
</div>
|
||||
<% } else if ( context.providers.length > 0 || context.hasSecondaryProviders ) { %>
|
||||
<div class="login-providers">
|
||||
<% if ( context.hasSecondaryProviders ) { %>
|
||||
<button type="button" class="button-secondary-login form-toggle" data-type="institution_login">
|
||||
<%- gettext("Use my institution/campus credentials") %>
|
||||
</button>
|
||||
<% } %>
|
||||
</div>
|
||||
<div class="section-title lines">
|
||||
<h2>
|
||||
<span class="text"><%- gettext("Create an account using") %></span>
|
||||
<span class="text"><%- gettext("or create a new one here") %></span>
|
||||
</h2>
|
||||
</div>
|
||||
<%
|
||||
_.each( context.providers, function( provider) {
|
||||
if ( provider.registerUrl ) { %>
|
||||
<button type="button" class="button button-primary button-<%- provider.id %> login-provider register-<%- provider.id %>" data-provider-url="<%- provider.registerUrl %>">
|
||||
<div class="icon <% if ( provider.iconClass ) { %>fa <%- provider.iconClass %><% } %>" aria-hidden="true">
|
||||
<% if ( provider.iconImage ) { %>
|
||||
<img class="icon-image" src="<%- provider.iconImage %>" alt="<%- provider.name %> icon" />
|
||||
<% } %>
|
||||
</div>
|
||||
<span aria-hidden="true"><%- provider.name %></span>
|
||||
<span class="sr"><%- _.sprintf( gettext("Create account using %(providerName)s."), {providerName: provider.name} ) %></span>
|
||||
</button>
|
||||
<% }
|
||||
}); %>
|
||||
|
||||
<% if ( context.hasSecondaryProviders ) { %>
|
||||
<button type="button" class="button-secondary-login form-toggle" data-type="institution_login">
|
||||
<%- gettext("Use my institution/campus credentials") %>
|
||||
</button>
|
||||
<% } %>
|
||||
</div>
|
||||
<div class="section-title lines">
|
||||
<h2>
|
||||
<span class="text"><%- gettext("or create a new one here") %></span>
|
||||
</h2>
|
||||
</div>
|
||||
<% } else { %>
|
||||
<div class="section-title lines">
|
||||
<h2>
|
||||
<span class="text"><%- gettext("Create a new account") %></span>
|
||||
</h2>
|
||||
</div>
|
||||
<% } else { %>
|
||||
<div class="section-title lines">
|
||||
<h2>
|
||||
<span class="text"><%- gettext("Create a new account") %></span>
|
||||
</h2>
|
||||
</div>
|
||||
<% } %>
|
||||
<% } %>
|
||||
|
||||
<%= context.fields %>
|
||||
|
||||
Reference in New Issue
Block a user