diff --git a/common/static/js/spec_helpers/edx.utils.validate.js b/common/static/js/spec_helpers/edx.utils.validate.js index cf41b58568..67cab08d37 100644 --- a/common/static/js/spec_helpers/edx.utils.validate.js +++ b/common/static/js/spec_helpers/edx.utils.validate.js @@ -13,9 +13,8 @@ var edx = edx || {}; email: '
  • A properly formatted e-mail is required
  • ', min: '
  • <%= field %> must be a minimum of <%= count %> characters long
  • ', max: '
  • <%= field %> must be a maximum of <%= count %> characters long
  • ', - password: '
  • A valid password is required
  • ', required: '
  • <%= field %> field is required
  • ', - terms: '
  • To enroll you must agree to the Terms of Service and Honor Code
  • ' + custom: '
  • <%= content %>
  • ' }, 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'); + customMsg = $el.data('errormsg-' + key) || false; - tpl = _fn.validate.msg[key]; + // If the field has a custom error msg attached use it + if ( customMsg ) { + tpl = _fn.validate.msg.custom; - obj = { - field: _fn.validate.str.capitalizeFirstLetter( name ) - }; + obj = { + content: customMsg + }; + } else { + tpl = _fn.validate.msg[key]; - if ( key === 'min' ) { - obj.count = $el.attr('minlength'); - } else if ( key === 'max' ) { - obj.count = $el.attr('maxlength'); + 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 ) ); diff --git a/lms/static/js/spec/main.js b/lms/static/js/spec/main.js index f5ecdb181a..acdb4b9565 100644 --- a/lms/static/js/spec/main.js +++ b/lms/static/js/spec/main.js @@ -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', 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 9b2662a58d..ce8dbe69b5 100644 --- a/lms/static/js/spec/student_account/password_reset_spec.js +++ b/lms/static/js/spec/student_account/password_reset_spec.js @@ -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'); }); }); } diff --git a/lms/static/js/student_account/models/PasswordResetModel.js b/lms/static/js/student_account/models/PasswordResetModel.js index 00958667b2..bfc1997fa9 100644 --- a/lms/static/js/student_account/models/PasswordResetModel.js +++ b/lms/static/js/student_account/models/PasswordResetModel.js @@ -29,8 +29,8 @@ var edx = edx || {}; .done(function() { model.trigger('success'); }) - .fail( function() { - model.trigger('error'); + .fail( function( error ) { + model.trigger( 'error', error ); }); } }); diff --git a/lms/static/js/student_account/views/AccessView.js b/lms/static/js/student_account/views/AccessView.js index dc2a45953f..f29450b7d0 100644 --- a/lms/static/js/student_account/views/AccessView.js +++ b/lms/static/js/student_account/views/AccessView.js @@ -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); + } } }); diff --git a/lms/static/js/student_account/views/FormView.js b/lms/static/js/student_account/views/FormView.js index 18460a9626..1d00f59a70 100644 --- a/lms/static/js/student_account/views/FormView.js +++ b/lms/static/js/student_account/views/FormView.js @@ -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 = ['
  • ' + error.responseText + '
  • ']; + 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' + error.responseText + '']; + 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"); + */ + 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 ); } - else { - this.$alreadyAuthenticatedMsg.addClass("hidden"); - // TODO -- display the error - } - } }); -})(jQuery, _, Backbone, gettext); \ No newline at end of file +})(jQuery, _, gettext); diff --git a/lms/static/js/student_account/views/PasswordResetView.js b/lms/static/js/student_account/views/PasswordResetView.js index 48b4480998..3474e7e3c5 100644 --- a/lms/static/js/student_account/views/PasswordResetView.js +++ b/lms/static/js/student_account/views/PasswordResetView.js @@ -1,6 +1,6 @@ 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); diff --git a/lms/static/js/student_account/views/RegisterView.js b/lms/static/js/student_account/views/RegisterView.js index 697c19cf7c..c455741b19 100644 --- a/lms/static/js/student_account/views/RegisterView.js +++ b/lms/static/js/student_account/views/RegisterView.js @@ -1,6 +1,6 @@ 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); diff --git a/lms/static/sass/views/_login-register.scss b/lms/static/sass/views/_login-register.scss index cda41059fa..d6f8bbe0c9 100644 --- a/lms/static/sass/views/_login-register.scss +++ b/lms/static/sass/views/_login-register.scss @@ -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; + }*/ + } } } diff --git a/lms/templates/student_account/access.underscore b/lms/templates/student_account/access.underscore index 6106cbc857..a920aaed56 100644 --- a/lms/templates/student_account/access.underscore +++ b/lms/templates/student_account/access.underscore @@ -8,7 +8,7 @@ checked<% } %> > -
    +
    @@ -16,7 +16,7 @@ checked<% } %>> -
    +
    diff --git a/lms/templates/student_account/form_field.underscore b/lms/templates/student_account/form_field.underscore index e6db67513d..b994e527b2 100644 --- a/lms/templates/student_account/form_field.underscore +++ b/lms/templates/student_account/form_field.underscore @@ -1,4 +1,4 @@ -

    +

    <% if ( type !== 'checkbox' ) { %>

    + + - + <%= fields %>
    diff --git a/lms/templates/student_account/password_reset.underscore b/lms/templates/student_account/password_reset.underscore index 109a943fdd..375e396c6b 100644 --- a/lms/templates/student_account/password_reset.underscore +++ b/lms/templates/student_account/password_reset.underscore @@ -23,7 +23,4 @@

    We've e-mailed you instructions for setting your password to the e-mail address you submitted. You should be receiving it shortly.

    - diff --git a/lms/templates/student_account/register.underscore b/lms/templates/student_account/register.underscore index 198f4c8acf..e98d0f5147 100644 --- a/lms/templates/student_account/register.underscore +++ b/lms/templates/student_account/register.underscore @@ -1,8 +1,8 @@ <% if (currentProvider) { %> -

    - You've successfully signed in with <%- currentProvider %>.
    - We just need a little more information before you start learning with edX. -

    +
    +

    You've successfully signed in with <%- currentProvider %>.

    +

    You've successfully signed in with <%- currentProvider %>. We just need a little more information before you start learning with edX.

    +
    <% } else { _.each( providers, function( provider) { %>