@@ -233,6 +233,8 @@ define([
|
||||
"js/spec/views/pages/container_subviews_spec",
|
||||
"js/spec/views/pages/group_configurations_spec",
|
||||
"js/spec/views/pages/course_outline_spec",
|
||||
"js/spec/views/pages/course_rerun_spec",
|
||||
"js/spec/views/pages/index_spec",
|
||||
|
||||
"js/spec/views/modals/base_modal_spec",
|
||||
"js/spec/views/modals/edit_xblock_spec",
|
||||
|
||||
@@ -1,21 +1,29 @@
|
||||
require(["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",
|
||||
"js/views/utils/view_utils"],
|
||||
function (domReady, $, _, CancelOnEscape, CreateCourseUtilsFactory, ViewUtils) {
|
||||
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 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;
|
||||
}
|
||||
|
||||
@@ -25,29 +33,19 @@ require(["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) {
|
||||
@@ -78,91 +76,20 @@ require(["domReady", "jquery", "underscore", "js/utils/cancel_on_escape"],
|
||||
$cancelButton.bind('click', cancelNewCourse);
|
||||
CancelOnEscape($cancelButton);
|
||||
|
||||
// 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(
|
||||
['.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();
|
||||
});
|
||||
CreateCourseUtils.configureHandlers();
|
||||
};
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
domReady(function () {
|
||||
var onReady = function () {
|
||||
$('.new-course-button').bind('click', addNewCourse);
|
||||
});
|
||||
$('.dismiss-button').bind('click', ViewUtils.deleteNotificationHandler(function () {
|
||||
ViewUtils.reload();
|
||||
}));
|
||||
$('.action-reload').bind('click', ViewUtils.reload);
|
||||
};
|
||||
|
||||
domReady(onReady);
|
||||
|
||||
return {
|
||||
onReady: onReady
|
||||
};
|
||||
});
|
||||
|
||||
@@ -7,7 +7,8 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
|
||||
getItemsOfType, getItemHeaders, verifyItemsExpanded, expandItemsAndVerifyState, collapseItemsAndVerifyState,
|
||||
createMockCourseJSON, createMockSectionJSON, createMockSubsectionJSON, verifyTypePublishable,
|
||||
mockCourseJSON, mockEmptyCourseJSON, mockSingleSectionCourseJSON, createMockVerticalJSON,
|
||||
mockOutlinePage = readFixtures('mock/mock-course-outline-page.underscore');
|
||||
mockOutlinePage = readFixtures('mock/mock-course-outline-page.underscore'),
|
||||
mockRerunNotification = readFixtures('mock/mock-course-rerun-notification.underscore');
|
||||
|
||||
createMockCourseJSON = function(options, children) {
|
||||
return $.extend(true, {}, {
|
||||
@@ -243,6 +244,18 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
|
||||
});
|
||||
});
|
||||
|
||||
describe("Rerun notification", function () {
|
||||
it("can be dismissed", function () {
|
||||
appendSetFixtures(mockRerunNotification);
|
||||
createCourseOutlinePage(this, mockEmptyCourseJSON);
|
||||
expect($('.wrapper-alert-announcement')).not.toHaveClass('is-hidden');
|
||||
$('.dismiss-button').click();
|
||||
create_sinon.expectJsonRequest(requests, 'DELETE', 'dummy_dismiss_url');
|
||||
create_sinon.respondToDelete(requests);
|
||||
expect($('.wrapper-alert-announcement')).toHaveClass('is-hidden');
|
||||
});
|
||||
});
|
||||
|
||||
describe("Button bar", function() {
|
||||
it('can add a section', function() {
|
||||
createCourseOutlinePage(this, mockEmptyCourseJSON);
|
||||
|
||||
205
cms/static/js/spec/views/pages/course_rerun_spec.js
Normal file
205
cms/static/js/spec/views/pages/course_rerun_spec.js
Normal file
@@ -0,0 +1,205 @@
|
||||
define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers", "js/views/course_rerun",
|
||||
"js/views/utils/create_course_utils", "js/views/utils/view_utils", "jquery.simulate"],
|
||||
function ($, create_sinon, view_helpers, CourseRerunUtils, CreateCourseUtilsFactory, ViewUtils) {
|
||||
describe("Create course rerun page", function () {
|
||||
var selectors = {
|
||||
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',
|
||||
processing: 'is-processing'
|
||||
},
|
||||
mockCreateCourseRerunHTML = readFixtures('mock/mock-create-course-rerun.underscore');
|
||||
|
||||
var CreateCourseUtils = CreateCourseUtilsFactory(selectors, classes);
|
||||
|
||||
var fillInFields = function (org, number, run, name) {
|
||||
$(selectors.org).val(org);
|
||||
$(selectors.number).val(number);
|
||||
$(selectors.run).val(run);
|
||||
$(selectors.name).val(name);
|
||||
};
|
||||
|
||||
beforeEach(function () {
|
||||
view_helpers.installMockAnalytics();
|
||||
window.source_course_key = 'test_course_key';
|
||||
appendSetFixtures(mockCreateCourseRerunHTML);
|
||||
CourseRerunUtils.onReady();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
view_helpers.removeMockAnalytics();
|
||||
delete window.source_course_key;
|
||||
});
|
||||
|
||||
describe("Field validation", function () {
|
||||
it("returns a message for an empty string", function () {
|
||||
var message = CreateCourseUtils.validateRequiredField('');
|
||||
expect(message).not.toBe('');
|
||||
});
|
||||
|
||||
it("does not return a message for a non empty string", function () {
|
||||
var message = CreateCourseUtils.validateRequiredField('edX');
|
||||
expect(message).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe("Error messages", function () {
|
||||
var setErrorMessage = function(selector, message) {
|
||||
var element = $(selector).parent();
|
||||
CreateCourseUtils.setNewCourseFieldInErr(element, message);
|
||||
return element;
|
||||
};
|
||||
|
||||
var type = function (input, value) {
|
||||
input.val(value);
|
||||
input.simulate("keyup", { keyCode: $.simulate.keyCode.SPACE });
|
||||
};
|
||||
|
||||
it("shows an error message", function () {
|
||||
var element = setErrorMessage(selectors.org, 'error message');
|
||||
expect(element).toHaveClass(classes.error);
|
||||
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.org, '');
|
||||
expect(element).not.toHaveClass(classes.error);
|
||||
expect(element.children(selectors.tipError)).toHaveClass(classes.hidden);
|
||||
});
|
||||
|
||||
it("disables the save button", function () {
|
||||
setErrorMessage(selectors.org, 'error message');
|
||||
expect($(selectors.save)).toHaveClass(classes.disabled);
|
||||
});
|
||||
|
||||
it("enables the save button when all errors are removed", function () {
|
||||
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.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);
|
||||
});
|
||||
|
||||
it("shows an error message when non URL characters are entered", function () {
|
||||
var input = $(selectors.org);
|
||||
expect(input.parent()).not.toHaveClass(classes.error);
|
||||
type(input, "%");
|
||||
expect(input.parent()).toHaveClass(classes.error);
|
||||
});
|
||||
|
||||
it("does not show an error message when tabbing into a field", function () {
|
||||
var input = $(selectors.number);
|
||||
input.val('');
|
||||
expect(input.parent()).not.toHaveClass(classes.error);
|
||||
input.simulate("keyup", { keyCode: $.simulate.keyCode.TAB });
|
||||
expect(input.parent()).not.toHaveClass(classes.error);
|
||||
});
|
||||
|
||||
it("shows an error message when a required field is empty", function () {
|
||||
var input = $(selectors.org);
|
||||
input.val('');
|
||||
expect(input.parent()).not.toHaveClass(classes.error);
|
||||
input.simulate("keyup", { keyCode: $.simulate.keyCode.ENTER });
|
||||
expect(input.parent()).toHaveClass(classes.error);
|
||||
});
|
||||
|
||||
it("shows an error message when spaces are entered and unicode is allowed", function () {
|
||||
var input = $(selectors.org);
|
||||
$(selectors.allowUnicode).val('True');
|
||||
expect(input.parent()).not.toHaveClass(classes.error);
|
||||
type(input, ' ');
|
||||
expect(input.parent()).toHaveClass(classes.error);
|
||||
});
|
||||
|
||||
it("shows an error message when total length exceeds 65 characters", function () {
|
||||
expect($(selectors.errorWrapper)).not.toHaveClass(classes.shown);
|
||||
type($(selectors.org), 'ThisIsAVeryLongNameThatWillExceedTheSixtyFiveCharacterLimit');
|
||||
type($(selectors.number), 'ThisIsAVeryLongNameThatWillExceedTheSixtyFiveCharacterLimit');
|
||||
type($(selectors.run), 'ThisIsAVeryLongNameThatWillExceedTheSixtyFiveCharacterLimit');
|
||||
expect($(selectors.errorWrapper)).toHaveClass(classes.shown);
|
||||
});
|
||||
|
||||
describe("Name field", function () {
|
||||
it("does not show an error message when non URL characters are entered", function () {
|
||||
var input = $(selectors.name);
|
||||
expect(input.parent()).not.toHaveClass(classes.error);
|
||||
type(input, "%");
|
||||
expect(input.parent()).not.toHaveClass(classes.error);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("saves course reruns", function () {
|
||||
var requests = create_sinon.requests(this);
|
||||
var redirectSpy = spyOn(ViewUtils, 'redirect')
|
||||
fillInFields('DemoX', 'DM101', '2014', 'Demo course');
|
||||
$(selectors.save).click();
|
||||
create_sinon.expectJsonRequest(requests, 'POST', '/course/', {
|
||||
source_course_key: 'test_course_key',
|
||||
org: 'DemoX',
|
||||
number: 'DM101',
|
||||
run: '2014',
|
||||
display_name: 'Demo course'
|
||||
});
|
||||
expect($(selectors.save)).toHaveClass(classes.disabled);
|
||||
expect($(selectors.save)).toHaveClass(classes.processing);
|
||||
expect($(selectors.cancel)).toHaveClass(classes.hidden);
|
||||
create_sinon.respondWithJson(requests, {
|
||||
url: 'dummy_test_url'
|
||||
});
|
||||
expect(redirectSpy).toHaveBeenCalledWith('dummy_test_url');
|
||||
});
|
||||
|
||||
it("displays an error when saving fails", function () {
|
||||
var requests = create_sinon.requests(this);
|
||||
fillInFields('DemoX', 'DM101', '2014', 'Demo course');
|
||||
$(selectors.save).click();
|
||||
create_sinon.respondWithJson(requests, {
|
||||
ErrMsg: 'error message'
|
||||
});
|
||||
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.save).click();
|
||||
expect(requests.length).toBe(0);
|
||||
});
|
||||
|
||||
it("can be canceled", function () {
|
||||
var redirectSpy = spyOn(ViewUtils, 'redirect');
|
||||
$(selectors.cancel).click();
|
||||
expect(redirectSpy).toHaveBeenCalledWith('/course/');
|
||||
});
|
||||
});
|
||||
});
|
||||
65
cms/static/js/spec/views/pages/index_spec.js
Normal file
65
cms/static/js/spec/views/pages/index_spec.js
Normal file
@@ -0,0 +1,65 @@
|
||||
define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers", "js/index",
|
||||
"js/views/utils/view_utils"],
|
||||
function ($, create_sinon, view_helpers, IndexUtils, ViewUtils) {
|
||||
describe("Course listing page", function () {
|
||||
var mockIndexPageHTML = readFixtures('mock/mock-index-page.underscore'), fillInFields;
|
||||
|
||||
var fillInFields = function (org, number, run, name) {
|
||||
$('.new-course-org').val(org);
|
||||
$('.new-course-number').val(number);
|
||||
$('.new-course-run').val(run);
|
||||
$('.new-course-name').val(name);
|
||||
};
|
||||
|
||||
beforeEach(function () {
|
||||
view_helpers.installMockAnalytics();
|
||||
appendSetFixtures(mockIndexPageHTML);
|
||||
IndexUtils.onReady();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
view_helpers.removeMockAnalytics();
|
||||
delete window.source_course_key;
|
||||
});
|
||||
|
||||
it("can dismiss notifications", function () {
|
||||
var requests = create_sinon.requests(this);
|
||||
var reloadSpy = spyOn(ViewUtils, 'reload');
|
||||
$('.dismiss-button').click();
|
||||
create_sinon.expectJsonRequest(requests, 'DELETE', 'dummy_dismiss_url');
|
||||
create_sinon.respondToDelete(requests);
|
||||
expect(reloadSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("saves new courses", function () {
|
||||
var requests = create_sinon.requests(this);
|
||||
var redirectSpy = spyOn(ViewUtils, 'redirect');
|
||||
$('.new-course-button').click()
|
||||
fillInFields('DemoX', 'DM101', '2014', 'Demo course');
|
||||
$('.new-course-save').click();
|
||||
create_sinon.expectJsonRequest(requests, 'POST', '/course/', {
|
||||
org: 'DemoX',
|
||||
number: 'DM101',
|
||||
run: '2014',
|
||||
display_name: 'Demo course'
|
||||
});
|
||||
create_sinon.respondWithJson(requests, {
|
||||
url: 'dummy_test_url'
|
||||
});
|
||||
expect(redirectSpy).toHaveBeenCalledWith('dummy_test_url');
|
||||
});
|
||||
|
||||
it("displays an error when saving fails", function () {
|
||||
var requests = create_sinon.requests(this);
|
||||
$('.new-course-button').click();
|
||||
fillInFields('DemoX', 'DM101', '2014', 'Demo course');
|
||||
$('.new-course-save').click();
|
||||
create_sinon.respondWithJson(requests, {
|
||||
ErrMsg: 'error message'
|
||||
});
|
||||
expect($('.wrap-error')).toHaveClass('is-shown');
|
||||
expect($('#course_creation_error')).toContainText('error message');
|
||||
expect($('.new-course-save')).toHaveClass('is-disabled');
|
||||
});
|
||||
});
|
||||
});
|
||||
87
cms/static/js/views/course_rerun.js
Normal file
87
cms/static/js/views/course_rerun.js
Normal file
@@ -0,0 +1,87 @@
|
||||
define(["domReady", "jquery", "underscore", "js/views/utils/create_course_utils", "js/views/utils/view_utils"],
|
||||
function (domReady, $, _, CreateCourseUtilsFactory, ViewUtils) {
|
||||
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();
|
||||
|
||||
if (CreateCourseUtils.hasInvalidRequiredFields()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var $newCourseForm = $(this).closest('#rerun-course-form');
|
||||
var display_name = $newCourseForm.find('.rerun-course-name').val();
|
||||
var org = $newCourseForm.find('.rerun-course-org').val();
|
||||
var number = $newCourseForm.find('.rerun-course-number').val();
|
||||
var run = $newCourseForm.find('.rerun-course-run').val();
|
||||
|
||||
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');
|
||||
});
|
||||
|
||||
// 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')
|
||||
);
|
||||
$('.action-cancel').addClass('is-hidden');
|
||||
};
|
||||
|
||||
var cancelRerunCourse = function (e) {
|
||||
e.preventDefault();
|
||||
// Clear out existing fields and errors
|
||||
$('.rerun-course-run').val('');
|
||||
$('#course_rerun_error').html('');
|
||||
$('wrapper-error').removeClass('is-shown').addClass('is-hidden');
|
||||
$('.rerun-course-save').off('click');
|
||||
ViewUtils.redirect('/course/');
|
||||
};
|
||||
|
||||
var onReady = function () {
|
||||
var $cancelButton = $('.rerun-course-cancel');
|
||||
var $courseRun = $('.rerun-course-run');
|
||||
$courseRun.focus().select();
|
||||
$('.rerun-course-save').on('click', saveRerunCourse);
|
||||
$cancelButton.bind('click', cancelRerunCourse);
|
||||
$('.cancel-button').bind('click', cancelRerunCourse);
|
||||
|
||||
CreateCourseUtils.configureHandlers();
|
||||
};
|
||||
|
||||
domReady(onReady);
|
||||
|
||||
// Return these functions so that they can be tested
|
||||
return {
|
||||
saveRerunCourse: saveRerunCourse,
|
||||
cancelRerunCourse: cancelRerunCourse,
|
||||
onReady: onReady
|
||||
};
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
define(["domReady", "jquery", "jquery.ui", "underscore", "gettext", "js/views/feedback_notification",
|
||||
"js/utils/cancel_on_escape", "js/utils/date_utils", "js/utils/module"],
|
||||
"js/utils/cancel_on_escape", "js/utils/date_utils", "js/utils/module", "js/views/utils/view_utils"],
|
||||
function (domReady, $, ui, _, gettext, NotificationView, CancelOnEscape,
|
||||
DateUtils, ModuleUtils) {
|
||||
DateUtils, ModuleUtils, ViewUtils) {
|
||||
|
||||
var modalSelector = '.edit-section-publish-settings';
|
||||
|
||||
@@ -222,6 +222,10 @@ define(["domReady", "jquery", "jquery.ui", "underscore", "gettext", "js/views/fe
|
||||
$('.toggle-button-sections').bind('click', toggleSections);
|
||||
$('.expand-collapse').bind('click', toggleSubmodules);
|
||||
|
||||
$('.dismiss-button').bind('click', ViewUtils.deleteNotificationHandler(function () {
|
||||
$('.wrapper-alert-announcement').remove();
|
||||
}));
|
||||
|
||||
var $body = $('body');
|
||||
$body.on('click', '.section-published-date .edit-release-date', editSectionPublishDate);
|
||||
$body.on('click', '.edit-section-publish-settings .action-save', saveSetSectionScheduleDate);
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
* This page is used to show the user an outline of the course.
|
||||
*/
|
||||
define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views/utils/xblock_utils",
|
||||
"js/views/course_outline"],
|
||||
function ($, _, gettext, BasePage, XBlockViewUtils, CourseOutlineView) {
|
||||
"js/views/course_outline", "js/views/utils/view_utils"],
|
||||
function ($, _, gettext, BasePage, XBlockViewUtils, CourseOutlineView, ViewUtils) {
|
||||
var expandedLocators, CourseOutlinePage;
|
||||
|
||||
CourseOutlinePage = BasePage.extend({
|
||||
@@ -25,6 +25,9 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
|
||||
self.outlineView.handleAddEvent(event);
|
||||
});
|
||||
this.model.on('change', this.setCollapseExpandVisibility, this);
|
||||
$('.dismiss-button').bind('click', ViewUtils.deleteNotificationHandler(function () {
|
||||
$('.wrapper-alert-announcement').removeClass('is-shown').addClass('is-hidden')
|
||||
}));
|
||||
},
|
||||
|
||||
setCollapseExpandVisibility: function() {
|
||||
|
||||
151
cms/static/js/views/utils/create_course_utils.js
Normal file
151
cms/static/js/views/utils/create_course_utils.js
Normal file
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* Provides utilities for validating courses during creation, for both new courses and reruns.
|
||||
*/
|
||||
define(["jquery", "underscore", "gettext", "js/views/utils/view_utils"],
|
||||
function ($, _, gettext, ViewUtils) {
|
||||
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).removeClass(classes.hiding);
|
||||
$(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).addClass(classes.hiding);
|
||||
}
|
||||
};
|
||||
|
||||
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) {
|
||||
ViewUtils.redirect(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
|
||||
};
|
||||
};
|
||||
});
|
||||
@@ -5,7 +5,7 @@ define(["jquery", "underscore", "gettext", "js/views/feedback_notification", "js
|
||||
function ($, _, gettext, NotificationView, PromptView) {
|
||||
var toggleExpandCollapse, showLoadingIndicator, hideLoadingIndicator, confirmThenRunOperation,
|
||||
runOperationShowingMessage, disableElementWhileRunning, getScrollOffset, setScrollOffset,
|
||||
setScrollTop, redirect, hasChangedAttributes;
|
||||
setScrollTop, redirect, reload, hasChangedAttributes, deleteNotificationHandler;
|
||||
|
||||
/**
|
||||
* Toggles the expanded state of the current element.
|
||||
@@ -94,6 +94,21 @@ define(["jquery", "underscore", "gettext", "js/views/feedback_notification", "js
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a handler that removes a notification, both dismissing it and deleting it from the database.
|
||||
* @param callback function to call when deletion succeeds
|
||||
*/
|
||||
deleteNotificationHandler = function(callback) {
|
||||
return function (event) {
|
||||
event.preventDefault();
|
||||
$.ajax({
|
||||
url: $(this).data('dismiss-link'),
|
||||
type: 'DELETE',
|
||||
success: callback
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Performs an animated scroll so that the window has the specified scroll top.
|
||||
* @param scrollTop The desired scroll top for the window.
|
||||
@@ -132,6 +147,13 @@ define(["jquery", "underscore", "gettext", "js/views/feedback_notification", "js
|
||||
window.location = url;
|
||||
};
|
||||
|
||||
/**
|
||||
* Reloads the page. This is broken out as its own function for unit testing.
|
||||
*/
|
||||
reload = function() {
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if a model has changes to at least one of the specified attributes.
|
||||
* @param model The model in question.
|
||||
@@ -158,10 +180,12 @@ define(["jquery", "underscore", "gettext", "js/views/feedback_notification", "js
|
||||
'confirmThenRunOperation': confirmThenRunOperation,
|
||||
'runOperationShowingMessage': runOperationShowingMessage,
|
||||
'disableElementWhileRunning': disableElementWhileRunning,
|
||||
'deleteNotificationHandler': deleteNotificationHandler,
|
||||
'setScrollTop': setScrollTop,
|
||||
'getScrollOffset': getScrollOffset,
|
||||
'setScrollOffset': setScrollOffset,
|
||||
'redirect': redirect,
|
||||
'reload': reload,
|
||||
'hasChangedAttributes': hasChangedAttributes
|
||||
};
|
||||
});
|
||||
|
||||
@@ -240,6 +240,282 @@ p, ul, ol, dl {
|
||||
}
|
||||
}
|
||||
|
||||
// ====================
|
||||
|
||||
// layout - basic
|
||||
.wrapper-view {
|
||||
|
||||
}
|
||||
|
||||
// ====================
|
||||
|
||||
// layout - basic page header
|
||||
.wrapper-mast {
|
||||
margin: ($baseline*1.5) 0 0 0;
|
||||
padding: 0 $baseline;
|
||||
position: relative;
|
||||
|
||||
.mast, .metadata {
|
||||
@include clearfix();
|
||||
position: relative;
|
||||
max-width: $fg-max-width;
|
||||
min-width: $fg-min-width;
|
||||
width: flex-grid(12);
|
||||
margin: 0 auto $baseline auto;
|
||||
color: $gray-d2;
|
||||
}
|
||||
|
||||
.mast {
|
||||
border-bottom: 1px solid $gray-l4;
|
||||
padding-bottom: ($baseline/2);
|
||||
|
||||
// layout with actions
|
||||
.page-header {
|
||||
width: flex-grid(12);
|
||||
}
|
||||
|
||||
// layout with actions
|
||||
&.has-actions {
|
||||
@include clearfix();
|
||||
|
||||
.page-header {
|
||||
float: left;
|
||||
width: flex-grid(6,12);
|
||||
margin-right: flex-gutter();
|
||||
}
|
||||
|
||||
.nav-actions {
|
||||
position: relative;
|
||||
bottom: -($baseline*0.75);
|
||||
float: right;
|
||||
width: flex-grid(6,12);
|
||||
text-align: right;
|
||||
|
||||
.nav-item {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin-right: ($baseline/2);
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// buttons
|
||||
.button {
|
||||
padding: ($baseline/4) ($baseline/2) ($baseline/3) ($baseline/2);
|
||||
}
|
||||
|
||||
.new-button {
|
||||
|
||||
}
|
||||
|
||||
.view-button {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// layout with actions
|
||||
&.has-subtitle {
|
||||
|
||||
.nav-actions {
|
||||
bottom: -($baseline*1.5);
|
||||
}
|
||||
}
|
||||
|
||||
// layout with navigation
|
||||
&.has-navigation {
|
||||
|
||||
.nav-actions {
|
||||
bottom: -($baseline*1.5);
|
||||
}
|
||||
|
||||
.navigation-link {
|
||||
@extend %cont-truncated;
|
||||
display: inline-block;
|
||||
vertical-align: bottom; // correct for extra padding in FF
|
||||
max-width: 250px;
|
||||
|
||||
&.navigation-current {
|
||||
@extend %ui-disabled;
|
||||
color: $gray;
|
||||
max-width: 250px;
|
||||
|
||||
&:before {
|
||||
color: $gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.navigation-link:before {
|
||||
content: " / ";
|
||||
margin: ($baseline/4);
|
||||
color: $gray;
|
||||
|
||||
&:hover {
|
||||
color: $gray;
|
||||
}
|
||||
}
|
||||
|
||||
.navigation .navigation-link:first-child:before {
|
||||
content: "";
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CASE: wizard-based mast
|
||||
.mast-wizard {
|
||||
|
||||
.page-header-sub {
|
||||
@extend %t-title4;
|
||||
color: $gray;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.page-header-super {
|
||||
@extend %t-title4;
|
||||
float: left;
|
||||
width: flex-grid(12,12);
|
||||
margin-top: ($baseline/2);
|
||||
border-top: 1px solid $gray-l4;
|
||||
padding-top: ($baseline/2);
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
// page metadata/action bar
|
||||
.metadata {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// layout - basic page content
|
||||
.wrapper-content {
|
||||
margin: 0;
|
||||
padding: 0 $baseline;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.content {
|
||||
@include clearfix();
|
||||
@extend %t-copy-base;
|
||||
max-width: $fg-max-width;
|
||||
min-width: $fg-min-width;
|
||||
width: flex-grid(12);
|
||||
margin: 0 auto;
|
||||
color: $gray-d2;
|
||||
|
||||
header {
|
||||
position: relative;
|
||||
margin-bottom: $baseline;
|
||||
border-bottom: 1px solid $gray-l4;
|
||||
padding-bottom: ($baseline/2);
|
||||
|
||||
.title-sub {
|
||||
@extend %t-copy-sub1;
|
||||
display: block;
|
||||
margin: 0;
|
||||
color: $gray-l2;
|
||||
}
|
||||
|
||||
.title-1 {
|
||||
@extend %t-title3;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-weight: 600;
|
||||
color: $gray-d3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content-primary, .content-supplementary {
|
||||
@include box-sizing(border-box);
|
||||
}
|
||||
|
||||
// layout - primary content
|
||||
.content-primary {
|
||||
|
||||
.title-1 {
|
||||
@extend %t-title3;
|
||||
}
|
||||
|
||||
.title-2 {
|
||||
@extend %t-title4;
|
||||
margin: 0 0 ($baseline/2) 0;
|
||||
}
|
||||
|
||||
.title-3 {
|
||||
@extend %t-title6;
|
||||
margin: 0 0 ($baseline/2) 0;
|
||||
}
|
||||
|
||||
header {
|
||||
@include clearfix();
|
||||
|
||||
.title-2 {
|
||||
width: flex-grid(5, 12);
|
||||
margin: 0 flex-gutter() 0 0;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.tip {
|
||||
@extend %t-copy-sub2;
|
||||
width: flex-grid(7, 12);
|
||||
float: right;
|
||||
margin-top: ($baseline/2);
|
||||
text-align: right;
|
||||
color: $gray-l2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// layout - supplemental content
|
||||
.content-supplementary {
|
||||
|
||||
> section {
|
||||
margin: 0 0 $baseline 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ====================
|
||||
|
||||
// layout - grandfathered
|
||||
.main-wrapper {
|
||||
position: relative;
|
||||
margin: 0 ($baseline*2);
|
||||
}
|
||||
|
||||
.inner-wrapper {
|
||||
@include clearfix();
|
||||
position: relative;
|
||||
max-width: 1280px;
|
||||
margin: auto;
|
||||
|
||||
> article {
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
|
||||
.main-column {
|
||||
clear: both;
|
||||
float: left;
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
float: right;
|
||||
width: 28%;
|
||||
}
|
||||
|
||||
.left {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
// ====================
|
||||
|
||||
|
||||
@@ -133,6 +133,27 @@
|
||||
}
|
||||
}
|
||||
|
||||
// white secondary button
|
||||
%btn-secondary-white {
|
||||
@extend %ui-btn-secondary;
|
||||
border-color: $white-t2;
|
||||
color: $white-t3;
|
||||
|
||||
&:hover, &:active {
|
||||
border-color: $white;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
&.current, &.active {
|
||||
background: $gray-d2;
|
||||
color: $gray-l5;
|
||||
|
||||
&:hover, &:active {
|
||||
background: $gray-d2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// green secondary button
|
||||
%btn-secondary-green {
|
||||
@extend %ui-btn-secondary;
|
||||
@@ -213,17 +234,6 @@
|
||||
|
||||
// ====================
|
||||
|
||||
// calls-to-action
|
||||
|
||||
// ====================
|
||||
|
||||
// specific buttons - view live
|
||||
%view-live-button {
|
||||
@extend %t-action4;
|
||||
}
|
||||
|
||||
// ====================
|
||||
|
||||
// UI: element actions list
|
||||
|
||||
%actions-list {
|
||||
|
||||
@@ -138,28 +138,10 @@ form {
|
||||
}
|
||||
}
|
||||
|
||||
// ELEM: form wrapper
|
||||
.wrapper-create-element {
|
||||
height: 0;
|
||||
margin-bottom: $baseline;
|
||||
opacity: 0.0;
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
|
||||
&.animate {
|
||||
@include transition(opacity $tmg-f1 ease-in-out 0s, height $tmg-f1 ease-in-out 0s);
|
||||
}
|
||||
|
||||
&.is-shown {
|
||||
height: auto; // define a specific height for the animating version of this UI to work properly
|
||||
opacity: 1.0;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
// ELEM: form
|
||||
// form styling for creating a new content item (course, user, textbook)
|
||||
form[class^="create-"] {
|
||||
// TODO: refactor this into a placeholder to extend.
|
||||
.form-create {
|
||||
@extend %ui-window;
|
||||
|
||||
.title {
|
||||
@@ -254,12 +236,19 @@ form[class^="create-"] {
|
||||
|
||||
.tip {
|
||||
@extend %t-copy-sub2;
|
||||
@include transition(color, 0.15s, ease-in-out);
|
||||
@include transition(color 0.15s ease-in-out);
|
||||
|
||||
|
||||
display: block;
|
||||
margin-top: ($baseline/4);
|
||||
color: $gray-l3;
|
||||
}
|
||||
|
||||
.tip-note {
|
||||
display: block;
|
||||
margin-top: ($baseline/4);
|
||||
}
|
||||
|
||||
.tip-error {
|
||||
display: none;
|
||||
float: none;
|
||||
@@ -366,7 +355,6 @@ form[class^="create-"] {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// form - inline xblock name edit on unit, container, outline
|
||||
|
||||
// TOOD: abstract this out into a Sass placeholder
|
||||
@@ -403,6 +391,25 @@ form[class^="create-"] {
|
||||
}
|
||||
}
|
||||
|
||||
// ELEM: form wrapper
|
||||
.wrapper-create-element {
|
||||
height: 0;
|
||||
margin-bottom: $baseline;
|
||||
opacity: 0.0;
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
|
||||
&.animate {
|
||||
@include transition(opacity $tmg-f1 ease-in-out 0s, height $tmg-f1 ease-in-out 0s);
|
||||
}
|
||||
|
||||
&.is-shown {
|
||||
height: auto; // define a specific height for the animating version of this UI to work properly
|
||||
opacity: 1.0;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
// ====================
|
||||
|
||||
// forms - grandfathered
|
||||
|
||||
@@ -141,6 +141,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
// CASE: wizard-based mast
|
||||
.mast-wizard {
|
||||
|
||||
.page-header-sub {
|
||||
@extend %t-title4;
|
||||
color: $gray;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.page-header-super {
|
||||
@extend %t-title4;
|
||||
float: left;
|
||||
width: flex-grid(12,12);
|
||||
margin-top: ($baseline/2);
|
||||
border-top: 1px solid $gray-l4;
|
||||
padding-top: ($baseline/2);
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
// page metadata/action bar
|
||||
.metadata {
|
||||
|
||||
|
||||
@@ -527,7 +527,7 @@
|
||||
&.wrapper-alert-warning {
|
||||
box-shadow: 0 1px 1px $white, inset 0 2px 2px $shadow-d1, inset 0 -4px 1px $orange;
|
||||
|
||||
[class^="icon"] {
|
||||
.alert-symbol {
|
||||
color: $orange;
|
||||
}
|
||||
}
|
||||
@@ -535,7 +535,7 @@
|
||||
&.wrapper-alert-error {
|
||||
box-shadow: 0 1px 1px $white, inset 0 2px 2px $shadow-d1, inset 0 -4px 1px $red-l1;
|
||||
|
||||
[class^="icon"] {
|
||||
.alert-symbol {
|
||||
color: $red-l1;
|
||||
}
|
||||
}
|
||||
@@ -543,7 +543,7 @@
|
||||
&.wrapper-alert-confirmation {
|
||||
box-shadow: 0 1px 1px $white, inset 0 2px 2px $shadow-d1, inset 0 -4px 1px $green;
|
||||
|
||||
[class^="icon"] {
|
||||
.alert-symbol {
|
||||
color: $green;
|
||||
}
|
||||
}
|
||||
@@ -551,7 +551,7 @@
|
||||
&.wrapper-alert-announcement {
|
||||
box-shadow: 0 1px 1px $white, inset 0 2px 2px $shadow-d1, inset 0 -4px 1px $blue;
|
||||
|
||||
[class^="icon"] {
|
||||
.alert-symbol {
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
@@ -559,7 +559,7 @@
|
||||
&.wrapper-alert-step-required {
|
||||
box-shadow: 0 1px 1px $white, inset 0 2px 2px $shadow-d1, inset 0 -4px 1px $pink;
|
||||
|
||||
[class^="icon"] {
|
||||
.alert-symbol {
|
||||
color: $pink;
|
||||
}
|
||||
}
|
||||
@@ -579,11 +579,11 @@
|
||||
@extend %t-strong;
|
||||
}
|
||||
|
||||
[class^="icon"], .copy {
|
||||
.alert-symbol, .copy {
|
||||
float: left;
|
||||
}
|
||||
|
||||
[class^="icon"] {
|
||||
.alert-symbol {
|
||||
@include transition (color 0.50s ease-in-out 0s);
|
||||
@extend %t-icon3;
|
||||
width: flex-grid(1, 12);
|
||||
@@ -605,7 +605,7 @@
|
||||
// with actions
|
||||
&.has-actions {
|
||||
|
||||
[class^="icon"] {
|
||||
.alert-symbol {
|
||||
width: flex-grid(1, 12);
|
||||
}
|
||||
|
||||
@@ -667,6 +667,29 @@
|
||||
background: $gray-d1;
|
||||
}
|
||||
}
|
||||
|
||||
// with dismiss (to sunset action-alert-clos)
|
||||
.action-dismiss {
|
||||
|
||||
.button {
|
||||
@extend %btn-secondary-white;
|
||||
padding:($baseline/4) ($baseline/2);
|
||||
}
|
||||
|
||||
.icon,.button-copy {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.icon {
|
||||
@extend %t-icon4;
|
||||
margin-right: ($baseline/4);
|
||||
}
|
||||
|
||||
.button-copy {
|
||||
@extend %t-copy-sub1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ====================
|
||||
|
||||
@@ -243,6 +243,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
// learn more (aka external help button)
|
||||
.external-help-button {
|
||||
@extend %ui-btn-flat-outline;
|
||||
@extend %sizing;
|
||||
}
|
||||
|
||||
// actions
|
||||
.list-actions {
|
||||
@extend %cont-no-list;
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
@import 'views/dashboard';
|
||||
@import 'views/export';
|
||||
@import 'views/index';
|
||||
@import 'views/course-create';
|
||||
@import 'views/import';
|
||||
@import 'views/outline';
|
||||
@import 'views/settings';
|
||||
|
||||
122
cms/static/sass/views/_course-create.scss
Normal file
122
cms/static/sass/views/_course-create.scss
Normal file
@@ -0,0 +1,122 @@
|
||||
// studio - views - course creation page
|
||||
// ====================
|
||||
|
||||
.view-course-create {
|
||||
|
||||
// basic layout
|
||||
// --------------------
|
||||
.content-primary, .content-supplementary {
|
||||
@include box-sizing(border-box);
|
||||
float: left;
|
||||
}
|
||||
|
||||
.content-primary {
|
||||
width: flex-grid(9, 12);
|
||||
margin-right: flex-gutter();
|
||||
}
|
||||
|
||||
.content-supplementary {
|
||||
width: flex-grid(3, 12);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
// header/masthead
|
||||
// --------------------
|
||||
.mast .page-header-super {
|
||||
|
||||
.course-original-title-id, .course-original-title {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.course-original-title-id {
|
||||
@extend %t-title5;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// course re-run form
|
||||
// --------------------
|
||||
.rerun-course {
|
||||
|
||||
.row {
|
||||
@include clearfix();
|
||||
margin-bottom: ($baseline*0.75);
|
||||
}
|
||||
|
||||
.column {
|
||||
float: left;
|
||||
width: 48%;
|
||||
}
|
||||
|
||||
.column:first-child {
|
||||
margin-right: 4%;
|
||||
}
|
||||
|
||||
label {
|
||||
@extend %t-title7;
|
||||
display: block;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.rerun-course-org,
|
||||
.rerun-course-number,
|
||||
.rerun-course-name,
|
||||
.rerun-course-run {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.rerun-course-name {
|
||||
@extend %t-title5;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.rerun-course-save {
|
||||
@include blue-button;
|
||||
|
||||
.icon {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-right: ($baseline/4);
|
||||
}
|
||||
}
|
||||
|
||||
.rerun-course-cancel {
|
||||
@include white-button;
|
||||
}
|
||||
|
||||
.item-details {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.wrap-error {
|
||||
@include transition(opacity $tmg-f2 ease 0s);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.wrap-error.is-shown {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.message-status {
|
||||
display: block;
|
||||
margin-bottom: 0;
|
||||
padding: ($baseline*.5) ($baseline*1.5) 8px ($baseline*1.5);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
// NOTE: override for modern button styling until all buttons (in _forms.scss) can be converted
|
||||
.actions {
|
||||
|
||||
.action-primary {
|
||||
@include blue-button;
|
||||
@extend %t-action2;
|
||||
}
|
||||
|
||||
.action-secondary {
|
||||
@include grey-button;
|
||||
@extend %t-action2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -236,7 +236,7 @@
|
||||
@extend %t-strong;
|
||||
margin: ($baseline/2);
|
||||
|
||||
[class^="icon-"] {
|
||||
.icon {
|
||||
margin-right: ($baseline/4);
|
||||
}
|
||||
}
|
||||
@@ -294,120 +294,307 @@
|
||||
// ELEM: course listings
|
||||
.courses {
|
||||
margin: $baseline 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
@extend %t-title6;
|
||||
margin-bottom: $baseline;
|
||||
border-bottom: 1px solid $gray-l3;
|
||||
padding-bottom: ($baseline/2);
|
||||
color: $gray-l2;
|
||||
}
|
||||
|
||||
.title {
|
||||
@extend %t-title6;
|
||||
margin-bottom: $baseline;
|
||||
border-bottom: 1px solid $gray-l3;
|
||||
padding-bottom: ($baseline/2);
|
||||
color: $gray-l2;
|
||||
}
|
||||
}
|
||||
|
||||
.list-courses {
|
||||
margin-top: $baseline;
|
||||
border-radius: 3px;
|
||||
border: 1px solid $gray;
|
||||
border: 1px solid $gray-l2;
|
||||
background: $white;
|
||||
box-shadow: 0 1px 2px $shadow-l1;
|
||||
box-shadow: 0 1px 1px $shadow-l1;
|
||||
|
||||
.course-item {
|
||||
@include box-sizing(border-box);
|
||||
width: flex-grid(9, 9);
|
||||
position: relative;
|
||||
border-bottom: 1px solid $gray-l1;
|
||||
padding: $baseline;
|
||||
li:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// STATE: hover/focus
|
||||
&:hover {
|
||||
background: $paleYellow;
|
||||
|
||||
.course-actions .view-live-button {
|
||||
opacity: 1.0;
|
||||
pointer-events: auto;
|
||||
}
|
||||
// UI: course wrappers (needed for status messages)
|
||||
.wrapper-course {
|
||||
|
||||
.course-title {
|
||||
color: $orange-d1;
|
||||
}
|
||||
// CASE: has status
|
||||
&.has-status {
|
||||
|
||||
.course-metadata {
|
||||
opacity: 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
.course-link, .course-actions {
|
||||
.course-status {
|
||||
@include box-sizing(border-box);
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
// encompassing course link
|
||||
.course-link {
|
||||
@extend %ui-depth2;
|
||||
width: flex-grid(7, 9);
|
||||
margin-right: flex-gutter();
|
||||
}
|
||||
|
||||
// course title
|
||||
.course-title {
|
||||
@extend %t-title4;
|
||||
@extend %t-light;
|
||||
margin: 0 ($baseline*2) ($baseline/4) 0;
|
||||
}
|
||||
|
||||
// course metadata
|
||||
.course-metadata {
|
||||
@extend %t-copy-sub1;
|
||||
@include transition(opacity $tmg-f1 ease-in-out 0);
|
||||
color: $gray;
|
||||
opacity: 0.75;
|
||||
|
||||
.metadata-item {
|
||||
display: inline-block;
|
||||
|
||||
&:after {
|
||||
content: "/";
|
||||
margin-left: ($baseline/10);
|
||||
margin-right: ($baseline/10);
|
||||
color: $gray-l4;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
@extend %cont-text-sr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.course-actions {
|
||||
@extend %ui-depth3;
|
||||
position: static;
|
||||
width: flex-grid(2, 9);
|
||||
width: flex-grid(3, 9);
|
||||
padding-right: ($baseline/2);
|
||||
text-align: right;
|
||||
|
||||
// view live button
|
||||
.view-live-button {
|
||||
@extend %ui-depth3;
|
||||
@include transition(opacity $tmg-f2 ease-in-out 0);
|
||||
@include box-sizing(border-box);
|
||||
padding: ($baseline/2);
|
||||
opacity: 0.0;
|
||||
pointer-events: none;
|
||||
.value {
|
||||
|
||||
&:hover {
|
||||
opacity: 1.0;
|
||||
pointer-events: auto;
|
||||
.copy, .icon {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.icon {
|
||||
@extend %t-icon4;
|
||||
margin-right: ($baseline/2);
|
||||
}
|
||||
|
||||
.copy {
|
||||
@extend %t-copy-sub1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
.status-message {
|
||||
@extend %t-copy-sub1;
|
||||
background-color: $gray-l5;
|
||||
box-shadow: 0 2px 2px 0 $shadow inset;
|
||||
padding: ($baseline*0.75) $baseline;
|
||||
|
||||
&.has-actions {
|
||||
|
||||
.copy, .status-actions {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.copy {
|
||||
width: 65%;
|
||||
margin: 0 $baseline 0 0;
|
||||
}
|
||||
|
||||
.status-actions {
|
||||
width: 30%;
|
||||
text-align: right;
|
||||
|
||||
.button {
|
||||
@extend %btn-secondary-white;
|
||||
padding:($baseline/4) ($baseline/2);
|
||||
}
|
||||
|
||||
.icon,.button-copy {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.icon {
|
||||
@extend %t-icon4;
|
||||
margin-right: ($baseline/4);
|
||||
}
|
||||
|
||||
.button-copy {
|
||||
@extend %t-copy-sub1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UI: individual course listings
|
||||
.course-item {
|
||||
@include box-sizing(border-box);
|
||||
width: flex-grid(9, 9);
|
||||
position: relative;
|
||||
border-bottom: 1px solid $gray-l2;
|
||||
padding: $baseline;
|
||||
|
||||
// STATE: hover/focus
|
||||
&:hover {
|
||||
background: $paleYellow;
|
||||
|
||||
.course-actions {
|
||||
opacity: 1.0;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.course-title {
|
||||
color: $orange-d1;
|
||||
}
|
||||
|
||||
.course-metadata {
|
||||
opacity: 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
.course-link, .course-actions {
|
||||
@include box-sizing(border-box);
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
// encompassing course link
|
||||
.course-link {
|
||||
@extend %ui-depth2;
|
||||
width: flex-grid(6, 9);
|
||||
margin-right: flex-gutter();
|
||||
}
|
||||
|
||||
// course title
|
||||
.course-title {
|
||||
@extend %t-title4;
|
||||
margin: 0 ($baseline*2) ($baseline/4) 0;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
// course metadata
|
||||
.course-metadata {
|
||||
@extend %t-copy-sub1;
|
||||
@include transition(opacity $tmg-f1 ease-in-out 0);
|
||||
color: $gray;
|
||||
opacity: 0.75;
|
||||
|
||||
.metadata-item {
|
||||
display: inline-block;
|
||||
|
||||
&:after {
|
||||
content: "/";
|
||||
margin-left: ($baseline/10);
|
||||
margin-right: ($baseline/10);
|
||||
color: $gray-l4;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
@extend %cont-text-sr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.course-actions {
|
||||
@include transition(opacity $tmg-f2 ease-in-out 0);
|
||||
@extend %ui-depth3;
|
||||
position: static;
|
||||
width: flex-grid(3, 9);
|
||||
text-align: right;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
|
||||
.action {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-right: ($baseline/2);
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
@extend %t-action3;
|
||||
}
|
||||
|
||||
// view live button
|
||||
.view-button {
|
||||
@include box-sizing(border-box);
|
||||
padding: ($baseline/2);
|
||||
}
|
||||
|
||||
// course re-run button
|
||||
.action-rerun {
|
||||
margin-right: $baseline;
|
||||
}
|
||||
|
||||
.rerun-button {
|
||||
font-weight: 600;
|
||||
// TODO: sync up button styling and add secondary style here
|
||||
}
|
||||
}
|
||||
|
||||
// CASE: is processing
|
||||
&.is-processing {
|
||||
|
||||
.course-status .value {
|
||||
color: $gray-l2;
|
||||
}
|
||||
}
|
||||
|
||||
// CASE: has an error
|
||||
&.has-error {
|
||||
|
||||
.course-status {
|
||||
color: $red; // TODO: abstract this out to an error-based color variable
|
||||
}
|
||||
|
||||
~ .status-message {
|
||||
background: $red-l1; // TODO: abstract this out to an error-based color variable
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
// CASE: last course in listing
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
// ====================
|
||||
|
||||
// CASE: courses that are being processed
|
||||
.courses-processing {
|
||||
margin-bottom: ($baseline*2);
|
||||
border-bottom: 1px solid $gray-l3;
|
||||
padding-bottom: ($baseline*2);
|
||||
|
||||
// TODO: abstract this case out better with normal course listings
|
||||
.list-courses {
|
||||
border: none;
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.wrapper-course {
|
||||
@extend %ui-window;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.course-item {
|
||||
border: none;
|
||||
|
||||
// STATE: hover/focus
|
||||
&:hover {
|
||||
background: inherit;
|
||||
|
||||
.course-title {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// course details (replacement for course-link when a course cannot be linked)
|
||||
.course-details {
|
||||
@extend %ui-depth2;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: flex-grid(6, 9);
|
||||
margin-right: flex-gutter();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ====================
|
||||
|
||||
// ELEM: new user form
|
||||
.wrapper-create-course {
|
||||
|
||||
@@ -494,6 +681,5 @@
|
||||
margin-bottom: 0;
|
||||
padding: ($baseline*.5) ($baseline*1.5) 8px ($baseline*1.5);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user