diff --git a/cms/static/coffee/spec/views/course_info_spec.coffee b/cms/static/coffee/spec/views/course_info_spec.coffee
index de33b850833b..49ad3b23e7a4 100644
--- a/cms/static/coffee/spec/views/course_info_spec.coffee
+++ b/cms/static/coffee/spec/views/course_info_spec.coffee
@@ -54,17 +54,14 @@ define ["js/views/course_info_handout", "js/views/course_info_update", "js/model
@courseInfoEdit.$el.find('.save-button').click()
@cancelNewCourseInfo = (useCancelButton) ->
- spyOn(@courseInfoEdit.$modalCover, 'show').andCallThrough()
- spyOn(@courseInfoEdit.$modalCover, 'hide').andCallThrough()
-
@courseInfoEdit.onNew(@event)
- expect(@courseInfoEdit.$modalCover.show).toHaveBeenCalled()
+ spyOn(@courseInfoEdit.$modalCover, 'hide').andCallThrough()
spyOn(@courseInfoEdit.$codeMirror, 'getValue').andReturn('unsaved changes')
model = @collection.at(0)
spyOn(model, "save").andCallThrough()
- cancelEditingUpdate(useCancelButton)
+ cancelEditingUpdate(@courseInfoEdit, @courseInfoEdit.$modalCover, useCancelButton)
expect(@courseInfoEdit.$modalCover.hide).toHaveBeenCalled()
expect(model.save).not.toHaveBeenCalled()
@@ -73,28 +70,25 @@ define ["js/views/course_info_handout", "js/views/course_info_update", "js/model
@cancelExistingCourseInfo = (useCancelButton) ->
@createNewUpdate('existing update')
-
- spyOn(@courseInfoEdit.$modalCover, 'show').andCallThrough()
- spyOn(@courseInfoEdit.$modalCover, 'hide').andCallThrough()
@courseInfoEdit.$el.find('.edit-button').click()
- expect(@courseInfoEdit.$modalCover.show).toHaveBeenCalled()
+ spyOn(@courseInfoEdit.$modalCover, 'hide').andCallThrough()
spyOn(@courseInfoEdit.$codeMirror, 'getValue').andReturn('modification')
model = @collection.at(0)
spyOn(model, "save").andCallThrough()
-
- cancelEditingUpdate(useCancelButton)
+ model.id = "saved_to_server"
+ cancelEditingUpdate(@courseInfoEdit, @courseInfoEdit.$modalCover, useCancelButton)
expect(@courseInfoEdit.$modalCover.hide).toHaveBeenCalled()
expect(model.save).not.toHaveBeenCalled()
previewContents = @courseInfoEdit.$el.find('.update-contents').html()
expect(previewContents).toEqual('existing update')
- cancelEditingUpdate = (update, useCancelButton) ->
+ cancelEditingUpdate = (update, modalCover, useCancelButton) ->
if useCancelButton
update.$el.find('.cancel-button').click()
else
- $('.modal-cover').click()
+ modalCover.click()
afterEach ->
@xhrRestore()
diff --git a/cms/static/coffee/src/views/module_edit.coffee b/cms/static/coffee/src/views/module_edit.coffee
index 2faccdab2649..a9ffae7d137e 100644
--- a/cms/static/coffee/src/views/module_edit.coffee
+++ b/cms/static/coffee/src/views/module_edit.coffee
@@ -1,7 +1,7 @@
define ["backbone", "jquery", "underscore", "gettext", "xmodule",
- "js/views/feedback_notification", "js/views/metadata", "js/collections/metadata"
- "jquery.inputnumber"],
-(Backbone, $, _, gettext, XModule, NotificationView, MetadataView, MetadataCollection) ->
+ "js/views/feedback_notification", "js/views/metadata", "js/collections/metadata",
+ "js/utils/modal", "jquery.inputnumber"],
+(Backbone, $, _, gettext, XModule, NotificationView, MetadataView, MetadataCollection, ModalUtils) ->
class ModuleEdit extends Backbone.View
tagName: 'li'
className: 'component'
@@ -103,17 +103,12 @@ define ["backbone", "jquery", "underscore", "gettext", "xmodule",
event.preventDefault()
@$el.removeClass('editing')
@$component_editor().slideUp(150)
- @hideModal()
-
- hideModal: ->
- $modalCover = $(".modal-cover")
- $modalCover.hide()
- $modalCover.removeClass('is-fixed')
+ ModalUtils.hideModalCover()
clickEditButton: (event) ->
event.preventDefault()
@$el.addClass('editing')
- $(".modal-cover").show().addClass('is-fixed')
+ ModalUtils.showModalCover(true)
@$component_editor().slideDown(150)
@loadEdit()
diff --git a/cms/static/js/base.js b/cms/static/js/base.js
index 0ac99325998d..84121fccd7f2 100644
--- a/cms/static/js/base.js
+++ b/cms/static/js/base.js
@@ -1,10 +1,8 @@
require(["domReady", "jquery", "underscore", "gettext", "js/views/feedback_notification", "js/views/feedback_prompt",
- "jquery.ui", "jquery.timepicker", "jquery.leanModal", "jquery.form", "jquery.smoothScroll"],
- function(domReady, $, _, gettext, NotificationView, PromptView) {
+ "js/utils/get_date", "js/utils/check_for_cancel", "jquery.ui", "jquery.timepicker", "jquery.leanModal", "jquery.form", "jquery.smoothScroll"],
+ function(domReady, $, _, gettext, NotificationView, PromptView, DateUtils, BindCheckForCancel) {
var $body;
-var $modal;
-var $modalCover;
var $newComponentItem;
var $changedInput;
var $spinner;
@@ -14,8 +12,6 @@ var $newComponentButton;
domReady(function() {
$body = $('body');
- $modal = $('.history-modal');
- $modalCover = $('.modal-cover');
$newComponentItem = $('.new-component-item');
$newComponentTypePicker = $('.new-component');
@@ -26,8 +22,6 @@ domReady(function() {
$('.expand-collapse-icon').bind('click', toggleSubmodules);
$('.visibility-options').bind('change', setVisibility);
- $modal.bind('click', hideModal);
- $modalCover.bind('click', hideModal);
$body.on('click', '.embeddable-xml-input', function() {
$(this).select();
@@ -94,16 +88,12 @@ domReady(function() {
// tender feedback window scrolling
$('a.show-tender').bind('click', smoothScrollTop);
- // toggling footer additional support
- $('.cta-show-sock').bind('click', toggleSock);
-
// toggling overview section details
$(function() {
if ($('.courseware-section').length > 0) {
$('.toggle-button-sections').addClass('is-shown');
}
});
- $('.toggle-button-sections').bind('click', toggleSections);
// autosave when leaving input field
$body.on('change', '.subsection-display-name-input', saveSubsection);
@@ -126,24 +116,11 @@ domReady(function() {
$('.sync-date').bind('click', syncReleaseDate);
- // import form setup
- $('.view-import .file-input').bind('change', showImportSubmit);
- $('.view-import .choose-file-button, .view-import .choose-file-button-inline').bind('click', function(e) {
- e.preventDefault();
- $('.view-import .file-input').click();
- });
-
- $('.new-course-button').bind('click', addNewCourse);
// section date setting
$('.set-publish-date').bind('click', setSectionScheduleDate);
$('.edit-section-start-cancel').bind('click', cancelSetSectionScheduleDate);
- $('.edit-section-start-save').bind('click', saveSetSectionScheduleDate);
- $body.on('click', '.section-published-date .edit-button', editSectionPublishDate);
- $body.on('click', '.section-published-date .schedule-button', editSectionPublishDate);
- $body.on('click', '.edit-subsection-publish-settings .save-button', saveSetSectionScheduleDate);
- $body.on('click', '.edit-subsection-publish-settings .cancel-button', hideModal);
$body.on('change', '.edit-subsection-publish-settings .start-date', function() {
if ($('.edit-subsection-publish-settings').find('.start-time').val() == '') {
$('.edit-subsection-publish-settings').find('.start-time').val('12:00am');
@@ -183,58 +160,6 @@ function linkNewWindow(e) {
e.preventDefault();
}
-function toggleSections(e) {
- e.preventDefault();
-
- $section = $('.courseware-section');
- sectionCount = $section.length;
- $button = $(this);
- $labelCollapsed = $(' ' +
- gettext('Collapse All Sections') + '');
- $labelExpanded = $(' ' +
- gettext('Expand All Sections') + '');
-
- var buttonLabel = $button.hasClass('is-activated') ? $labelCollapsed : $labelExpanded;
- $button.toggleClass('is-activated').html(buttonLabel);
-
- if ($button.hasClass('is-activated')) {
- $section.addClass('collapsed');
- // first child in order to avoid the icons on the subsection lists which are not in the first child
- $section.find('header .expand-collapse-icon').removeClass('collapse').addClass('expand');
- } else {
- $section.removeClass('collapsed');
- // first child in order to avoid the icons on the subsection lists which are not in the first child
- $section.find('header .expand-collapse-icon').removeClass('expand').addClass('collapse');
- }
-}
-
-function editSectionPublishDate(e) {
- e.preventDefault();
- $modal = $('.edit-subsection-publish-settings').show();
- $modal.attr('data-id', $(this).attr('data-id'));
- $modal.find('.start-date').val($(this).attr('data-date'));
- $modal.find('.start-time').val($(this).attr('data-time'));
- if ($modal.find('.start-date').val() == '' && $modal.find('.start-time').val() == '') {
- $modal.find('.save-button').hide();
- }
- $modal.find('.section-name').html('"' + $(this).closest('.courseware-section').find('.section-name-span').text() + '"');
- $modalCover.show();
-}
-
-function showImportSubmit(e) {
- var filepath = $(this).val();
- if (filepath.substr(filepath.length - 6, 6) == 'tar.gz') {
- $('.error-block').hide();
- $('.file-name').html($(this).val().replace('C:\\fakepath\\', ''));
- $('.file-name-block').show();
- $('.view-import .choose-file-button').hide();
- $('.submit-button').show();
- $('.progress').show();
- } else {
- $('.error-block').html(gettext('File format not supported. Please upload a file with a tar.gz extension.')).show();
- }
-}
-
function syncReleaseDate(e) {
e.preventDefault();
$(this).closest('.notice').hide();
@@ -242,22 +167,6 @@ function syncReleaseDate(e) {
$("#start_time").val("");
}
-function getDatetime(datepickerInput, timepickerInput) {
- // given a pair of inputs (datepicker and timepicker), return a JS Date
- // object that corresponds to the datetime that they represent. Assume
- // UTC timezone, NOT the timezone of the user's browser.
- var date = $(datepickerInput).datepicker("getDate");
- var time = $(timepickerInput).timepicker("getTime");
- if(date && time) {
- return new Date(Date.UTC(
- date.getFullYear(), date.getMonth(), date.getDate(),
- time.getHours(), time.getMinutes()
- ));
- } else {
- return null;
- }
-}
-
function autosaveInput(e) {
var self = this;
if (this.saveTimer) {
@@ -298,7 +207,7 @@ function saveSubsection() {
// get datetimes for start and due, stick into metadata
_(["start", "due"]).each(function(name) {
- var datetime = getDatetime(
+ var datetime = DateUtils(
document.getElementById(name+"_date"),
document.getElementById(name+"_time")
);
@@ -319,9 +228,6 @@ function saveSubsection() {
success: function() {
$spinner.delay(500).fadeOut(150);
$changedInput = null;
- },
- error: function() {
- showToastMessage(gettext('There has been an error while saving your changes.'));
}
});
}
@@ -410,44 +316,6 @@ function _deleteItem($el, type) {
confirm.show();
}
-function hideModal(e) {
- if (e) {
- e.preventDefault();
- }
- // Unit editors do not want the modal cover to hide when users click outside
- // of the editor. Users must press Cancel or Save to exit the editor.
- // module_edit adds and removes the "is-fixed" class.
- if (!$modalCover.hasClass("is-fixed")) {
- $(".modal, .edit-subsection-publish-settings").hide();
- $modalCover.hide();
- }
-}
-
-function toggleSock(e) {
- e.preventDefault();
-
- var $btnLabel = $(this).find('.copy');
- var $sock = $('.wrapper-sock');
- var $sockContent = $sock.find('.wrapper-inner');
-
- $sock.toggleClass('is-shown');
- $sockContent.toggle('fast');
-
- $.smoothScroll({
- offset: -200,
- easing: 'swing',
- speed: 1000,
- scrollElement: null,
- scrollTarget: $sock
- });
-
- if ($sock.hasClass('is-shown')) {
- $btnLabel.text(gettext('Hide Studio Help'));
- } else {
- $btnLabel.text(gettext('Looking for Help with Studio?'));
- }
-}
-
function toggleSubmodules(e) {
e.preventDefault();
$(this).toggleClass('expand').toggleClass('collapse');
@@ -459,15 +327,6 @@ function setVisibility(e) {
$(e.target).closest('.option').addClass('checked');
}
-function editComponent(e) {
- e.preventDefault();
- $(this).closest('.xmodule_edit').addClass('editing').find('.component-editor').slideDown(150);
-}
-
-function closeComponentEditor(e) {
- e.preventDefault();
- $(this).closest('.xmodule_edit').removeClass('editing').find('.component-editor').slideUp(150);
-}
function showDateSetter(e) {
e.preventDefault();
@@ -497,41 +356,7 @@ function hideAlert(e) {
$(this).closest('.wrapper-alert').removeClass('is-shown');
}
-function showToastMessage(message, $button, lifespan) {
- var $toast = $('
' + 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(); - }); -} - -function validateRequiredField(msg) { - return msg.length === 0 ? gettext('Required field.') : ''; -} - -function setNewCourseFieldInErr(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'); - } - } -}; - -function saveNewCourse(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) { - return; - } - - var $newCourseForm = $(this).closest('#create-course-form'); - var display_name = $newCourseForm.find('.new-course-name').val(); - var org = $newCourseForm.find('.new-course-org').val(); - 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 - }); - - $.post('/create_new_course', { - 'org': org, - 'number': number, - 'display_name': display_name, - 'run': run - }, - function(data) { - if (data.id !== undefined) { - window.location = '/' + data.id.replace(/.*:\/\//, ''); - } else if (data.ErrMsg !== undefined) { - $('.wrap-error').addClass('is-shown'); - $('#course_creation_error').html('' + data.ErrMsg + '
'); - $('.new-course-save').addClass('is-disabled'); - } - } - ); -} - -function cancelNewCourse(e) { - e.preventDefault(); - $('.new-course-button').removeClass('is-disabled'); - $('.wrapper-create-course').removeClass('is-shown'); - // Clear out existing fields and errors - _.each( - ['.new-course-name', '.new-course-org', '.new-course-number', '.new-course-run'], - function(field) { - $(field).val(''); - } - ); - $('#course_creation_error').html(''); - $('.wrap-error').removeClass('is-shown'); - $('.new-course-save').off('click'); -} - function addNewSubsection(e) { e.preventDefault(); var $section = $(this).closest('.courseware-section'); @@ -758,9 +417,7 @@ function addNewSubsection(e) { $newSubsection.find('.new-subsection-form').bind('submit', saveNewSubsection); $cancelButton.bind('click', cancelNewSubsection); - $body.bind('keyup', { - $cancelButton: $cancelButton - }, checkForCancel); + BindCheckForCancel($cancelButton); } function saveNewSubsection(e) { @@ -806,65 +463,7 @@ function cancelSetSectionScheduleDate(e) { $(this).parent().siblings("h4").show(); } -function saveSetSectionScheduleDate(e) { - e.preventDefault(); - - var datetime = getDatetime( - $('.edit-subsection-publish-settings .start-date'), - $('.edit-subsection-publish-settings .start-time') - ); - - var id = $modal.attr('data-id'); - - analytics.track('Edited Section Release Date', { - 'course': course_location_analytics, - 'id': id, - 'start': datetime - }); - var saving = new NotificationView.Mini({ - title: gettext("Saving…") - }); - saving.show(); - // call into server to commit the new order - $.ajax({ - url: "/save_item", - type: "POST", - dataType: "json", - contentType: "application/json", - data: JSON.stringify({ - 'id': id, - 'metadata': { - 'start': datetime - } - }) - }).success(function() { - var pad2 = function(number) { - // pad a number to two places: useful for formatting months, days, hours, etc - // when displaying a date/time - return (number < 10 ? '0' : '') + number; - }; - - var $thisSection = $('.courseware-section[data-id="' + id + '"]'); - var html = _.template( - '' + - '' + gettext("Will Release:") + ' ' + - gettext("{month}/{day}/{year} at {hour}:{minute} UTC") + - '' + - '' + - gettext("Edit") + - '', - {year: datetime.getUTCFullYear(), month: pad2(datetime.getUTCMonth() + 1), day: pad2(datetime.getUTCDate()), - hour: pad2(datetime.getUTCHours()), minute: pad2(datetime.getUTCMinutes()), - id: id}, - {interpolate: /\{(.+?)\}/g}); - $thisSection.find('.section-published-date').html(html); - hideModal(); - saving.hide(); - }); -} - // Add to window object for unit test (overview_spec). - window.saveSetSectionScheduleDate = saveSetSectionScheduleDate; window.deleteSection = deleteSection; }); // end require() diff --git a/cms/static/js/index.js b/cms/static/js/index.js new file mode 100644 index 000000000000..f2d2fd3a9a51 --- /dev/null +++ b/cms/static/js/index.js @@ -0,0 +1,161 @@ +require(["domReady", "jquery", "underscore", "js/utils/check_for_cancel"], + function (domReady, $, _, BindCheckForCancel) { + 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) { + return; + } + + var $newCourseForm = $(this).closest('#create-course-form'); + var display_name = $newCourseForm.find('.new-course-name').val(); + var org = $newCourseForm.find('.new-course-org').val(); + 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 + }); + + $.post('/create_new_course', { + 'org': org, + 'number': number, + 'display_name': display_name, + 'run': run + }, + function (data) { + if (data.id !== undefined) { + window.location = '/' + data.id.replace(/.*:\/\//, ''); + } else if (data.ErrMsg !== undefined) { + $('.wrap-error').addClass('is-shown'); + $('#course_creation_error').html('' + data.ErrMsg + '
'); + $('.new-course-save').addClass('is-disabled'); + } + } + ); + }; + + var cancelNewCourse = function (e) { + e.preventDefault(); + $('.new-course-button').removeClass('is-disabled'); + $('.wrapper-create-course').removeClass('is-shown'); + // Clear out existing fields and errors + _.each( + ['.new-course-name', '.new-course-org', '.new-course-number', '.new-course-run'], + function (field) { + $(field).val(''); + } + ); + $('#course_creation_error').html(''); + $('.wrap-error').removeClass('is-shown'); + $('.new-course-save').off('click'); + }; + + var addNewCourse = function (e) { + e.preventDefault(); + $('.new-course-button').addClass('is-disabled'); + $('.new-course-save').addClass('is-disabled'); + var $newCourse = $('.wrapper-create-course').addClass('is-shown'); + var $cancelButton = $newCourse.find('.new-course-cancel'); + var $courseName = $('.new-course-name'); + $courseName.focus().select(); + $('.new-course-save').on('click', saveNewCourse); + $cancelButton.bind('click', cancelNewCourse); + BindCheckForCancel($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 (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('' + 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'); + } + } + }; + + + domReady(function () { + $('.new-course-button').bind('click', addNewCourse); + }); + }); \ No newline at end of file diff --git a/cms/static/js/sock.js b/cms/static/js/sock.js new file mode 100644 index 000000000000..03d366c97b27 --- /dev/null +++ b/cms/static/js/sock.js @@ -0,0 +1,32 @@ +require(["domReady", "jquery", "jquery.smoothScroll"], + function (domReady, $) { + var toggleSock = function (e) { + e.preventDefault(); + + var $btnLabel = $(this).find('.copy'); + var $sock = $('.wrapper-sock'); + var $sockContent = $sock.find('.wrapper-inner'); + + $sock.toggleClass('is-shown'); + $sockContent.toggle('fast'); + + $.smoothScroll({ + offset: -200, + easing: 'swing', + speed: 1000, + scrollElement: null, + scrollTarget: $sock + }); + + if ($sock.hasClass('is-shown')) { + $btnLabel.text(gettext('Hide Studio Help')); + } else { + $btnLabel.text(gettext('Looking for Help with Studio?')); + } + }; + + domReady(function () { + // toggling footer additional support + $('.cta-show-sock').bind('click', toggleSock); + }); + }); \ No newline at end of file diff --git a/cms/static/js/utils/check_for_cancel.js b/cms/static/js/utils/check_for_cancel.js new file mode 100644 index 000000000000..bb8ca86227f5 --- /dev/null +++ b/cms/static/js/utils/check_for_cancel.js @@ -0,0 +1,17 @@ +define(["jquery"], function($) { + var $body = $('body'); + var checkForCancel = function (e) { + if (e.which == 27) { + $body.unbind('keyup', checkForCancel); + e.data.$cancelButton.click(); + } + }; + + var bindCheckForCancel = function (cancelButton) { + $body.bind('keyup', { + $cancelButton: cancelButton + }, checkForCancel); + }; + + return bindCheckForCancel; +}); \ No newline at end of file diff --git a/cms/static/js/utils/get_date.js b/cms/static/js/utils/get_date.js new file mode 100644 index 000000000000..795441585c6d --- /dev/null +++ b/cms/static/js/utils/get_date.js @@ -0,0 +1,18 @@ +define(["jquery"], function($) { + var getDate = function (datepickerInput, timepickerInput) { + // given a pair of inputs (datepicker and timepicker), return a JS Date + // object that corresponds to the datetime.js that they represent. Assume + // UTC timezone, NOT the timezone of the user's browser. + var date = $(datepickerInput).datepicker("getDate"); + var time = $(timepickerInput).timepicker("getTime"); + if(date && time) { + return new Date(Date.UTC( + date.getFullYear(), date.getMonth(), date.getDate(), + time.getHours(), time.getMinutes() + )); + } else { + return null; + } + }; + return getDate; +}); \ No newline at end of file diff --git a/cms/static/js/utils/modal.js b/cms/static/js/utils/modal.js new file mode 100644 index 000000000000..617dc4988dd1 --- /dev/null +++ b/cms/static/js/utils/modal.js @@ -0,0 +1,81 @@ +define(["jquery"], function($) { + /** + * Hides the modal and modal cover, using the standard selectors. + * Note though that the class "is-fixed" on the modal cover + * prevents the closing operation. + */ + var hideModal = function(e) { + if (e) { + e.preventDefault(); + } + + var $modalCover = getModalCover(); + + // Unit editors do not want the modal cover to hide when users click outside + // of the editor. Users must press Cancel or Save to exit the editor. + // module_edit adds and removes the "is-fixed" class. + if (!$modalCover.hasClass("is-fixed")) { + getModal().hide(); + hideModalCover($modalCover); + } + }; + + /** + * Hides just the modal cover. Caller can pass in a specific + * element as the modal cover, otherwise the standard selector will be used. + * + * This method also unbinds the click handler on the modal cover. + */ + var hideModalCover = function (modalCover) { + if (modalCover == undefined) { + modalCover = getModalCover(); + } + modalCover.hide(); + modalCover.removeClass("is-fixed"); + modalCover.unbind('click'); + }; + + /** + * Shows the modal and modal cover, using the standard selectors. + */ + var showModal = function () { + getModal().show(); + showModalCover(); + }; + + /** + * Shows just the modal cover. The caller can optionally choose + * to have the class "is-fixed" added to the cover, and + * the user can also choose to specify a custom click handler + * for the modal cover. + * + * This method returns the modal cover element. + */ + var showModalCover = function(addFixed, clickHandler) { + var $modalCover = getModalCover(); + $modalCover.show(); + if (addFixed) { + $modalCover.addClass("is-fixed"); + } + if (clickHandler) { + $modalCover.bind('click', clickHandler); + } + else { + $modalCover.bind('click', hideModal); + } + return $modalCover; + }; + + var getModalCover = function () { + return $('.modal-cover'); + }; + + var getModal = function () { + return $(".modal, .showAsModal"); + }; + + return { + showModal: showModal, hideModal: hideModal, + showModalCover: showModalCover, hideModalCover: hideModalCover + }; +}); \ No newline at end of file diff --git a/cms/static/js/views/course_info_handout.js b/cms/static/js/views/course_info_handout.js index d22de768b404..95e5a7c76753 100644 --- a/cms/static/js/views/course_info_handout.js +++ b/cms/static/js/views/course_info_handout.js @@ -1,7 +1,6 @@ -define(["backbone", "underscore", "codemirror", "js/views/feedback_notification", "js/views/course_info_helper"], - function(Backbone, _, CodeMirror, NotificationView, CourseInfoHelper) { +define(["backbone", "underscore", "codemirror", "js/views/feedback_notification", "js/views/course_info_helper", "js/utils/modal"], + function(Backbone, _, CodeMirror, NotificationView, CourseInfoHelper, ModalUtils) { - var $modalCover = $(".modal-cover"); // the handouts view is dumb right now; it needs tied to a model and all that jazz var CourseInfoHandoutsView = Backbone.View.extend({ // collection is CourseUpdateCollection @@ -47,10 +46,7 @@ define(["backbone", "underscore", "codemirror", "js/views/feedback_notification" this.$codeMirror = CourseInfoHelper.editWithCodeMirror( self.model, 'data', self.options['base_asset_url'], this.$editor.get(0)); - $modalCover.show(); - $modalCover.bind('click', function() { - self.closeEditor(); - }); + this.$modalCover = ModalUtils.showModalCover(false, function() { self.closeEditor() }); }, onSave: function(event) { @@ -81,8 +77,7 @@ define(["backbone", "underscore", "codemirror", "js/views/feedback_notification" closeEditor: function() { this.$form.hide(); - $modalCover.unbind('click'); - $modalCover.hide(); + ModalUtils.hideModalCover(this.$modalCover); this.$form.find('.CodeMirror').remove(); this.$codeMirror = null; } diff --git a/cms/static/js/views/course_info_update.js b/cms/static/js/views/course_info_update.js index 256f63624a5d..b361c522ffe5 100644 --- a/cms/static/js/views/course_info_update.js +++ b/cms/static/js/views/course_info_update.js @@ -1,6 +1,6 @@ define(["backbone", "underscore", "codemirror", "js/models/course_update", - "js/views/feedback_prompt", "js/views/feedback_notification", "js/views/course_info_helper"], - function(Backbone, _, CodeMirror, CourseUpdateModel, PromptView, NotificationView, CourseInfoHelper) { + "js/views/feedback_prompt", "js/views/feedback_notification", "js/views/course_info_helper", "js/utils/modal"], + function(Backbone, _, CodeMirror, CourseUpdateModel, PromptView, NotificationView, CourseInfoHelper, ModalUtils) { var CourseInfoUpdateView = Backbone.View.extend({ // collection is CourseUpdateCollection @@ -17,8 +17,6 @@ define(["backbone", "underscore", "codemirror", "js/models/course_update", this.render(); // when the client refetches the updates as a whole, re-render them this.listenTo(this.collection, 'reset', this.render); - - this.$modalCover = $(".modal-cover"); }, render: function () { @@ -64,9 +62,8 @@ define(["backbone", "underscore", "codemirror", "js/models/course_update", $newForm.addClass('editing'); this.$currentPost = $newForm.closest('li'); - this.$modalCover.show(); - this.$modalCover.bind('click', function() { - self.closeEditor(true); + this.$modalCover = ModalUtils.showModalCover(false, function() { + self.closeEditor(true) }); $('.date').datepicker('destroy'); @@ -91,7 +88,7 @@ define(["backbone", "underscore", "codemirror", "js/models/course_update", ele.remove(); } }); - this.closeEditor(); + this.closeEditor(false); analytics.track('Saved Course Update', { 'course': course_location_analytics, @@ -121,10 +118,11 @@ define(["backbone", "underscore", "codemirror", "js/models/course_update", this.$codeMirror = CourseInfoHelper.editWithCodeMirror( targetModel, 'content', self.options['base_asset_url'], $textArea.get(0)); - this.$modalCover.show(); - this.$modalCover.bind('click', function() { - self.closeEditor(false); - }); + this.$modalCover = ModalUtils.showModalCover(false, + function() { + self.closeEditor(false) + } + ); }, onDelete: function(event) { @@ -198,8 +196,7 @@ define(["backbone", "underscore", "codemirror", "js/models/course_update", this.$currentPost.find('.CodeMirror').remove(); } - this.$modalCover.unbind('click'); - this.$modalCover.hide(); + ModalUtils.hideModalCover(this.$modalCover); this.$codeMirror = null; }, diff --git a/cms/static/js/views/import.js b/cms/static/js/views/import.js index 14d2c6a22e60..238e953a92a5 100644 --- a/cms/static/js/views/import.js +++ b/cms/static/js/views/import.js @@ -2,8 +2,8 @@ * Course import-related js. */ define( - ["jquery", "underscore", "gettext"], - function($, _, gettext) { + ["domReady", "jquery", "underscore", "gettext"], + function(domReady, $, _, gettext) { "use strict"; @@ -146,5 +146,28 @@ var CourseImport = { }; +var showImportSubmit = function (e) { + var filepath = $(this).val(); + if (filepath.substr(filepath.length - 6, 6) == 'tar.gz') { + $('.error-block').hide(); + $('.file-name').html($(this).val().replace('C:\\fakepath\\', '')); + $('.file-name-block').show(); + $('.view-import .choose-file-button').hide(); + $('.submit-button').show(); + $('.progress').show(); + } else { + $('.error-block').html(gettext('File format not supported. Please upload a file with atar.gz extension.')).show();
+ }
+};
+
+domReady(function () {
+ // import form setup
+ $('.view-import .file-input').bind('change', showImportSubmit);
+ $('.view-import .choose-file-button, .view-import .choose-file-button-inline').bind('click', function (e) {
+ e.preventDefault();
+ $('.view-import .file-input').click();
+ });
+});
+
return CourseImport;
});
diff --git a/cms/static/js/views/overview.js b/cms/static/js/views/overview.js
index 6e18dfdee266..dc095dbcb0c4 100644
--- a/cms/static/js/views/overview.js
+++ b/cms/static/js/views/overview.js
@@ -1,5 +1,106 @@
-define(["domReady", "jquery", "jquery.ui", "gettext", "js/views/feedback_notification", "draggabilly"],
- function (domReady, $, ui, gettext, NotificationView, Draggabilly) {
+define(["domReady", "jquery", "jquery.ui", "underscore", "gettext", "js/views/feedback_notification", "draggabilly", "js/utils/modal", "js/utils/get_date"],
+ function (domReady, $, ui, _, gettext, NotificationView, Draggabilly, ModalUtils, DateUtils) {
+
+ var modalSelector = '.edit-subsection-publish-settings';
+
+ var toggleSections = function(e) {
+ e.preventDefault();
+
+ var $section = $('.courseware-section');
+ var $button = $(this);
+ var $labelCollapsed = $(' ' +
+ gettext('Collapse All Sections') + '');
+ var $labelExpanded = $(' ' +
+ gettext('Expand All Sections') + '');
+
+ var buttonLabel = $button.hasClass('is-activated') ? $labelCollapsed : $labelExpanded;
+ $button.toggleClass('is-activated').html(buttonLabel);
+
+ if ($button.hasClass('is-activated')) {
+ $section.addClass('collapsed');
+ // first child in order to avoid the icons on the subsection lists which are not in the first child
+ $section.find('header .expand-collapse-icon').removeClass('collapse').addClass('expand');
+ } else {
+ $section.removeClass('collapsed');
+ // first child in order to avoid the icons on the subsection lists which are not in the first child
+ $section.find('header .expand-collapse-icon').removeClass('expand').addClass('collapse');
+ }
+ };
+
+
+ var editSectionPublishDate = function (e) {
+ e.preventDefault();
+ var $modal = $(modalSelector);
+ $modal.attr('data-id', $(this).attr('data-id'));
+ $modal.find('.start-date').val($(this).attr('data-date'));
+ $modal.find('.start-time').val($(this).attr('data-time'));
+ if ($modal.find('.start-date').val() == '' && $modal.find('.start-time').val() == '') {
+ $modal.find('.save-button').hide();
+ }
+ $modal.find('.section-name').html('"' + $(this).closest('.courseware-section').find('.section-name-span').text() + '"');
+ ModalUtils.showModal();
+ };
+
+ function saveSetSectionScheduleDate(e) {
+ e.preventDefault();
+
+ var datetime = DateUtils(
+ $('.edit-subsection-publish-settings .start-date'),
+ $('.edit-subsection-publish-settings .start-time')
+ );
+
+ var id = $(modalSelector).attr('data-id');
+
+ analytics.track('Edited Section Release Date', {
+ 'course': course_location_analytics,
+ 'id': id,
+ 'start': datetime
+ });
+
+ var saving = new NotificationView.Mini({
+ title: gettext("Saving…")
+ });
+ saving.show();
+ // call into server to commit the new order
+ $.ajax({
+ url: "/save_item",
+ type: "POST",
+ dataType: "json",
+ contentType: "application/json",
+ data: JSON.stringify({
+ 'id': id,
+ 'metadata': {
+ 'start': datetime
+ }
+ })
+ }).success(function() {
+ var pad2 = function(number) {
+ // pad a number to two places: useful for formatting months, days, hours, etc
+ // when displaying a date/time
+ return (number < 10 ? '0' : '') + number;
+ };
+
+ var $thisSection = $('.courseware-section[data-id="' + id + '"]');
+ var html = _.template(
+ '' +
+ '' + gettext("Will Release:") + ' ' +
+ gettext("{month}/{day}/{year} at {hour}:{minute} UTC") +
+ '' +
+ '' +
+ gettext("Edit") +
+ '',
+ {year: datetime.getUTCFullYear(), month: pad2(datetime.getUTCMonth() + 1), day: pad2(datetime.getUTCDate()),
+ hour: pad2(datetime.getUTCHours()), minute: pad2(datetime.getUTCMinutes()),
+ id: id},
+ {interpolate: /\{(.+?)\}/g});
+ $thisSection.find('.section-published-date').html(html);
+ ModalUtils.hideModal();
+ saving.hide();
+ });
+ }
+ // Add to window object for unit test (overview_spec).
+ window.saveSetSectionScheduleDate = saveSetSectionScheduleDate;
+
var overviewDragger = {
droppableClasses: 'drop-target drop-target-prepend drop-target-before drop-target-after',
@@ -295,6 +396,13 @@ define(["domReady", "jquery", "jquery.ui", "gettext", "js/views/feedback_notific
};
domReady(function() {
+ $('.toggle-button-sections').bind('click', toggleSections);
+ var $body = $('body');
+ $body.on('click', '.section-published-date .edit-button', editSectionPublishDate);
+ $body.on('click', '.section-published-date .schedule-button', editSectionPublishDate);
+ $body.on('click', '.edit-subsection-publish-settings .save-button', saveSetSectionScheduleDate);
+ $body.on('click', '.edit-subsection-publish-settings .cancel-button', ModalUtils.hideModal);
+
// Section
overviewDragger.makeDraggable(
'.courseware-section',
diff --git a/cms/templates/asset_index.html b/cms/templates/asset_index.html
index 9301e857f217..909fc73c5e9f 100644
--- a/cms/templates/asset_index.html
+++ b/cms/templates/asset_index.html
@@ -18,29 +18,25 @@
+ <%include file="widgets/sock.html" />
% endif
- <%include file="widgets/footer.html" />
+<%include file="widgets/footer.html" />
<%include file="widgets/tender.html" />
diff --git a/cms/templates/index.html b/cms/templates/index.html
index df8459e448c5..e6d146355550 100644
--- a/cms/templates/index.html
+++ b/cms/templates/index.html
@@ -7,7 +7,7 @@
<%block name="jsextra">