diff --git a/cms/static/coffee/spec/views/course_info_spec.coffee b/cms/static/coffee/spec/views/course_info_spec.coffee
index de33b85083..49ad3b23e7 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/spec/views/overview_spec.coffee b/cms/static/coffee/spec/views/overview_spec.coffee
index c5cee89866..1dea609f12 100644
--- a/cms/static/coffee/spec/views/overview_spec.coffee
+++ b/cms/static/coffee/spec/views/overview_spec.coffee
@@ -1,5 +1,5 @@
define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base", "date", "jquery.timepicker"],
-(OverviewDragger, Notification, sinon) ->
+(Overview, Notification, sinon) ->
describe "Course Overview", ->
beforeEach ->
@@ -62,9 +62,9 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base
"""
- spyOn(window, 'saveSetSectionScheduleDate').andCallThrough()
+ spyOn(Overview, 'saveSetSectionScheduleDate').andCallThrough()
# Have to do this here, as it normally gets bound in document.ready()
- $('a.save-button').click(saveSetSectionScheduleDate)
+ $('a.save-button').click(Overview.saveSetSectionScheduleDate)
$('a.delete-section-button').click(deleteSection)
$(".edit-subsection-publish-settings .start-date").datepicker()
@@ -75,7 +75,7 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base
requests = @requests = []
@xhr.onCreate = (req) -> requests.push(req)
- OverviewDragger.makeDraggable(
+ Overview.overviewDragger.makeDraggable(
'.unit',
'.unit-drag-handle',
'ol.sortable-unit-list',
@@ -90,7 +90,7 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base
it "should save model when save is clicked", ->
$('a.edit-button').click()
$('a.save-button').click()
- expect(saveSetSectionScheduleDate).toHaveBeenCalled()
+ expect(Overview.saveSetSectionScheduleDate).toHaveBeenCalled()
it "should show a confirmation on save", ->
$('a.edit-button').click()
@@ -120,7 +120,7 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base
$ele.offset(
top: $ele.offset().top + 10, left: $ele.offset().left
)
- destination = OverviewDragger.findDestination($ele, 1)
+ destination = Overview.overviewDragger.findDestination($ele, 1)
expect(destination.ele).toBe($('#unit-2'))
expect(destination.attachMethod).toBe('before')
@@ -130,7 +130,7 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base
top: $('#unit-4').offset().top + 8
left: $ele.offset().left
)
- destination = OverviewDragger.findDestination($ele, 1)
+ destination = Overview.overviewDragger.findDestination($ele, 1)
expect(destination.ele).toBe($('#unit-4'))
# Dragging down into first element, we have a fudge factor makes it easier to drag at beginning.
expect(destination.attachMethod).toBe('before')
@@ -139,7 +139,7 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base
top: $('#unit-4').offset().top + 12
left: $ele.offset().left
)
- destination = OverviewDragger.findDestination($ele, 1)
+ destination = Overview.overviewDragger.findDestination($ele, 1)
expect(destination.ele).toBe($('#unit-4'))
expect(destination.attachMethod).toBe('after')
@@ -149,7 +149,7 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base
top: $('#unit-3').offset().bottom + 4
left: $ele.offset().left
)
- destination = OverviewDragger.findDestination($ele, -1)
+ destination = Overview.overviewDragger.findDestination($ele, -1)
expect(destination.ele).toBe($('#unit-3'))
# Dragging down up into last element, we have a fudge factor makes it easier to drag at beginning.
expect(destination.attachMethod).toBe('after')
@@ -158,7 +158,7 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base
top: $('#unit-3').offset().top + 4
left: $ele.offset().left
)
- destination = OverviewDragger.findDestination($ele, -1)
+ destination = Overview.overviewDragger.findDestination($ele, -1)
expect(destination.ele).toBe($('#unit-3'))
expect(destination.attachMethod).toBe('before')
@@ -168,7 +168,7 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base
top: $('#subsection-3').offset().top + 10
left: $ele.offset().left
)
- destination = OverviewDragger.findDestination($ele, 1)
+ destination = Overview.overviewDragger.findDestination($ele, 1)
expect(destination.ele).toBe($('#subsection-list-3'))
expect(destination.attachMethod).toBe('prepend')
@@ -177,7 +177,7 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base
$ele.offset(
top: $ele.offset().top + 200, left: $ele.offset().left
)
- destination = OverviewDragger.findDestination($ele, 1)
+ destination = Overview.overviewDragger.findDestination($ele, 1)
expect(destination).toEqual(
ele: null
attachMethod: ""
@@ -190,21 +190,21 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base
top: $('#subsection-2').offset().top + 3
left: $ele.offset().left
)
- destination = OverviewDragger.findDestination($ele, 1)
+ destination = Overview.overviewDragger.findDestination($ele, 1)
expect(destination.ele).toBe($('#subsection-list-2'))
expect(destination.parentList).toBe($('#subsection-2'))
expect(destination.attachMethod).toBe('prepend')
describe "onDragStart", ->
it "sets the dragState to its default values", ->
- expect(OverviewDragger.dragState).toEqual({})
+ expect(Overview.overviewDragger.dragState).toEqual({})
# Call with some dummy data
- OverviewDragger.onDragStart(
+ Overview.overviewDragger.onDragStart(
{element: $('#unit-1')},
null,
null
)
- expect(OverviewDragger.dragState).toEqual(
+ expect(Overview.overviewDragger.dragState).toEqual(
dropDestination: null,
attachMethod: '',
parentList: null,
@@ -214,7 +214,7 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base
it "collapses expanded elements", ->
expect($('#subsection-1')).not.toHaveClass('collapsed')
- OverviewDragger.onDragStart(
+ Overview.overviewDragger.onDragStart(
{element: $('#subsection-1')},
null,
null
@@ -233,7 +233,7 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base
$ele.offset(
top: dragY, left: dragX
)
- OverviewDragger.onDragMove(
+ Overview.overviewDragger.onDragMove(
{element: $ele, dragPoint:
{y: dragY}}, '', {clientX: dragX}
)
@@ -246,7 +246,7 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base
$ele.offset(
top: dragY, left: $ele.offset().left
)
- OverviewDragger.onDragMove(
+ Overview.overviewDragger.onDragMove(
{element: $ele, dragPoint:
{y: dragY}}, '', {clientX: $ele.offset().left - 3}
)
@@ -254,33 +254,33 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base
expect($ele).not.toHaveClass('valid-drop')
it "scrolls up if necessary", ->
- OverviewDragger.onDragMove(
+ Overview.overviewDragger.onDragMove(
{element: $('#unit-1')}, '', {clientY: 2}
)
expect(@scrollSpy).toHaveBeenCalledWith(0, -10)
it "scrolls down if necessary", ->
- OverviewDragger.onDragMove(
+ Overview.overviewDragger.onDragMove(
{element: $('#unit-1')}, '', {clientY: (window.innerHeight - 5)}
)
expect(@scrollSpy).toHaveBeenCalledWith(0, 10)
describe "onDragEnd", ->
beforeEach ->
- @reorderSpy = spyOn(OverviewDragger, 'handleReorder')
+ @reorderSpy = spyOn(Overview.overviewDragger, 'handleReorder')
afterEach ->
@reorderSpy.reset()
it "calls handleReorder on a successful drag", ->
- OverviewDragger.dragState.dropDestination = $('#unit-2')
- OverviewDragger.dragState.attachMethod = "before"
- OverviewDragger.dragState.parentList = $('#subsection-1')
+ Overview.overviewDragger.dragState.dropDestination = $('#unit-2')
+ Overview.overviewDragger.dragState.attachMethod = "before"
+ Overview.overviewDragger.dragState.parentList = $('#subsection-1')
$('#unit-1').offset(
top: $('#unit-1').offset().top + 10
left: $('#unit-1').offset().left
)
- OverviewDragger.onDragEnd(
+ Overview.overviewDragger.onDragEnd(
{element: $('#unit-1')},
null,
{clientX: $('#unit-1').offset().left}
@@ -288,15 +288,15 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base
expect(@reorderSpy).toHaveBeenCalled()
it "clears out the drag state", ->
- OverviewDragger.onDragEnd(
+ Overview.overviewDragger.onDragEnd(
{element: $('#unit-1')},
null,
null
)
- expect(OverviewDragger.dragState).toEqual({})
+ expect(Overview.overviewDragger.dragState).toEqual({})
it "sets the element to the correct position", ->
- OverviewDragger.onDragEnd(
+ Overview.overviewDragger.onDragEnd(
{element: $('#unit-1')},
null,
null
@@ -308,7 +308,7 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base
it "expands an element if it was collapsed on drag start", ->
$('#subsection-1').addClass('collapsed')
$('#subsection-1').addClass('expand-on-drop')
- OverviewDragger.onDragEnd(
+ Overview.overviewDragger.onDragEnd(
{element: $('#subsection-1')},
null,
null
@@ -318,10 +318,10 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base
it "expands a collapsed element when something is dropped in it", ->
$('#subsection-2').addClass('collapsed')
- OverviewDragger.dragState.dropDestination = $('#list-2')
- OverviewDragger.dragState.attachMethod = "prepend"
- OverviewDragger.dragState.parentList = $('#subsection-2')
- OverviewDragger.onDragEnd(
+ Overview.overviewDragger.dragState.dropDestination = $('#list-2')
+ Overview.overviewDragger.dragState.attachMethod = "prepend"
+ Overview.overviewDragger.dragState.parentList = $('#subsection-2')
+ Overview.overviewDragger.onDragEnd(
{element: $('#unit-1')},
null,
{clientX: $('#unit-1').offset().left}
@@ -344,15 +344,15 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base
@clock.restore()
it "should send an update on reorder", ->
- OverviewDragger.dragState.dropDestination = $('#unit-4')
- OverviewDragger.dragState.attachMethod = "after"
- OverviewDragger.dragState.parentList = $('#subsection-2')
+ Overview.overviewDragger.dragState.dropDestination = $('#unit-4')
+ Overview.overviewDragger.dragState.attachMethod = "after"
+ Overview.overviewDragger.dragState.parentList = $('#subsection-2')
# Drag Unit 1 from Subsection 1 to the end of Subsection 2.
$('#unit-1').offset(
top: $('#unit-4').offset().top + 10
left: $('#unit-4').offset().left
)
- OverviewDragger.onDragEnd(
+ Overview.overviewDragger.onDragEnd(
{element: $('#unit-1')},
null,
{clientX: $('#unit-1').offset().left}
diff --git a/cms/static/coffee/src/views/module_edit.coffee b/cms/static/coffee/src/views/module_edit.coffee
index 2b6cdcec0b..a79fccec82 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", "xblock/runtime.v1",
"js/views/feedback_notification", "js/views/metadata", "js/collections/metadata"
- "jquery.inputnumber", "xmodule"],
-(Backbone, $, _, gettext, XBlock, NotificationView, MetadataView, MetadataCollection) ->
+ "js/utils/modal", "jquery.inputnumber", "xmodule"],
+(Backbone, $, _, gettext, XBlock, NotificationView, MetadataView, MetadataCollection, ModalUtils) ->
class ModuleEdit extends Backbone.View
tagName: 'li'
className: 'component'
@@ -87,7 +87,7 @@ define ["backbone", "jquery", "underscore", "gettext", "xblock/runtime.v1",
id: _this.model.id
data.metadata = _.extend(data.metadata || {}, @changedMetadata())
- @hideModal()
+ ModalUtils.hideModalCover()
saving = new NotificationView.Mini
title: gettext('Saving…')
saving.show()
@@ -102,17 +102,12 @@ define ["backbone", "jquery", "underscore", "gettext", "xblock/runtime.v1",
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 4fd340ad7b..592096aeff 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",
- "js/utils/cancel_on_escape", "jquery.ui", "jquery.timepicker", "jquery.leanModal", "jquery.form", "jquery.smoothScroll"],
- function(domReady, $, _, gettext, NotificationView, PromptView, CancelOnEscape) {
+ "js/utils/get_date", "jquery.ui", "jquery.leanModal", "jquery.form", "jquery.smoothScroll"],
+ function(domReady, $, _, gettext, NotificationView, PromptView, DateUtils) {
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');
@@ -23,12 +19,6 @@ domReady(function() {
$newComponentButton = $('.new-component-button');
$spinner = $('');
- $('.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();
});
@@ -70,7 +60,7 @@ domReady(function() {
$('.nav-dd .nav-item .wrapper-nav-sub').removeClass('is-shown');
$title.addClass('is-selected');
$subnav.addClass('is-shown');
- // if propogation is not stopped, the event will bubble up to the
+ // if propagation is not stopped, the event will bubble up to the
// body element, which will close the dropdown.
e.stopPropagation();
}
@@ -94,14 +84,6 @@ domReady(function() {
// tender feedback window scrolling
$('a.show-tender').bind('click', smoothScrollTop);
- // 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);
$('.subsection-display-name-input').each(function() {
@@ -113,12 +95,8 @@ domReady(function() {
// expand/collapse methods for optional date setters
$('.set-date').bind('click', showDateSetter);
$('.remove-date').bind('click', removeDateSetter);
- // add new/delete section
- $('.new-courseware-section-button').bind('click', addNewSection);
- $('.delete-section-button').bind('click', deleteSection);
- // add new/delete subsection
- $('.new-subsection-item').bind('click', addNewSubsection);
+ $('.delete-section-button').bind('click', deleteSection);
$('.delete-subsection-button').bind('click', deleteSubsection);
$('.sync-date').bind('click', syncReleaseDate);
@@ -126,12 +104,7 @@ domReady(function() {
// 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');
@@ -171,44 +144,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 syncReleaseDate(e) {
e.preventDefault();
$(this).closest('.notice').hide();
@@ -216,21 +151,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;
@@ -272,7 +192,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")
);
@@ -381,30 +301,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 toggleSubmodules(e) {
- e.preventDefault();
- $(this).toggleClass('expand').toggleClass('collapse');
- $(this).closest('.branch, .window').toggleClass('collapsed');
-}
-
-function setVisibility(e) {
- $(this).find('.checked').removeClass('checked');
- $(e.target).closest('.option').addClass('checked');
-}
-
function showDateSetter(e) {
e.preventDefault();
var $block = $(this).closest('.due-date-input');
@@ -422,7 +318,6 @@ function removeDateSetter(e) {
$block.find('.time').val('');
}
-
function hideNotification(e) {
(e).preventDefault();
$(this).closest('.wrapper-notification').removeClass('is-shown').addClass('is-hiding').attr('aria-hidden', 'true');
@@ -433,101 +328,6 @@ function hideAlert(e) {
$(this).closest('.wrapper-alert').removeClass('is-shown');
}
-function addNewSection(e) {
- e.preventDefault();
-
- $(e.target).addClass('disabled');
-
- var $newSection = $($('#new-section-template').html());
- var $cancelButton = $newSection.find('.new-section-name-cancel');
- $('.courseware-overview').prepend($newSection);
- $newSection.find('.new-section-name').focus().select();
- $newSection.find('.section-name-form').bind('submit', saveNewSection);
- $cancelButton.bind('click', cancelNewSection);
- CancelOnEscape($cancelButton);
-}
-
-function saveNewSection(e) {
- e.preventDefault();
-
- var $saveButton = $(this).find('.new-section-name-save');
- var parent = $saveButton.data('parent');
- var category = $saveButton.data('category');
- var display_name = $(this).find('.new-section-name').val();
-
- analytics.track('Created a Section', {
- 'course': course_location_analytics,
- 'display_name': display_name
- });
-
- $.post('/create_item', {
- 'parent_location': parent,
- 'category': category,
- 'display_name': display_name
- },
-
- function(data) {
- if (data.id != undefined) location.reload();
- });
-}
-
-function cancelNewSection(e) {
- e.preventDefault();
- $('.new-courseware-section-button').removeClass('disabled');
- $(this).parents('section.new-section').remove();
-}
-
-function addNewSubsection(e) {
- e.preventDefault();
- var $section = $(this).closest('.courseware-section');
- var $newSubsection = $($('#new-subsection-template').html());
- $section.find('.subsection-list > ol').append($newSubsection);
- $section.find('.new-subsection-name-input').focus().select();
-
- var $saveButton = $newSubsection.find('.new-subsection-name-save');
- var $cancelButton = $newSubsection.find('.new-subsection-name-cancel');
-
- var parent = $(this).parents("section.branch").data("id");
-
- $saveButton.data('parent', parent);
- $saveButton.data('category', $(this).data('category'));
-
- $newSubsection.find('.new-subsection-form').bind('submit', saveNewSubsection);
- $cancelButton.bind('click', cancelNewSubsection);
- CancelOnEscape($cancelButton);
-}
-
-function saveNewSubsection(e) {
- e.preventDefault();
-
- var parent = $(this).find('.new-subsection-name-save').data('parent');
- var category = $(this).find('.new-subsection-name-save').data('category');
- var display_name = $(this).find('.new-subsection-name-input').val();
-
- analytics.track('Created a Subsection', {
- 'course': course_location_analytics,
- 'display_name': display_name
- });
-
-
- $.post('/create_item', {
- 'parent_location': parent,
- 'category': category,
- 'display_name': display_name
- },
-
- function(data) {
- if (data.id != undefined) {
- location.reload();
- }
- });
-}
-
-function cancelNewSubsection(e) {
- e.preventDefault();
- $(this).parents('li.branch').remove();
-}
-
function setSectionScheduleDate(e) {
e.preventDefault();
$(this).closest("h4").hide();
@@ -540,65 +340,6 @@ 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/utils/get_date.js b/cms/static/js/utils/get_date.js
new file mode 100644
index 0000000000..e934e9aea9
--- /dev/null
+++ b/cms/static/js/utils/get_date.js
@@ -0,0 +1,18 @@
+define(["jquery", "jquery.ui", "jquery.timepicker"], 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;
+});
diff --git a/cms/static/js/utils/modal.js b/cms/static/js/utils/modal.js
new file mode 100644
index 0000000000..5786144568
--- /dev/null
+++ b/cms/static/js/utils/modal.js
@@ -0,0 +1,83 @@
+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 (module_edit) 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.
+ 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");
+ }
+ $modalCover.unbind('click');
+ 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
+ };
+});
diff --git a/cms/static/js/views/course_info_handout.js b/cms/static/js/views/course_info_handout.js
index d22de768b4..f9804d03a4 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();
- });
+ 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.$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 256f63624a..d5dd82376e 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,9 @@ 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);
+ // Variable stored for unit test.
+ this.$modalCover = ModalUtils.showModalCover(false, function() {
+ self.closeEditor(true)
});
$('.date').datepicker('destroy');
@@ -91,7 +89,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 +119,12 @@ 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);
- });
+ // Variable stored for unit test.
+ this.$modalCover = ModalUtils.showModalCover(false,
+ function() {
+ self.closeEditor(false)
+ }
+ );
},
onDelete: function(event) {
@@ -198,8 +198,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/overview.js b/cms/static/js/views/overview.js
index 6e18dfdee2..fd2008eab5 100644
--- a/cms/static/js/views/overview.js
+++ b/cms/static/js/views/overview.js
@@ -1,5 +1,204 @@
-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/cancel_on_escape", "js/utils/get_date"],
+ function (domReady, $, ui, _, gettext, NotificationView, Draggabilly, ModalUtils, CancelOnEscape, 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 toggleSubmodules = function(e) {
+ e.preventDefault();
+ $(this).toggleClass('expand').toggleClass('collapse');
+ $(this).closest('.branch, .window').toggleClass('collapsed');
+ };
+
+ 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();
+ };
+
+ var saveSetSectionScheduleDate = function (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();
+ });
+ };
+
+ var addNewSection = function (e) {
+ e.preventDefault();
+
+ $(e.target).addClass('disabled');
+
+ var $newSection = $($('#new-section-template').html());
+ var $cancelButton = $newSection.find('.new-section-name-cancel');
+ $('.courseware-overview').prepend($newSection);
+ $newSection.find('.new-section-name').focus().select();
+ $newSection.find('.section-name-form').bind('submit', saveNewSection);
+ $cancelButton.bind('click', cancelNewSection);
+ CancelOnEscape($cancelButton);
+ };
+
+ var saveNewSection = function (e) {
+ e.preventDefault();
+
+ var $saveButton = $(this).find('.new-section-name-save');
+ var parent = $saveButton.data('parent');
+ var category = $saveButton.data('category');
+ var display_name = $(this).find('.new-section-name').val();
+
+ analytics.track('Created a Section', {
+ 'course': course_location_analytics,
+ 'display_name': display_name
+ });
+
+ $.post('/create_item', {
+ 'parent_location': parent,
+ 'category': category,
+ 'display_name': display_name
+ },
+
+ function(data) {
+ if (data.id != undefined) location.reload();
+ });
+ };
+
+ var cancelNewSection = function (e) {
+ e.preventDefault();
+ $('.new-courseware-section-button').removeClass('disabled');
+ $(this).parents('section.new-section').remove();
+ };
+
+ var addNewSubsection = function (e) {
+ e.preventDefault();
+ var $section = $(this).closest('.courseware-section');
+ var $newSubsection = $($('#new-subsection-template').html());
+ $section.find('.subsection-list > ol').append($newSubsection);
+ $section.find('.new-subsection-name-input').focus().select();
+
+ var $saveButton = $newSubsection.find('.new-subsection-name-save');
+ var $cancelButton = $newSubsection.find('.new-subsection-name-cancel');
+
+ var parent = $(this).parents("section.branch").data("id");
+
+ $saveButton.data('parent', parent);
+ $saveButton.data('category', $(this).data('category'));
+
+ $newSubsection.find('.new-subsection-form').bind('submit', saveNewSubsection);
+ $cancelButton.bind('click', cancelNewSubsection);
+ CancelOnEscape($cancelButton);
+ };
+
+ var saveNewSubsection = function (e) {
+ e.preventDefault();
+
+ var parent = $(this).find('.new-subsection-name-save').data('parent');
+ var category = $(this).find('.new-subsection-name-save').data('category');
+ var display_name = $(this).find('.new-subsection-name-input').val();
+
+ analytics.track('Created a Subsection', {
+ 'course': course_location_analytics,
+ 'display_name': display_name
+ });
+
+
+ $.post('/create_item', {
+ 'parent_location': parent,
+ 'category': category,
+ 'display_name': display_name
+ },
+
+ function(data) {
+ if (data.id != undefined) {
+ location.reload();
+ }
+ });
+ };
+
+ var cancelNewSubsection = function (e) {
+ e.preventDefault();
+ $(this).parents('li.branch').remove();
+ };
var overviewDragger = {
droppableClasses: 'drop-target drop-target-prepend drop-target-before drop-target-after',
@@ -295,6 +494,24 @@ define(["domReady", "jquery", "jquery.ui", "gettext", "js/views/feedback_notific
};
domReady(function() {
+ // toggling overview section details
+ $(function() {
+ if ($('.courseware-section').length > 0) {
+ $('.toggle-button-sections').addClass('is-shown');
+ }
+ });
+ $('.toggle-button-sections').bind('click', toggleSections);
+ $('.expand-collapse-icon').bind('click', toggleSubmodules);
+
+ 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);
+
+ $('.new-courseware-section-button').bind('click', addNewSection);
+ $('.new-subsection-item').bind('click', addNewSubsection);
+
// Section
overviewDragger.makeDraggable(
'.courseware-section',
@@ -318,5 +535,8 @@ define(["domReady", "jquery", "jquery.ui", "gettext", "js/views/feedback_notific
);
});
- return overviewDragger;
+ return {
+ overviewDragger: overviewDragger,
+ saveSetSectionScheduleDate: saveSetSectionScheduleDate
+ };
});
diff --git a/cms/templates/asset_index.html b/cms/templates/asset_index.html
index 9301e857f2..909fc73c5e 100644
--- a/cms/templates/asset_index.html
+++ b/cms/templates/asset_index.html
@@ -18,29 +18,25 @@