From 56ba7850a0c7ed8f46c0253a4e8710eaf9b8e576 Mon Sep 17 00:00:00 2001 From: alasdairswan Date: Tue, 1 Dec 2015 14:50:32 -0500 Subject: [PATCH] Integrated JS with Peter's Django wrapper Rendering form. Updated form field template to accept instructions for all input types Added setExtraData function to FormView to allow non-form attributes to be added to the model before saving --- lms/djangoapps/courseware/tests/test_views.py | 12 +- lms/djangoapps/courseware/views.py | 6 +- .../financial_assistance_form_factory.js | 17 ++ .../models/financial_assistance_model.js | 14 ++ .../financial_assessment_form.underscore | 48 ++++ .../financial_assessment_submitted.underscore | 8 + .../views/financial_assistance_form_view.js | 107 +++++++++ .../financial_assistance_form_view_spec.js | 59 +++++ .../js/student_account/views/FormView.js | 8 + .../sass/views/_financial-assistance.scss | 211 ++++++++++++++++-- .../student_account/form_field.underscore | 15 +- 11 files changed, 467 insertions(+), 38 deletions(-) create mode 100644 lms/static/js/financial-assistance/financial_assistance_form_factory.js create mode 100644 lms/static/js/financial-assistance/models/financial_assistance_model.js create mode 100644 lms/static/js/financial-assistance/templates/financial_assessment_form.underscore create mode 100644 lms/static/js/financial-assistance/templates/financial_assessment_submitted.underscore create mode 100644 lms/static/js/financial-assistance/views/financial_assistance_form_view.js create mode 100644 lms/static/js/spec/financial-assistance/financial_assistance_form_view_spec.js diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index 6f671f8f51..f2cfe41c25 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -522,15 +522,15 @@ class ViewsTestCase(ModuleStoreTestCase): effort = "I'm done, okay? You just give me my money, and you and I, we're done." data = { 'username': username, - 'course_id': course, - 'legal_name': legal_name, + 'course': course, + 'name': legal_name, 'email': self.user.email, 'country': country, 'income': income, 'reason_for_applying': reason_for_applying, 'goals': goals, 'effort': effort, - 'marketing_permission': False, + 'mktg-permission': False, } response = self._submit_financial_assistance_form(data) self.assertEqual(response.status_code, 204) @@ -560,15 +560,15 @@ class ViewsTestCase(ModuleStoreTestCase): def test_zendesk_submission_failed(self, _mock_record_feedback): response = self._submit_financial_assistance_form({ 'username': self.user.username, - 'course_id': '', - 'legal_name': '', + 'course': '', + 'name': '', 'email': '', 'country': '', 'income': '', 'reason_for_applying': '', 'goals': '', 'effort': '', - 'marketing_permission': False, + 'mktg-permission': False, }) self.assertEqual(response.status_code, 500) diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py index 6c2ecf1ce3..72f15070c5 100644 --- a/lms/djangoapps/courseware/views.py +++ b/lms/djangoapps/courseware/views.py @@ -1460,15 +1460,15 @@ def financial_assistance_request(request): if request.user.username != username: return HttpResponseForbidden() - course_id = data['course_id'] - legal_name = data['legal_name'] + course_id = data['course'] + legal_name = data['name'] email = data['email'] country = data['country'] income = data['income'] reason_for_applying = data['reason_for_applying'] goals = data['goals'] effort = data['effort'] - marketing_permission = data['marketing_permission'] + marketing_permission = data['mktg-permission'] ip_address = get_ip(request) except ValueError: # Thrown if JSON parsing fails diff --git a/lms/static/js/financial-assistance/financial_assistance_form_factory.js b/lms/static/js/financial-assistance/financial_assistance_form_factory.js new file mode 100644 index 0000000000..0cc77a7617 --- /dev/null +++ b/lms/static/js/financial-assistance/financial_assistance_form_factory.js @@ -0,0 +1,17 @@ +;(function (define) { + 'use strict'; + + define([ + 'js/financial-assistance/views/financial_assistance_form_view' + ], + function (FinancialAssistanceFormView) { + return function (options) { + var formView = new FinancialAssistanceFormView({ + el: '.financial-assistance-wrapper', + context: options + }); + + return formView; + }; + }); +}).call(this, define || RequireJS.define); diff --git a/lms/static/js/financial-assistance/models/financial_assistance_model.js b/lms/static/js/financial-assistance/models/financial_assistance_model.js new file mode 100644 index 0000000000..3039059158 --- /dev/null +++ b/lms/static/js/financial-assistance/models/financial_assistance_model.js @@ -0,0 +1,14 @@ +/** + * Model for Financial Assistance. + */ +(function (define) { + 'use strict'; + define(['backbone'], function (Backbone) { + var FinancialAssistance = Backbone.Model.extend({ + initialize: function(options) { + this.url = options.url; + } + }); + return FinancialAssistance; + }); +}).call(this, define || RequireJS.define); diff --git a/lms/static/js/financial-assistance/templates/financial_assessment_form.underscore b/lms/static/js/financial-assistance/templates/financial_assessment_form.underscore new file mode 100644 index 0000000000..7efe95c9c0 --- /dev/null +++ b/lms/static/js/financial-assistance/templates/financial_assessment_form.underscore @@ -0,0 +1,48 @@ +

<%- gettext('Financial Assistance Application') %>

+ +
+ <% _.each(header_text, function(copy) { %> +

<%- copy %>

+ <% }); %> +
+ +
+ + + + + <%= fields %> + + +
diff --git a/lms/static/js/financial-assistance/templates/financial_assessment_submitted.underscore b/lms/static/js/financial-assistance/templates/financial_assessment_submitted.underscore new file mode 100644 index 0000000000..88897f0cfb --- /dev/null +++ b/lms/static/js/financial-assistance/templates/financial_assessment_submitted.underscore @@ -0,0 +1,8 @@ +

<%- gettext('Financial Assistance Application') %>

+

<%- interpolate_text( + gettext('Thank you for submitting your financial assistance application for {course_name}! You can expect a response in 2-4 business days.'), {course_name: course} + ) %> +

+
+ <%- gettext('Go to Dashboard') %> +
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 new file mode 100644 index 0000000000..1cecdb4db7 --- /dev/null +++ b/lms/static/js/financial-assistance/views/financial_assistance_form_view.js @@ -0,0 +1,107 @@ +;(function (define) { + 'use strict'; + + define(['backbone', + 'jquery', + 'underscore', + 'gettext', + 'js/financial-assistance/models/financial_assistance_model', + 'text!js/financial-assistance/templates/financial_assessment_form.underscore', + 'text!js/financial-assistance/templates/financial_assessment_submitted.underscore', + 'js/student_account/views/FormView', + 'text!templates/student_account/form_field.underscore' + ], + function(Backbone, $, _, gettext, FinancialAssistanceModel, formViewTpl, successTpl, FormView, formFieldTpl) { + return FormView.extend({ + el: '.financial-assistance-wrapper', + events: { + 'click .js-submit-form': 'submitForm' + }, + tpl: formViewTpl, + fieldTpl: formFieldTpl, + formType: 'financial-assistance', + requiredStr: '', + submitButton: '.js-submit-form', + + initialize: function(data) { + var context = data.context, + fields = context.fields; + + // Add default option to array + if ( fields[0].options.length > 1 ) { + fields[0].options.unshift({ + name: '- ' + gettext('Choose one') + ' -', + value: '', + default: true + }); + } + + // Set non-form data needed to render the View + this.context = { + dashboard_url: context.dashboard_url, + header_text: context.header_text, + platform_name: context.platform_name, + student_faq_url: context.student_faq_url + }; + + // Make the value accessible to this View + this.user_details = context.user_details; + + // Initialize the model and set user details + this.model = new FinancialAssistanceModel({ + url: context.submit_url + }); + this.model.set( context.user_details ); + this.listenTo( this.model, 'error', this.saveError ); + this.model.on('sync', this.renderSuccess, this); + + // Build the form + this.buildForm( fields ); + }, + + render: function(html) { + var data = _.extend( this.model.toJSON(), this.context, { + fields: html || '', + }); + + this.$el.html(_.template(this.tpl, data)); + + this.postRender(); + + return this; + }, + + renderSuccess: function() { + this.$el.html(_.template(successTpl, { + course: this.model.get('course'), + dashboard_url: this.context.dashboard_url + })); + + $('.js-success-message').focus(); + }, + + saveError: function(error) { + /*jslint maxlen: 500 */ + var txt = [ + 'An error has occurred. Wait a few minutes and then try to submit the application again.', + 'If you continue to have issues please contact support.' + ], + msg = gettext(txt.join(' ')); + + if (error.status === 0) { + msg = gettext('An error has occurred. Check your Internet connection and try again.'); + } + + this.errors = ['
  • ' + msg + '
  • ']; + this.setErrors(); + this.element.hide( this.$resetSuccess ); + this.toggleDisableButton(false); + }, + + setExtraData: function(data) { + return _.extend(data, this.user_details); + } + }); + } + ); +}).call(this, define || RequireJS.define); 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 new file mode 100644 index 0000000000..9ec688e934 --- /dev/null +++ b/lms/static/js/spec/financial-assistance/financial_assistance_form_view_spec.js @@ -0,0 +1,59 @@ +define([ + 'backbone', + 'jquery', + 'js/financial-assistance/views/financial_assistance_form_view' + ], function (Backbone, $, FinancialAssistanceFormView) { + + 'use strict'; + + describe('Financial Assistance View', function () { + var view = null, + context = { + fields: [{ + defaultValue: '', + form: 'financial-assistance', + instructions: 'select a course', + label: 'Course', + name: 'course', + options: [ + {'name': 'Verified with Audit', 'value': 'course-v1:HCFA+VA101+2015'}, + {'name': 'Something Else', 'value': 'course-v1:SomethingX+SE101+215'}, + {'name': 'Test Course', 'value': 'course-v1:TestX+T101+2015'} + ], + placeholder: '', + required: true, + requiredStr: '', + type: 'select' + }], + user_details: { + country: 'UK', + email: 'xsy@edx.org', + name: 'xsy', + username: 'xsy4ever' + }, + header_text: ['Line one.', 'Line two.'], + student_faq_url: '/faqs', + dashboard_url: '/dashboard', + platform_name: 'edx', + submit_url: '/api/financial/v1/assistance' + }; + + beforeEach(function() { + setFixtures('
    '); + view = new FinancialAssistanceFormView({ + el: '.financial-assistance-wrapper', + context: context + }); + }); + + afterEach(function() { + view.undelegateEvents(); + view.remove(); + }); + + it('should exist', function() { + expect(view).toBeDefined(); + }); + }); + } +); diff --git a/lms/static/js/student_account/views/FormView.js b/lms/static/js/student_account/views/FormView.js index 75f7c56f1c..42fb614a76 100644 --- a/lms/static/js/student_account/views/FormView.js +++ b/lms/static/js/student_account/views/FormView.js @@ -213,6 +213,13 @@ this.focusFirstError(); }, + /* Allows extended views to add non-form attributes + * to the data before saving it to model + */ + setExtraData: function( data ) { + return data; + }, + submitForm: function( event ) { var data = this.getFormData(); @@ -223,6 +230,7 @@ this.toggleDisableButton(true); if ( !_.compact(this.errors).length ) { + data = this.setExtraData( data ); this.model.set( data ); this.model.save(); this.toggleErrorMsg( false ); diff --git a/lms/static/sass/views/_financial-assistance.scss b/lms/static/sass/views/_financial-assistance.scss index 4fc5bc69bd..e8415e6da9 100644 --- a/lms/static/sass/views/_financial-assistance.scss +++ b/lms/static/sass/views/_financial-assistance.scss @@ -1,33 +1,39 @@ +%fa-copy { + @extend %t-copy-base; + padding: ($baseline/2) 0; + margin: 0; + color: $m-gray-d2; +}; + .financial-assistance-wrapper { margin: auto; - padding: $baseline 0; + padding: $baseline ($baseline/2); max-width: 1180px; - .financial-assistance { + h1 { + @extend %t-title4; + @include text-align(left); + margin: 0; + padding: ($baseline/2) 0; border-bottom: 4px solid $gray-l5; + color: $m-gray-d3; + } - h1 { - @extend %t-title4; - @include text-align(left); - margin: 0; - padding: ($baseline/2) 0; - border-bottom: 4px solid $gray-l5; - color: $m-gray-d3; - } + h2 { + @extend %t-title6; + @extend %t-strong; + margin-top: ($baseline/2); + text-transform: none; + } - h2 { - @extend %t-title6; - @extend %t-strong; - margin-top: ($baseline/2); - text-transform: none; - } + p { + @extend %fa-copy; + font-size: 0.875em; + } - p { - @extend %t-copy-base; - padding: ($baseline/2) 0; - margin: 0; - color: $m-gray-d2; - } + .financial-assistance { + padding-bottom: ($baseline/2); + border-bottom: 4px solid $gray-l5; .apply-form-list { padding: 0; @@ -73,4 +79,165 @@ border-radius: 2px; } } + + // Application form View + .intro { + border-bottom: 4px solid $gray-l5; + + p { + margin: 10px 0; + } + } + + .success-message { + p { + margin: 10px 0; + } + } + + .btn-dashboard { + @include float(right); + color: $white; + + &:hover, + &:active, + &:focus { + color: $white; + } + } + + .user-info { + @include clearfix(); + border-bottom: 2px solid $gray-l5; + padding: 20px 0; + margin-bottom: 20px; + + .info-column { + @include float(left); + width: 100%; + margin: 10px 0; + } + + .title { + @extend %fa-copy; + padding: 0; + } + + .data { + @extend %fa-copy; + padding: 0; + color: $black; + font-size: 1.125em; + } + } + + .financial-assistance-form { + @extend .login-register; + + .action-primary { + @include float(left); + width: auto; + margin-top: 0; + } + + .nav-link { + margin: 15px 0; + display: block; + } + + form { + border: none; + } + + .form-field { + select, + input { + width: 320px; + } + + input { + border: { + top: none; + right: none; + bottom: 3px solid $gray-l1; + left: none; + }; + box-shadow: none; + } + + textarea { + height: 125px; + } + + .checkbox { + height: auto; + position: absolute; + top: 5px; + + & + label { + @include margin-left(30px); + display: inline-block; + } + } + } + } + + .cta-wrapper { + border-top: 4px solid $gray-l5; + padding: 20px 0; + } + + @include media($bp-medium) { + .user-info { + .info-column { + width: 50%; + } + } + + .financial-assistance-form { + .action-primary { + @include float(right); + } + + .nav-link { + display: inline-block; + } + } + } + + @include media($bp-large) { + .user-info { + .info-column { + width: 25%; + } + } + + .financial-assistance-form { + .action-primary { + @include float(right); + } + + .nav-link { + display: inline-block; + } + } + } + + @include media($bp-huge) { + .user-info { + .info-column { + width: 25%; + } + } + + .financial-assistance-form { + .action-primary { + @include float(right); + } + + .nav-link { + display: inline-block; + } + } + } } diff --git a/lms/templates/student_account/form_field.underscore b/lms/templates/student_account/form_field.underscore index 943b070940..898a59b5e6 100644 --- a/lms/templates/student_account/form_field.underscore +++ b/lms/templates/student_account/form_field.underscore @@ -21,6 +21,7 @@ <% }); %> + <% if ( instructions ) { %> <%= instructions %><% } %> <% } else if ( type === 'textarea' ) { %> + <% if ( instructions ) { %> <%= instructions %><% } %> <% } else { %> placeholder="<%= placeholder %>"<% } %> value="<%- defaultValue %>" /> + <% if ( type === 'checkbox' ) { %> + + <% } %> <% if ( instructions ) { %> <%= instructions %><% } %> <% } %> - <% if ( type === 'checkbox' ) { %> - - <% } %> - <% if( form === 'login' && name === 'password' ) { %> <%- gettext("Forgot password?") %> <% } %>