From 63603d70f4446276ae43113fa8cbd3ac711ec39d Mon Sep 17 00:00:00 2001 From: Ben McMorran Date: Tue, 19 Aug 2014 15:52:50 -0400 Subject: [PATCH] Refactor shared course creation validation into create_course_utils --- cms/static/js/index.js | 152 ++++----------- .../js/spec/views/pages/course_rerun_spec.js | 99 +++++----- cms/static/js/views/course_rerun.js | 176 ++++-------------- .../js/views/utils/create_course_utils.js | 151 +++++++++++++++ 4 files changed, 276 insertions(+), 302 deletions(-) create mode 100644 cms/static/js/views/utils/create_course_utils.js diff --git a/cms/static/js/index.js b/cms/static/js/index.js index 91d8d71c96..e6bbf124c7 100644 --- a/cms/static/js/index.js +++ b/cms/static/js/index.js @@ -1,5 +1,23 @@ -define(["domReady", "jquery", "underscore", "js/utils/cancel_on_escape"], - function (domReady, $, _, CancelOnEscape) { +define(["domReady", "jquery", "underscore", "js/utils/cancel_on_escape", "js/views/utils/create_course_utils"], + function (domReady, $, _, CancelOnEscape, CreateCourseUtilsFactory) { + var CreateCourseUtils = CreateCourseUtilsFactory({ + name: '.new-course-name', + org: '.new-course-org', + number: '.new-course-number', + run: '.new-course-run', + save: '.new-course-save', + errorWrapper: '.wrap-error', + errorMessage: '#course_creation_error', + tipError: 'span.tip-error', + error: '.error', + allowUnicode: '.allow-unicode-course-id' + }, { + shown: 'is-shown', + showing: 'is-showing', + hiding: 'is-hiding', + disabled: 'is-disabled', + error: 'error' + }); var dismissNotification = function (e) { e.preventDefault(); @@ -14,19 +32,7 @@ define(["domReady", "jquery", "underscore", "js/utils/cancel_on_escape"], var saveNewCourse = function (e) { e.preventDefault(); - // One final check for empty values - var errors = _.reduce( - ['.new-course-name', '.new-course-org', '.new-course-number', '.new-course-run'], - function (acc, ele) { - var $ele = $(ele); - var error = validateRequiredField($ele.val()); - setNewCourseFieldInErr($ele.parent('li'), error); - return error ? true : acc; - }, - false - ); - - if (errors) { + if (CreateCourseUtils.hasInvalidRequiredFields()) { return; } @@ -36,29 +42,19 @@ define(["domReady", "jquery", "underscore", "js/utils/cancel_on_escape"], var number = $newCourseForm.find('.new-course-number').val(); var run = $newCourseForm.find('.new-course-run').val(); - analytics.track('Created a Course', { - 'org': org, - 'number': number, - 'display_name': display_name, - 'run': run - }); + course_info = { + org: org, + number: number, + display_name: display_name, + run: run + }; - $.postJSON('/course/', { - 'org': org, - 'number': number, - 'display_name': display_name, - 'run': run - }, - function (data) { - if (data.url !== undefined) { - window.location = data.url; - } else if (data.ErrMsg !== undefined) { - $('.wrap-error').addClass('is-shown'); - $('#course_creation_error').html('

' + data.ErrMsg + '

'); - $('.new-course-save').addClass('is-disabled'); - } - } - ); + analytics.track('Created a Course', course_info); + CreateCourseUtils.createCourse(course_info, function (errorMessage) { + $('.wrap-error').addClass('is-shown'); + $('#course_creation_error').html('

' + errorMessage + '

'); + $('.new-course-save').addClass('is-disabled'); + }); }; var cancelNewCourse = function (e) { @@ -77,25 +73,6 @@ define(["domReady", "jquery", "underscore", "js/utils/cancel_on_escape"], $('.new-course-save').off('click'); }; - // Check that a course (org, number, run) doesn't use any special characters - var validateCourseItemEncoding = function (item) { - var required = validateRequiredField(item); - if (required) { - return required; - } - if ($('.allow-unicode-course-id').val() === 'True'){ - if (/\s/g.test(item)) { - return gettext('Please do not use any spaces in this field.'); - } - } - else{ - if (item !== encodeURIComponent(item)) { - return gettext('Please do not use any spaces or special characters in this field.'); - } - } - return ''; - }; - var addNewCourse = function (e) { e.preventDefault(); $('.new-course-button').addClass('is-disabled'); @@ -108,68 +85,7 @@ define(["domReady", "jquery", "underscore", "js/utils/cancel_on_escape"], $cancelButton.bind('click', cancelNewCourse); CancelOnEscape($cancelButton); - // Ensure that org/course_num/run < 65 chars. - var validateTotalCourseItemsLength = function () { - var totalLength = _.reduce( - ['.new-course-org', '.new-course-number', '.new-course-run'], - function (sum, ele) { - return sum + $(ele).val().length; - }, 0 - ); - if (totalLength > 65) { - $('.wrap-error').addClass('is-shown'); - $('#course_creation_error').html('

' + gettext('The combined length of the organization, course number, and course run fields cannot be more than 65 characters.') + '

'); - $('.new-course-save').addClass('is-disabled'); - } - else { - $('.wrap-error').removeClass('is-shown'); - } - }; - - // Handle validation asynchronously - _.each( - ['.new-course-org', '.new-course-number', '.new-course-run'], - function (ele) { - var $ele = $(ele); - $ele.on('keyup', function (event) { - // Don't bother showing "required field" error when - // the user tabs into a new field; this is distracting - // and unnecessary - if (event.keyCode === 9) { - return; - } - var error = validateCourseItemEncoding($ele.val()); - setNewCourseFieldInErr($ele.parent('li'), error); - validateTotalCourseItemsLength(); - }); - } - ); - var $name = $('.new-course-name'); - $name.on('keyup', function () { - var error = validateRequiredField($name.val()); - setNewCourseFieldInErr($name.parent('li'), error); - validateTotalCourseItemsLength(); - }); - }; - - var validateRequiredField = function (msg) { - return msg.length === 0 ? gettext('Required field.') : ''; - }; - - var setNewCourseFieldInErr = function (el, msg) { - if(msg) { - el.addClass('error'); - el.children('span.tip-error').addClass('is-showing').removeClass('is-hiding').text(msg); - $('.new-course-save').addClass('is-disabled'); - } - else { - el.removeClass('error'); - el.children('span.tip-error').addClass('is-hiding').removeClass('is-showing'); - // One "error" div is always present, but hidden or shown - if($('.error').length === 1) { - $('.new-course-save').removeClass('is-disabled'); - } - } + CreateCourseUtils.configureHandlers(); }; var onReady = function () { diff --git a/cms/static/js/spec/views/pages/course_rerun_spec.js b/cms/static/js/spec/views/pages/course_rerun_spec.js index fc77d29536..8bd31809c3 100644 --- a/cms/static/js/spec/views/pages/course_rerun_spec.js +++ b/cms/static/js/spec/views/pages/course_rerun_spec.js @@ -1,17 +1,24 @@ -define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers", "js/views/course_rerun"], - function ($, create_sinon, view_helpers, CourseRerunUtils) { +define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers", "js/views/course_rerun", + "js/views/utils/create_course_utils"], + function ($, create_sinon, view_helpers, CourseRerunUtils, CreateCourseUtilsFactory) { describe("Create course rerun page", function () { var selectors = { - courseOrg: '.rerun-course-org', - courseNumber: '.rerun-course-number', - courseRun: '.rerun-course-run', - courseName: '.rerun-course-name', - errorField: '.tip-error', - saveButton: '.rerun-course-save', - cancelButton: '.rerun-course-cancel', - errorMessage: '.wrapper-error' + org: '.rerun-course-org', + number: '.rerun-course-number', + run: '.rerun-course-run', + name: '.rerun-course-name', + tipError: 'span.tip-error', + save: '.rerun-course-save', + cancel: '.rerun-course-cancel', + errorWrapper: '.wrapper-error', + errorMessage: '#course_rerun_error', + error: '.error', + allowUnicode: '.allow-unicode-course-id' }, classes = { + shown: 'is-shown', + showing: 'is-showing', + hiding: 'is-hidden', hidden: 'is-hidden', error: 'error', disabled: 'is-disabled', @@ -19,11 +26,13 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers" }, mockCreateCourseRerunHTML = readFixtures('mock/mock-create-course-rerun.underscore'); + var CreateCourseUtils = CreateCourseUtilsFactory(selectors, classes); + var fillInFields = function (org, number, run, name) { - $(selectors.courseOrg).val(org); - $(selectors.courseNumber).val(number); - $(selectors.courseRun).val(run); - $(selectors.courseName).val(name); + $(selectors.org).val(org); + $(selectors.number).val(number); + $(selectors.run).val(run); + $(selectors.name).val(name); }; beforeEach(function () { @@ -40,12 +49,12 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers" describe("Field validation", function () { it("returns a message for an empty string", function () { - var message = CourseRerunUtils.validateRequiredField(''); + var message = CreateCourseUtils.validateRequiredField(''); expect(message).not.toBe(''); }); it("does not return a message for a non empty string", function () { - var message = CourseRerunUtils.validateRequiredField('edX'); + var message = CreateCourseUtils.validateRequiredField('edX'); expect(message).toBe(''); }); }); @@ -53,43 +62,43 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers" describe("Error messages", function () { var setErrorMessage = function(selector, message) { var element = $(selector).parent(); - CourseRerunUtils.setNewCourseFieldInErr(element, message); + CreateCourseUtils.setNewCourseFieldInErr(element, message); return element; }; it("shows an error message", function () { - var element = setErrorMessage(selectors.courseOrg, 'error message'); + var element = setErrorMessage(selectors.org, 'error message'); expect(element).toHaveClass(classes.error); - expect(element.children(selectors.errorField)).not.toHaveClass(classes.hidden); - expect(element.children(selectors.errorField)).toContainText('error message'); + expect(element.children(selectors.tipError)).not.toHaveClass(classes.hidden); + expect(element.children(selectors.tipError)).toContainText('error message'); }); it("hides an error message", function () { - var element = setErrorMessage(selectors.courseOrg, ''); + var element = setErrorMessage(selectors.org, ''); expect(element).not.toHaveClass(classes.error); - expect(element.children(selectors.errorField)).toHaveClass(classes.hidden); + expect(element.children(selectors.tipError)).toHaveClass(classes.hidden); }); it("disables the save button", function () { - setErrorMessage(selectors.courseOrg, 'error message'); - expect($(selectors.saveButton)).toHaveClass(classes.disabled); + setErrorMessage(selectors.org, 'error message'); + expect($(selectors.save)).toHaveClass(classes.disabled); }); it("enables the save button when all errors are removed", function () { - setErrorMessage(selectors.courseOrg, 'error message 1'); - setErrorMessage(selectors.courseNumber, 'error message 2'); - expect($(selectors.saveButton)).toHaveClass(classes.disabled); - setErrorMessage(selectors.courseOrg, ''); - setErrorMessage(selectors.courseNumber, ''); - expect($(selectors.saveButton)).not.toHaveClass(classes.disabled); + setErrorMessage(selectors.org, 'error message 1'); + setErrorMessage(selectors.number, 'error message 2'); + expect($(selectors.save)).toHaveClass(classes.disabled); + setErrorMessage(selectors.org, ''); + setErrorMessage(selectors.number, ''); + expect($(selectors.save)).not.toHaveClass(classes.disabled); }); it("does not enable the save button when errors remain", function () { - setErrorMessage(selectors.courseOrg, 'error message 1'); - setErrorMessage(selectors.courseNumber, 'error message 2'); - expect($(selectors.saveButton)).toHaveClass(classes.disabled); - setErrorMessage(selectors.courseOrg, ''); - expect($(selectors.saveButton)).toHaveClass(classes.disabled); + setErrorMessage(selectors.org, 'error message 1'); + setErrorMessage(selectors.number, 'error message 2'); + expect($(selectors.save)).toHaveClass(classes.disabled); + setErrorMessage(selectors.org, ''); + expect($(selectors.save)).toHaveClass(classes.disabled); }); }); @@ -97,7 +106,7 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers" var requests = create_sinon.requests(this); window.source_course_key = 'test_course_key'; fillInFields('DemoX', 'DM101', '2014', 'Demo course'); - $(selectors.saveButton).click(); + $(selectors.save).click(); create_sinon.expectJsonRequest(requests, 'POST', '/course/', { source_course_key: 'test_course_key', org: 'DemoX', @@ -105,28 +114,28 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers" run: '2014', display_name: 'Demo course' }); - expect($(selectors.saveButton)).toHaveClass(classes.disabled); - expect($(selectors.saveButton)).toHaveClass(classes.processing); - expect($(selectors.cancelButton)).toHaveClass(classes.hidden); + expect($(selectors.save)).toHaveClass(classes.disabled); + expect($(selectors.save)).toHaveClass(classes.processing); + expect($(selectors.cancel)).toHaveClass(classes.hidden); }); it("displays an error when saving fails", function () { var requests = create_sinon.requests(this); fillInFields('DemoX', 'DM101', '2014', 'Demo course'); - $(selectors.saveButton).click(); + $(selectors.save).click(); create_sinon.respondWithJson(requests, { ErrMsg: 'error message' }); - expect($(selectors.errorMessage)).not.toHaveClass(classes.hidden); - expect($(selectors.errorMessage)).toContainText('error message'); - expect($(selectors.saveButton)).not.toHaveClass(classes.processing); - expect($(selectors.cancelButton)).not.toHaveClass(classes.hidden); + expect($(selectors.errorWrapper)).not.toHaveClass(classes.hidden); + expect($(selectors.errorWrapper)).toContainText('error message'); + expect($(selectors.save)).not.toHaveClass(classes.processing); + expect($(selectors.cancel)).not.toHaveClass(classes.hidden); }); it("does not save if there are validation errors", function () { var requests = create_sinon.requests(this); fillInFields('DemoX', 'DM101', '', 'Demo course'); - $(selectors.saveButton).click(); + $(selectors.save).click(); expect(requests.length).toBe(0); }); }); diff --git a/cms/static/js/views/course_rerun.js b/cms/static/js/views/course_rerun.js index e9860a0796..60fd88fd7d 100644 --- a/cms/static/js/views/course_rerun.js +++ b/cms/static/js/views/course_rerun.js @@ -1,21 +1,28 @@ -define(["domReady", "jquery", "underscore"], - function (domReady, $, _) { +define(["domReady", "jquery", "underscore", "js/views/utils/create_course_utils"], + function (domReady, $, _, CreateCourseUtilsFactory) { + var CreateCourseUtils = CreateCourseUtilsFactory({ + name: '.rerun-course-name', + org: '.rerun-course-org', + number: '.rerun-course-number', + run: '.rerun-course-run', + save: '.rerun-course-save', + errorWrapper: '.wrapper-error', + errorMessage: '#course_rerun_error', + tipError: 'span.tip-error', + error: '.error', + allowUnicode: '.allow-unicode-course-id' + }, { + shown: 'is-shown', + showing: 'is-showing', + hiding: 'is-hidden', + disabled: 'is-disabled', + error: 'error' + }); var saveRerunCourse = function (e) { e.preventDefault(); - // One final check for errors - var errors = _.reduce( - ['.rerun-course-name', '.rerun-course-org', '.rerun-course-number', '.rerun-course-run'], - function (acc, ele) { - var $ele = $(ele); - var error = validateRequiredField($ele.val()); - setNewCourseFieldInErr($ele.parent('li'), error); - return error ? true : acc; - }, - false - ); - if (errors) { + if (CreateCourseUtils.hasInvalidRequiredFields()) { return; } @@ -25,31 +32,22 @@ define(["domReady", "jquery", "underscore"], var number = $newCourseForm.find('.rerun-course-number').val(); var run = $newCourseForm.find('.rerun-course-run').val(); - analytics.track('Reran a Course', { - 'source_course_key': source_course_key, - 'org': org, - 'number': number, - 'display_name': display_name, - 'run': run + course_info = { + source_course_key: source_course_key, + org: org, + number: number, + display_name: display_name, + run: run + }; + + analytics.track('Reran a Course', course_info); + CreateCourseUtils.createCourse(course_info, function (errorMessage) { + $('.wrapper-error').addClass('is-shown').removeClass('is-hidden'); + $('#course_rerun_error').html('

' + errorMessage + '

'); + $('.rerun-course-save').addClass('is-disabled').removeClass('is-processing').html(gettext('Create Re-run')); + $('.action-cancel').removeClass('is-hidden'); }); - $.postJSON('/course/', { - 'source_course_key': source_course_key, - 'org': org, - 'number': number, - 'display_name': display_name, - 'run': run - }, - function (data) { - if (data.url !== undefined) { - window.location = data.url; - } else if (data.ErrMsg !== undefined) { - $('.wrapper-error').addClass('is-shown').removeClass('is-hidden'); - $('#course_rerun_error').html('

' + data.ErrMsg + '

'); - $('.rerun-course-save').addClass('is-disabled').removeClass('is-processing').html(gettext('Create Re-run')); - $('.action-cancel').removeClass('is-hidden'); - } - } - ); + // Go into creating re-run state $('.rerun-course-save').addClass('is-disabled').addClass('is-processing').html( '' + gettext('Processing Re-run Request') @@ -67,26 +65,6 @@ define(["domReady", "jquery", "underscore"], window.location.href = '/course/'; }; - var validateRequiredField = function (msg) { - return msg.length === 0 ? gettext('Required field.') : ''; - }; - - var setNewCourseFieldInErr = function (el, msg) { - if(msg) { - el.addClass('error'); - el.children('span.tip-error').addClass('is-shown').removeClass('is-hidden').text(msg); - $('.rerun-course-save').addClass('is-disabled'); - } - else { - el.removeClass('error'); - el.children('span.tip-error').addClass('is-hidden').removeClass('is-shown'); - // One "error" div is always present, but hidden or shown - if($('.error').length === 1) { - $('.rerun-course-save').removeClass('is-disabled'); - } - } - }; - var onReady = function () { var $cancelButton = $('.rerun-course-cancel'); var $courseRun = $('.rerun-course-run'); @@ -95,85 +73,7 @@ define(["domReady", "jquery", "underscore"], $cancelButton.bind('click', cancelRerunCourse); $('.cancel-button').bind('click', cancelRerunCourse); - // Check that a course (org, number, run) doesn't use any special characters - var validateCourseItemEncoding = function (item) { - var required = validateRequiredField(item); - if (required) { - return required; - } - if ($('.allow-unicode-course-id').val() === 'True'){ - if (/\s/g.test(item)) { - return gettext('Please do not use any spaces in this field.'); - } - } - else{ - if (item !== encodeURIComponent(item)) { - return gettext('Please do not use any spaces or special characters in this field.'); - } - } - return ''; - }; - - // Ensure that org/course_num/run < 65 chars. - var validateTotalCourseItemsLength = function () { - var totalLength = _.reduce( - ['.rerun-course-org', '.rerun-course-number', '.rerun-course-run'], - function (sum, ele) { - return sum + $(ele).val().length; - }, 0 - ); - if (totalLength > 65) { - $('.wrap-error').addClass('is-shown'); - $('#course_creation_error').html('

' + gettext('The combined length of the organization, course number, and course run fields cannot be more than 65 characters.') + '

'); - $('.rerun-course-save').addClass('is-disabled'); - } - else { - $('.wrap-error').removeClass('is-shown'); - } - }; - - // Ensure that all fields are not empty - var validateFilledFields = function () { - return _.reduce( - ['.rerun-course-org', '.rerun-course-number', '.rerun-course-run', '.rerun-course-name'], - function (acc, ele) { - var $ele = $(ele); - return $ele.val().length !== 0 ? acc : false; - }, - true - ); - }; - - // Handle validation asynchronously - _.each( - ['.rerun-course-org', '.rerun-course-number', '.rerun-course-run'], - function (ele) { - var $ele = $(ele); - $ele.on('keyup', function (event) { - // Don't bother showing "required field" error when - // the user tabs into a new field; this is distracting - // and unnecessary - if (event.keyCode === 9) { - return; - } - var error = validateCourseItemEncoding($ele.val()); - setNewCourseFieldInErr($ele.parent(), error); - validateTotalCourseItemsLength(); - if(!validateFilledFields()) { - $('.rerun-course-save').addClass('is-disabled'); - } - }); - } - ); - var $name = $('.rerun-course-name'); - $name.on('keyup', function () { - var error = validateRequiredField($name.val()); - setNewCourseFieldInErr($name.parent(), error); - validateTotalCourseItemsLength(); - if(!validateFilledFields()) { - $('.rerun-course-save').addClass('is-disabled'); - } - }); + CreateCourseUtils.configureHandlers(); }; domReady(onReady); @@ -182,8 +82,6 @@ define(["domReady", "jquery", "underscore"], return { saveRerunCourse: saveRerunCourse, cancelRerunCourse: cancelRerunCourse, - validateRequiredField: validateRequiredField, - setNewCourseFieldInErr: setNewCourseFieldInErr, onReady: onReady }; }); diff --git a/cms/static/js/views/utils/create_course_utils.js b/cms/static/js/views/utils/create_course_utils.js new file mode 100644 index 0000000000..a8959fa247 --- /dev/null +++ b/cms/static/js/views/utils/create_course_utils.js @@ -0,0 +1,151 @@ +/** + * Provides utilities for validating courses during creation, for both new courses and reruns. + */ +define(["jquery", "underscore", "gettext"], + function ($, _, gettext) { + return function (selectors, classes) { + var validateRequiredField, validateCourseItemEncoding, validateTotalCourseItemsLength, setNewCourseFieldInErr, + hasInvalidRequiredFields, createCourse, validateFilledFields, configureHandlers; + + validateRequiredField = function (msg) { + return msg.length === 0 ? gettext('Required field.') : ''; + }; + + // Check that a course (org, number, run) doesn't use any special characters + validateCourseItemEncoding = function (item) { + var required = validateRequiredField(item); + if (required) { + return required; + } + if ($(selectors.allowUnicode).val() === 'True') { + if (/\s/g.test(item)) { + return gettext('Please do not use any spaces in this field.'); + } + } + else { + if (item !== encodeURIComponent(item)) { + return gettext('Please do not use any spaces or special characters in this field.'); + } + } + return ''; + }; + + // Ensure that org/course_num/run < 65 chars. + validateTotalCourseItemsLength = function () { + var totalLength = _.reduce( + [selectors.org, selectors.number, selectors.run], + function (sum, ele) { + return sum + $(ele).val().length; + }, 0 + ); + if (totalLength > 65) { + $(selectors.errorWrapper).addClass(classes.shown); + $(selectors.errorMessage).html('

' + gettext('The combined length of the organization, course number, and course run fields cannot be more than 65 characters.') + '

'); + $(selectors.save).addClass(classes.disabled); + } + else { + $(selectors.errorWrapper).removeClass(classes.shown); + } + }; + + setNewCourseFieldInErr = function (el, msg) { + if (msg) { + el.addClass(classes.error); + el.children(selectors.tipError).addClass(classes.showing).removeClass(classes.hiding).text(msg); + $(selectors.save).addClass(classes.disabled); + } + else { + el.removeClass(classes.error); + el.children(selectors.tipError).addClass(classes.hiding).removeClass(classes.showing); + // One "error" div is always present, but hidden or shown + if ($(selectors.error).length === 1) { + $(selectors.save).removeClass(classes.disabled); + } + } + }; + + // One final check for empty values + hasInvalidRequiredFields = function () { + return _.reduce( + [selectors.name, selectors.org, selectors.number, selectors.run], + function (acc, ele) { + var $ele = $(ele); + var error = validateRequiredField($ele.val()); + setNewCourseFieldInErr($ele.parent(), error); + return error ? true : acc; + }, + false + ); + }; + + createCourse = function (courseInfo, errorHandler) { + $.postJSON( + '/course/', + courseInfo, + function (data) { + if (data.url !== undefined) { + window.location = data.url; + } else if (data.ErrMsg !== undefined) { + errorHandler(data.ErrMsg); + } + } + ); + }; + + // Ensure that all fields are not empty + validateFilledFields = function () { + return _.reduce( + [selectors.org, selectors.number, selectors.run, selectors.name], + function (acc, ele) { + var $ele = $(ele); + return $ele.val().length !== 0 ? acc : false; + }, + true + ); + }; + + // Handle validation asynchronously + configureHandlers = function () { + _.each( + [selectors.org, selectors.number, selectors.run], + function (ele) { + var $ele = $(ele); + $ele.on('keyup', function (event) { + // Don't bother showing "required field" error when + // the user tabs into a new field; this is distracting + // and unnecessary + if (event.keyCode === 9) { + return; + } + var error = validateCourseItemEncoding($ele.val()); + setNewCourseFieldInErr($ele.parent(), error); + validateTotalCourseItemsLength(); + if (!validateFilledFields()) { + $(selectors.save).addClass(classes.disabled); + } + }); + } + ); + var $name = $(selectors.name); + $name.on('keyup', function () { + var error = validateRequiredField($name.val()); + setNewCourseFieldInErr($name.parent(), error); + validateTotalCourseItemsLength(); + if (!validateFilledFields()) { + $(selectors.save).addClass(classes.disabled); + } + }); + }; + + return { + validateRequiredField: validateRequiredField, + validateCourseItemEncoding: validateCourseItemEncoding, + validateTotalCourseItemsLength: validateTotalCourseItemsLength, + setNewCourseFieldInErr: setNewCourseFieldInErr, + hasInvalidRequiredFields: hasInvalidRequiredFields, + createCourse: createCourse, + validateFilledFields: validateFilledFields, + configureHandlers: configureHandlers + }; + }; + });