Commit 63603d70 by Ben McMorran

Refactor shared course creation validation into create_course_utils

parent 78879ebc
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('<p>' + data.ErrMsg + '</p>');
$('.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('<p>' + errorMessage + '</p>');
$('.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('<p>' + gettext('The combined length of the organization, course number, and course run fields cannot be more than 65 characters.') + '</p>');
$('.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 () {
......
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);
});
});
......
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('<p>' + errorMessage + '</p>');
$('.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('<p>' + data.ErrMsg + '</p>');
$('.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(
'<i class="icon icon-refresh icon-spin"></i>' + 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('<p>' + gettext('The combined length of the organization, course number, and course run fields cannot be more than 65 characters.') + '</p>');
$('.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
};
});
/**
* 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('<p>' + gettext('The combined length of the organization, course number, and course run fields cannot be more than 65 characters.') + '</p>');
$(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
};
};
});
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