Commit a9a4057d by AlasdairSwan

Merge pull request #5718 from edx/alasdair/logistration-t-and-c

ECOM-466 Added support for custom error messages. Styled select widths
parents 60f02feb fd14406a
......@@ -13,9 +13,8 @@ var edx = edx || {};
email: '<li>A properly formatted e-mail is required</li>',
min: '<li><%= field %> must be a minimum of <%= count %> characters long</li>',
max: '<li><%= field %> must be a maximum of <%= count %> characters long</li>',
password: '<li>A valid password is required</li>',
required: '<li><%= field %> field is required</li>',
terms: '<li>To enroll you must agree to the <a href="#">Terms of Service and Honor Code</a></li>'
custom: '<li><%= content %></li>'
},
field: function( el ) {
......@@ -78,7 +77,7 @@ var edx = edx || {};
},
isBlank: function( $el ) {
return ( $el.attr('type') === 'checkbox' ) ? $el.prop('checked') : !$el.val();
return ( $el.attr('type') === 'checkbox' ) ? !$el.prop('checked') : !$el.val();
},
email: {
......@@ -105,22 +104,33 @@ var edx = edx || {};
var txt = [],
tpl,
name,
obj;
obj,
customMsg;
_.each( tests, function( value, key ) {
if ( !value ) {
name = $el.attr('name');
tpl = _fn.validate.msg[key];
obj = {
field: _fn.validate.str.capitalizeFirstLetter( name )
};
if ( key === 'min' ) {
obj.count = $el.attr('minlength');
} else if ( key === 'max' ) {
obj.count = $el.attr('maxlength');
customMsg = $el.data('errormsg-' + key) || false;
// If the field has a custom error msg attached use it
if ( customMsg ) {
tpl = _fn.validate.msg.custom;
obj = {
content: customMsg
};
} else {
tpl = _fn.validate.msg[key];
obj = {
field: _fn.validate.str.capitalizeFirstLetter( name )
};
if ( key === 'min' ) {
obj.count = $el.attr('minlength');
} else if ( key === 'max' ) {
obj.count = $el.attr('maxlength');
}
}
txt.push( _.template( tpl, obj ) );
......
......@@ -263,13 +263,20 @@
// Student account registration/login
// Loaded explicitly until these are converted to RequireJS
'js/student_account/views/FormView': {
exports: 'js/student_account/views/FormView',
deps: ['jquery', 'underscore', 'backbone', 'gettext']
},
'js/student_account/models/LoginModel': {
exports: 'js/student_account/models/LoginModel',
deps: ['jquery', 'underscore', 'backbone', 'gettext', 'jquery.cookie']
},
'js/student_account/views/LoginView': {
exports: 'js/student_account/views/LoginView',
deps: ['js/student_account/models/LoginModel']
deps: [
'js/student_account/models/LoginModel',
'js/student_account/views/FormView'
]
},
'js/student_account/models/PasswordResetModel': {
exports: 'js/student_account/models/PasswordResetModel',
......@@ -277,7 +284,10 @@
},
'js/student_account/views/PasswordResetView': {
exports: 'js/student_account/views/PasswordResetView',
deps: ['js/student_account/models/PasswordResetModel']
deps: [
'js/student_account/models/PasswordResetModel',
'js/student_account/views/FormView'
]
},
'js/student_account/models/RegisterModel': {
exports: 'js/student_account/models/RegisterModel',
......@@ -285,7 +295,10 @@
},
'js/student_account/views/RegisterView': {
exports: 'js/student_account/views/RegisterView',
deps: ['js/student_account/models/RegisterModel']
deps: [
'js/student_account/models/RegisterModel',
'js/student_account/views/FormView'
]
},
'js/student_account/views/AccessView': {
exports: 'js/student_account/views/AccessView',
......
......@@ -4,11 +4,21 @@ define(['js/common_helpers/template_helpers', 'js/student_account/views/Password
'use strict';
var view = null,
ajaxSuccess = true;
ajaxSuccess = true,
model = new edx.student.account.PasswordResetModel(),
data = [{
label: 'E-mail',
instructions: 'This is the e-mail address you used to register with edX',
name: 'email',
required: true,
type: 'email',
restrictions: [],
defaultValue: ''
}];
var submitEmail = function(validationSuccess) {
// Simulate manual entry of an email address
$('#reset-password-email').val('foo@bar.baz');
$('#password-reset-email').val('foo@bar.baz');
// Create a fake click event
var clickEvent = $.Event('click');
......@@ -16,7 +26,10 @@ define(['js/common_helpers/template_helpers', 'js/student_account/views/Password
// Used to avoid spying on view.validate twice
if (typeof validationSuccess !== 'undefined') {
// Force validation to return as expected
spyOn(view, 'validate').andReturn(validationSuccess);
spyOn(view, 'validate').andReturn({
isValid: validationSuccess,
message: "We're all good."
});
}
// Submit the email address
......@@ -43,12 +56,15 @@ define(['js/common_helpers/template_helpers', 'js/student_account/views/Password
if (ajaxSuccess) {
defer.resolve();
} else {
defer.reject();
defer.rejectWith(this, ["The server could not be contacted."]);
}
}).promise();
});
view = new edx.student.account.PasswordResetView();
view = new edx.student.account.PasswordResetView({
fields: data,
model: model
});
});
it("allows the user to request a new password", function() {
......@@ -72,13 +88,13 @@ define(['js/common_helpers/template_helpers', 'js/student_account/views/Password
// If we get an error status on the AJAX request, display an error
ajaxSuccess = false;
submitEmail(true);
expect(view.$resetFail).not.toHaveClass('hidden');
expect(view.$'#submission-error').not.toHaveClass('hidden');
// If we try again and succeed, the error should go away
ajaxSuccess = true;
// No argument means we won't spy on view.validate again
submitEmail();
expect(view.$resetFail).toHaveClass('hidden');
expect(view.$'#submission-error').toHaveClass('hidden');
});
});
}
......
......@@ -29,8 +29,8 @@ var edx = edx || {};
.done(function() {
model.trigger('success');
})
.fail( function() {
model.trigger('error');
.fail( function( error ) {
model.trigger( 'error', error );
});
}
});
......
......@@ -126,8 +126,8 @@ var edx = edx || {};
},
resetPassword: function() {
this.$header.addClass('hidden');
$(this.el).find('.form-type').addClass('hidden');
this.element.hide( this.$header );
this.element.hide( $(this.el).find('.form-type') );
this.loadForm('reset');
},
......@@ -139,14 +139,29 @@ var edx = edx || {};
this.loadForm( type );
}
$(this.el).find('.form-wrapper').addClass('hidden');
$form.removeClass('hidden');
this.element.hide( $(this.el).find('.form-wrapper') );
this.element.show( $form );
},
form: {
isLoaded: function( $form ) {
return $form.html().length > 0;
}
},
/* Helper method ot toggle display
* including accessibility considerations
*/
element: {
hide: function( $el ) {
$el.addClass('hidden')
.attr('aria-hidden', true);
},
show: function( $el ) {
$el.removeClass('hidden')
.attr('aria-hidden', false);
}
}
});
......
......@@ -29,16 +29,25 @@ var edx = edx || {};
requiredStr: '*',
initialize: function( data ) {
this.preRender( data );
this.tpl = $(this.tpl).html();
this.fieldTpl = $(this.fieldTpl).html();
this.buildForm( data.fields );
this.model = data.model;
this.listenTo( this.model, 'error', this.modelError );
this.listenTo( this.model, 'error', this.saveError );
},
/* Allows extended views to add custom
* init steps without needing to repeat
* default init steps
*/
preRender: function( data ) {
/* custom code goes here */
return data;
},
// Renders the form.
render: function( html ) {
var fields = html || '';
......@@ -76,6 +85,31 @@ var edx = edx || {};
this.render( html.join('') );
},
/* Helper method ot toggle display
* including accessibility considerations
*/
element: {
hide: function( $el ) {
if ( $el ) {
$el.addClass('hidden')
.attr('aria-hidden', true);
}
},
show: function( $el ) {
if ( $el ) {
$el.removeClass('hidden')
.attr('aria-hidden', false);
}
}
},
forgotPassword: function( event ) {
event.preventDefault();
this.trigger('password-help');
},
getFormData: function() {
var obj = {},
......@@ -116,10 +150,25 @@ var edx = edx || {};
return obj;
},
forgotPassword: function( event ) {
event.preventDefault();
saveError: function( error ) {
this.errors = ['<li>' + error.responseText + '</li>'];
this.setErrors();
},
this.trigger('password-help');
setErrors: function() {
var $msg = this.$errors.find('.message-copy'),
html = [],
errors = this.errors,
i,
len = errors.length;
for ( i=0; i<len; i++ ) {
html.push( errors[i] );
}
$msg.html( html.join('') );
this.element.show( this.$errors );
},
submitForm: function( event ) {
......@@ -140,34 +189,9 @@ var edx = edx || {};
if ( show ) {
this.setErrors();
} else {
this.$errors
.addClass('hidden')
.attr('aria-hidden', true);
}
},
setErrors: function() {
var $msg = this.$errors.find('.message-copy'),
html = [],
errors = this.errors,
i,
len = errors.length;
for ( i=0; i<len; i++ ) {
html.push( errors[i] );
this.element.hide( this.$errors );
}
$msg.html( html.join('') );
this.$errors
.removeClass('hidden')
.attr('aria-hidden', false);
},
modelError: function( error ) {
console.log('error ', error);
},
validate: function( $el, form ) {
return edx.utils.validate( $el, form );
}
......
var edx = edx || {};
(function($, _, Backbone, gettext) {
(function($, _, gettext) {
'use strict';
edx.student = edx.student || {};
......@@ -21,17 +21,9 @@ var edx = edx || {};
requiredStr: '',
initialize: function( data ) {
this.tpl = $(this.tpl).html();
this.fieldTpl = $(this.fieldTpl).html();
this.buildForm( data.fields );
this.model = data.model;
preRender: function( data ) {
this.providers = data.thirdPartyAuth.providers || [];
this.currentProvider = data.thirdPartyAuth.currentProvider || '';
this.listenTo( this.model, 'error', this.saveError );
},
render: function( html ) {
......@@ -49,11 +41,11 @@ var edx = edx || {};
},
postRender: function() {
var $container = $(this.el);
this.$container = $(this.el);
this.$form = $container.find('form');
this.$errors = $container.find('.submission-error');
this.$alreadyAuthenticatedMsg = $container.find('.already-authenticated-msg');
this.$form = this.$container.find('form');
this.$errors = this.$container.find('.submission-error');
this.$authError = this.$container.find('.already-authenticated-msg');
/* If we're already authenticated with a third-party
* provider, try logging in. The easiest way to do this
......@@ -72,6 +64,7 @@ var edx = edx || {};
thirdPartyAuth: function( event ) {
var providerUrl = $(event.target).data('provider-url') || '';
if (providerUrl) {
window.location.href = providerUrl;
} else {
......@@ -81,23 +74,25 @@ var edx = edx || {};
},
saveError: function( error ) {
console.log(error.status, ' error: ', error.responseText);
this.errors = ['<li>' + error.responseText + '</li>'];
this.setErrors();
/* If we've gotten a 403 error, it means that we've successfully
* authenticated with a third-party provider, but we haven't
* linked the account to an EdX account. In this case,
* we need to prompt the user to enter a little more information
* to complete the registration process.
*/
if (error.status === 403 && error.responseText === "third-party-auth" && this.currentProvider) {
this.$alreadyAuthenticatedMsg.removeClass("hidden");
}
else {
this.$alreadyAuthenticatedMsg.addClass("hidden");
// TODO -- display the error
*/
if ( error.status === 403 &&
error.responseText === 'third-party-auth' &&
this.currentProvider ) {
this.element.show( this.$authError );
this.element.hide( this.$errors );
} else {
this.element.hide( this.$authError );
this.element.show( this.$errors );
}
}
});
})(jQuery, _, Backbone, gettext);
\ No newline at end of file
})(jQuery, _, gettext);
var edx = edx || {};
(function($, _, Backbone, gettext) {
(function($, _, gettext) {
'use strict';
edx.student = edx.student || {};
......@@ -24,34 +24,25 @@ var edx = edx || {};
this.$form = $container.find('form');
this.$resetFail = $container.find('.js-reset-fail');
this.$errors = $container.find('.submission-error');
this.listenTo( this.model, 'success', this.resetComplete );
this.listenTo( this.model, 'error', this.resetError );
this.listenTo( this.model, 'error', this.saveError );
},
toggleErrorMsg: function( show ) {
if ( show ) {
this.setErrors();
} else {
this.$errors
.addClass('hidden')
.attr('aria-hidden', true);
this.element.hide( this.$errors );
}
},
resetComplete: function() {
var $el = $(this.el);
$el.find('#password-reset-form').addClass('hidden');
$el.find('.js-reset-success').removeClass('hidden');
this.$resetFail.addClass('hidden');
},
resetError: function() {
this.$resetFail.removeClass('hidden');
this.element.hide( $el.find('#password-reset-form') );
this.element.show( $el.find('.js-reset-success') );
},
submitForm: function( event ) {
......@@ -73,4 +64,4 @@ var edx = edx || {};
}
});
})(jQuery, _, Backbone, gettext);
})(jQuery, _, gettext);
var edx = edx || {};
(function($, _, Backbone, gettext) {
(function($, _, gettext) {
'use strict';
edx.student = edx.student || {};
......@@ -18,13 +18,7 @@ var edx = edx || {};
formType: 'register',
initialize: function( data ) {
this.tpl = $(this.tpl).html();
this.fieldTpl = $(this.fieldTpl).html();
this.buildForm( data.fields );
this.model = data.model;
preRender: function( data ) {
this.providers = data.thirdPartyAuth.providers || [];
this.currentProvider = data.thirdPartyAuth.currentProvider || '';
},
......@@ -45,6 +39,7 @@ var edx = edx || {};
thirdPartyAuth: function( event ) {
var providerUrl = $(event.target).data('provider-url') || '';
if (providerUrl) {
window.location.href = providerUrl;
} else {
......@@ -54,4 +49,4 @@ var edx = edx || {};
}
});
})(jQuery, _, Backbone, gettext);
\ No newline at end of file
})(jQuery, _, gettext);
......@@ -119,6 +119,8 @@
}
.form-field {
@include clearfix;
clear: both;
width: 100%;
margin: 0 0 $baseline 0;
......@@ -218,6 +220,45 @@
text-transform: none;
}
.login-provider {
@extend %btn-secondary-blue-outline;
width: 100%;
margin-top: 20px;
.icon {
color: inherit;
margin-right: $baseline/2;
}
&.button-Google:hover, &.button-Google:focus {
background-color: #dd4b39;
border: 1px solid #A5382B;
}
&.button-Google:hover {
box-shadow: 0 2px 1px 0 #8D3024;
}
&.button-Facebook:hover, &.button-Facebook:focus {
background-color: #3b5998;
border: 1px solid #263A62;
}
&.button-Facebook:hover {
box-shadow: 0 2px 1px 0 #30487C;
}
&.button-LinkedIn:hover , &.button-LinkedIn:focus {
background-color: #0077b5;
border: 1px solid #06527D;
}
&.button-LinkedIn:hover {
box-shadow: 0 2px 1px 0 #005D8E;
}
}
/** Error Container - from _account.scss **/
.status {
@include box-sizing(border-box);
......@@ -277,6 +318,12 @@
@include media( $tablet ) {
$grid-columns: 8;
%inline-form-field-tablet {
clear: none;
display: inline-block;
float: left;
}
.headline,
.tagline,
.form-type {
......@@ -287,11 +334,30 @@
.form-toggle {
margin-right: 5px;
}
.form-field {
&.select-gender {
@extend %inline-form-field-tablet;
width: calc( 50% - 10px );
margin-right: 20px;
}
&.select-year_of_birth {
@extend %inline-form-field-tablet;
width: calc( 50% - 10px );
}
}
}
@include media( $desktop ) {
$grid-columns: 12;
%inline-form-field-desktop {
clear: none;
display: inline-block;
float: left;
}
.headline,
.tagline,
.form-type {
......@@ -302,5 +368,38 @@
.form-toggle {
margin-right: 10px;
}
.form-field {
&.select-level_of_education {
@extend %inline-form-field-desktop;
width: 290px;
margin-right: 20px;
}
&.select-gender {
@extend %inline-form-field-desktop;
width: 60px;
margin-right: 20px;
}
&.select-year_of_birth {
@extend %inline-form-field-desktop;
width: 100px;
}
}
// TODO: Update so actually using the grid
.login-provider {
@include span-columns(6);
/*width: calc( 50% - 12px );
&:nth-child(odd) {
margin-left: 10px;
}
&:nth-child(even) {
margin-right: 10px;
}*/
}
}
}
......@@ -8,7 +8,7 @@
<input type="radio" name="form" id="register-option" value="register" class="form-toggle" <% if ( mode === 'register' ) { %>checked<% } %> >
<label for"register-option" class="form-label">I am a new user</label>
</h2>
<div id="register-form" class="form-wrapper <% if ( mode !== 'register' ) { %>hidden<% } %>"></div>
<div id="register-form" class="form-wrapper <% if ( mode !== 'register' ) { %>hidden" aria-hidden="true<% } %>"></div>
</section>
<section class="form-type">
......@@ -16,7 +16,7 @@
<input type="radio" name="form" id="login-option" value="login" class="form-toggle" <% if ( mode === 'login' ) { %>checked<% } %>>
<label for="login-option" class="form-label">I am a returning user with an edX account</label>
</h2>
<div id="login-form" class="form-wrapper <% if ( mode !== 'login' ) { %>hidden<% } %>"></div>
<div id="login-form" class="form-wrapper <% if ( mode !== 'login' ) { %>hidden" aria-hidden="true<% } %>"></div>
</section>
<div id="password-reset-wrapper"></div>
<p class="form-field">
<p class="form-field <%=type%>-<%= name %>">
<% if ( type !== 'checkbox' ) { %>
<label for="<%= form %>-<%= name %>">
<%= label %>
......@@ -11,22 +11,39 @@
<% } %>
<% if ( type === 'select' ) { %>
<select id="<%= form %>-<%= name %>" name="<%= name %>" class="input-inline" aria-describedby="<%= form %>-<%= name %>-desc">
<select id="<%= form %>-<%= name %>"
name="<%= name %>"
class="input-inline"
aria-describedby="<%= form %>-<%= name %>-desc">
<% _.each(options, function(el) { %>
<option value="<%= el.value%>"><%= el.name %></option>
<% }); %>
</select>
<% } else if ( type === 'textarea' ) { %>
<textarea id="<%= form %>-<%= name %>" type="<%= type %>" name="<%= name %>" class="input-block" aria-describedby="<%= form %>-<%= name %>-desc"
<textarea id="<%= form %>-<%= name %>"
type="<%= type %>"
name="<%= name %>"
class="input-block"
aria-describedby="<%= form %>-<%= name %>-desc"
<% if ( restrictions.min_length ) { %> minlength="<%= restrictions.min_length %>"<% } %>
<% if ( restrictions.max_length ) { %> maxlength="<%= restrictions.max_length %>"<% } %>
<% if ( typeof errorMessages !== 'undefined' ) { _.each(errorMessages, function(msg, type) {%>
data-errormsg-<%= type %>="<%= msg %>"
<% }); } %>
<% if ( required ) { %> required<% } %> >
</textarea>
<% } else { %>
<input id="<%= form %>-<%= name %>" type="<%= type %>" name="<%= name %>" class="input-block <% if ( type === 'checkbox' ) { %>checkbox<% } %>" aria-describedby="<%= form %>-<%= name %>-desc"
<input id="<%= form %>-<%= name %>"
type="<%= type %>"
name="<%= name %>"
class="input-block <% if ( type === 'checkbox' ) { %>checkbox<% } %>"
aria-describedby="<%= form %>-<%= name %>-desc"
<% if ( restrictions.min_length ) { %> minlength="<%= restrictions.min_length %>"<% } %>
<% if ( restrictions.max_length ) { %> maxlength="<%= restrictions.max_length %>"<% } %>
<% if ( required ) { %> required<% } %>
<% if ( typeof errorMessages !== 'undefined' ) { _.each(errorMessages, function(msg, type) {%>
data-errormsg-<%= type %>="<%= msg %>"
<% }); } %>
value="<%- defaultValue %>"
/>
<% } %>
......
<form id="login">
<div class="status already-authenticated-msg hidden" aria-hidden="true">
<% if (currentProvider) { %>
<p class="message-copy">You've successfully logged into <%- currentProvider %>, but you need to link your account. Please click "I am a returning user" to create an EdX account.</p>
<% } %>
</div>
<div class="status submission-error hidden" aria-hidden="true">
<h4 class="message-title">We couldn't log you in.</h4>
<ul class="message-copy"></ul>
</div>
<div class="already-authenticated-msg hidden">
<% if (currentProvider) { %>
<p class="instructions">You've successfully logged into <%- currentProvider %>, but you need to link your account. Please click "I am a returning user" to create an EdX account.</p>
<% } %>
</div>
<%= fields %>
<button class="action action-primary action-update js-login">Log in</button>
</form>
......
......@@ -23,7 +23,4 @@
<p>We've e-mailed you instructions for setting your password to the e-mail address you submitted. You should be receiving it shortly.</p>
</div>
</div>
<div class="js-reset-fail hidden">
<h2>Password Reset Failed</h2>
</div>
</section>
<% if (currentProvider) { %>
<p class="instructions">
You've successfully signed in with <strong><%- currentProvider %></strong>.<br />
We just need a little more information before you start learning with edX.
</p>
<div class="status" aria-hidden="false">
<h4 class="message-title">You've successfully signed in with <strong><%- currentProvider %></strong>.</h4>
<p class="message-copy">You've successfully signed in with <strong><%- currentProvider %></strong>. We just need a little more information before you start learning with edX.</p>
</div>
<% } else {
_.each( providers, function( provider) { %>
<button type="submit"class="button button-primary button-<%- provider.name %> login-provider" data-provider-url="<%- provider.registerUrl %>">
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment