From 52f4f6afbc4f95143c82513875ca650370cee173 Mon Sep 17 00:00:00 2001 From: cahrens Date: Fri, 11 Oct 2013 11:51:12 -0400 Subject: [PATCH] Pull modal cover and date utils into their own files. Move overview-specific functions into overview.js. --- .../coffee/spec/views/course_info_spec.coffee | 20 +- .../coffee/spec/views/overview_spec.coffee | 76 ++--- .../coffee/src/views/module_edit.coffee | 15 +- cms/static/js/base.js | 269 +----------------- cms/static/js/utils/get_date.js | 18 ++ cms/static/js/utils/modal.js | 83 ++++++ cms/static/js/views/course_info_handout.js | 13 +- cms/static/js/views/course_info_update.js | 27 +- cms/static/js/views/overview.js | 226 ++++++++++++++- cms/templates/asset_index.html | 16 +- cms/templates/overview.html | 2 +- 11 files changed, 402 insertions(+), 363 deletions(-) create mode 100644 cms/static/js/utils/get_date.js create mode 100644 cms/static/js/utils/modal.js 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 @@