diff --git a/cms/static/coffee/spec/main.coffee b/cms/static/coffee/spec/main.coffee
index 4980e86c65..11f937a2b4 100644
--- a/cms/static/coffee/spec/main.coffee
+++ b/cms/static/coffee/spec/main.coffee
@@ -207,16 +207,17 @@ define([
"js/spec/video/transcripts/videolist_spec", "js/spec/video/transcripts/message_manager_spec",
"js/spec/video/transcripts/file_uploader_spec",
- "js/spec/models/explicit_url_spec"
+ "js/spec/models/explicit_url_spec",
+ "js/spec/utils/drag_and_drop_spec",
"js/spec/utils/handle_iframe_binding_spec",
"js/spec/utils/module_spec",
"js/spec/views/baseview_spec",
"js/spec/views/paging_spec",
- "js/spec/views/unit_spec"
- "js/spec/views/xblock_spec"
+ "js/spec/views/unit_spec",
+ "js/spec/views/xblock_spec",
# these tests are run separate in the cms-squire suite, due to process
# isolation issues with Squire.js
diff --git a/cms/static/coffee/spec/views/overview_spec.coffee b/cms/static/coffee/spec/views/overview_spec.coffee
index c29d31f414..d89c64c145 100644
--- a/cms/static/coffee/spec/views/overview_spec.coffee
+++ b/cms/static/coffee/spec/views/overview_spec.coffee
@@ -54,38 +54,6 @@ define ["js/views/overview", "js/views/feedback_notification", "js/spec/create_s
"""
- appendSetFixtures """
-
-
- -
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
- -
-
-
- -
-
-
-
-
-
-
- """
-
spyOn(Overview, 'saveSetSectionScheduleDate').andCallThrough()
# Have to do this here, as it normally gets bound in document.ready()
$('a.action-save').click(Overview.saveSetSectionScheduleDate)
@@ -96,20 +64,6 @@ define ["js/views/overview", "js/views/feedback_notification", "js/spec/create_s
window.analytics = jasmine.createSpyObj('analytics', ['track'])
window.course_location_analytics = jasmine.createSpy()
- Overview.overviewDragger.makeDraggable(
- '.unit',
- '.unit-drag-handle',
- 'ol.sortable-unit-list',
- 'li.courseware-subsection, article.subsection-body'
- )
-
- Overview.overviewDragger.makeDraggable(
- '.courseware-subsection',
- '.subsection-drag-handle',
- '.sortable-subsection-list',
- 'section'
- )
-
afterEach ->
delete window.analytics
delete window.course_location_analytics
@@ -143,305 +97,3 @@ define ["js/views/overview", "js/views/feedback_notification", "js/spec/create_s
# $('a.delete-section-button').click()
# $('a.action-primary').click()
# expect(@notificationSpy).toHaveBeenCalled()
-
- describe "findDestination", ->
- it "correctly finds the drop target of a drag", ->
- $ele = $('#unit-1')
- $ele.offset(
- top: $ele.offset().top + 10, left: $ele.offset().left
- )
- destination = Overview.overviewDragger.findDestination($ele, 1)
- expect(destination.ele).toBe($('#unit-2'))
- expect(destination.attachMethod).toBe('before')
-
- it "can drag and drop across section boundaries, with special handling for single sibling", ->
- $ele = $('#unit-1')
- $unit4 = $('#unit-4')
- $ele.offset(
- top: $unit4.offset().top + 8
- left: $ele.offset().left
- )
- # Dragging down, we will insert after.
- destination = Overview.overviewDragger.findDestination($ele, 1)
- expect(destination.ele).toBe($unit4)
- expect(destination.attachMethod).toBe('after')
-
- # Dragging up, we will insert before.
- destination = Overview.overviewDragger.findDestination($ele, -1)
- expect(destination.ele).toBe($unit4)
- expect(destination.attachMethod).toBe('before')
-
- # If past the end the drop target, will attach after.
- $ele.offset(
- top: $unit4.offset().top + $unit4.height() + 1
- left: $ele.offset().left
- )
- destination = Overview.overviewDragger.findDestination($ele, 0)
- expect(destination.ele).toBe($unit4)
- expect(destination.attachMethod).toBe('after')
-
-
- $unit0 = $('#unit-0')
- # If before the start the drop target, will attach before.
- $ele.offset(
- top: $unit0.offset().top - 16
- left: $ele.offset().left
- )
- destination = Overview.overviewDragger.findDestination($ele, 0)
- expect(destination.ele).toBe($unit0)
- expect(destination.attachMethod).toBe('before')
-
- it """can drop before the first element, even if element being dragged is
- slightly before the first element""", ->
- $ele = $('#subsection-2')
- $ele.offset(
- top: $('#subsection-0').offset().top - 5
- left: $ele.offset().left
- )
- destination = Overview.overviewDragger.findDestination($ele, -1)
- expect(destination.ele).toBe($('#subsection-0'))
- expect(destination.attachMethod).toBe('before')
-
- it "can drag and drop across section boundaries, with special handling for last element", ->
- $ele = $('#unit-4')
- $ele.offset(
- top: $('#unit-3').offset().bottom + 4
- left: $ele.offset().left
- )
- 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')
- # Now past the "fudge factor".
- $ele.offset(
- top: $('#unit-3').offset().top + 4
- left: $ele.offset().left
- )
- destination = Overview.overviewDragger.findDestination($ele, -1)
- expect(destination.ele).toBe($('#unit-3'))
- expect(destination.attachMethod).toBe('before')
-
- it """can drop past the last element, even if element being dragged is
- slightly before/taller then the last element""", ->
- $ele = $('#subsection-2')
- $ele.offset(
- # Make the top 1 before the top of the last element in the list.
- # This mimics the problem when the element being dropped is taller then then
- # the last element in the list.
- top: $('#subsection-4').offset().top - 1
- left: $ele.offset().left
- )
- destination = Overview.overviewDragger.findDestination($ele, 1)
- expect(destination.ele).toBe($('#subsection-4'))
- expect(destination.attachMethod).toBe('after')
-
- it "can drag into an empty list", ->
- $ele = $('#unit-1')
- $ele.offset(
- top: $('#subsection-3').offset().top + 10
- left: $ele.offset().left
- )
- destination = Overview.overviewDragger.findDestination($ele, 1)
- expect(destination.ele).toBe($('#subsection-list-3'))
- expect(destination.attachMethod).toBe('prepend')
-
- it "reports a null destination on a failed drag", ->
- $ele = $('#unit-1')
- $ele.offset(
- top: $ele.offset().top + 200, left: $ele.offset().left
- )
- destination = Overview.overviewDragger.findDestination($ele, 1)
- expect(destination).toEqual(
- ele: null
- attachMethod: ""
- )
-
- it "can drag into a collapsed list", ->
- $('#subsection-2').addClass('collapsed')
- $ele = $('#unit-2')
- $ele.offset(
- top: $('#subsection-2').offset().top + 3
- left: $ele.offset().left
- )
- 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(Overview.overviewDragger.dragState).toEqual({})
- # Call with some dummy data
- Overview.overviewDragger.onDragStart(
- {element: $('#unit-1')},
- null,
- null
- )
- expect(Overview.overviewDragger.dragState).toEqual(
- dropDestination: null,
- attachMethod: '',
- parentList: null,
- lastY: 0,
- dragDirection: 0
- )
-
- it "collapses expanded elements", ->
- expect($('#subsection-1')).not.toHaveClass('collapsed')
- Overview.overviewDragger.onDragStart(
- {element: $('#subsection-1')},
- null,
- null
- )
- expect($('#subsection-1')).toHaveClass('collapsed')
- expect($('#subsection-1')).toHaveClass('expand-on-drop')
-
- describe "onDragMove", ->
- beforeEach ->
- @scrollSpy = spyOn(window, 'scrollBy').andCallThrough()
-
- it "adds the correct CSS class to the drop destination", ->
- $ele = $('#unit-1')
- dragY = $ele.offset().top + 10
- dragX = $ele.offset().left
- $ele.offset(
- top: dragY, left: dragX
- )
- Overview.overviewDragger.onDragMove(
- {element: $ele, dragPoint:
- {y: dragY}}, '', {clientX: dragX}
- )
- expect($('#unit-2')).toHaveClass('drop-target drop-target-before')
- expect($ele).toHaveClass('valid-drop')
-
- it "does not add CSS class to the drop destination if out of bounds", ->
- $ele = $('#unit-1')
- dragY = $ele.offset().top + 10
- $ele.offset(
- top: dragY, left: $ele.offset().left
- )
- Overview.overviewDragger.onDragMove(
- {element: $ele, dragPoint:
- {y: dragY}}, '', {clientX: $ele.offset().left - 3}
- )
- expect($('#unit-2')).not.toHaveClass('drop-target drop-target-before')
- expect($ele).not.toHaveClass('valid-drop')
-
- it "scrolls up if necessary", ->
- Overview.overviewDragger.onDragMove(
- {element: $('#unit-1')}, '', {clientY: 2}
- )
- expect(@scrollSpy).toHaveBeenCalledWith(0, -10)
-
- it "scrolls down if necessary", ->
- Overview.overviewDragger.onDragMove(
- {element: $('#unit-1')}, '', {clientY: (window.innerHeight - 5)}
- )
- expect(@scrollSpy).toHaveBeenCalledWith(0, 10)
-
- describe "onDragEnd", ->
- beforeEach ->
- @reorderSpy = spyOn(Overview.overviewDragger, 'handleReorder')
-
- afterEach ->
- @reorderSpy.reset()
-
- it "calls handleReorder on a successful drag", ->
- 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
- )
- Overview.overviewDragger.onDragEnd(
- {element: $('#unit-1')},
- null,
- {clientX: $('#unit-1').offset().left}
- )
- expect(@reorderSpy).toHaveBeenCalled()
-
- it "clears out the drag state", ->
- Overview.overviewDragger.onDragEnd(
- {element: $('#unit-1')},
- null,
- null
- )
- expect(Overview.overviewDragger.dragState).toEqual({})
-
- it "sets the element to the correct position", ->
- Overview.overviewDragger.onDragEnd(
- {element: $('#unit-1')},
- null,
- null
- )
- # Chrome sets the CSS to 'auto', but Firefox uses '0px'.
- expect(['0px', 'auto']).toContain($('#unit-1').css('top'))
- expect(['0px', 'auto']).toContain($('#unit-1').css('left'))
-
- it "expands an element if it was collapsed on drag start", ->
- $('#subsection-1').addClass('collapsed')
- $('#subsection-1').addClass('expand-on-drop')
- Overview.overviewDragger.onDragEnd(
- {element: $('#subsection-1')},
- null,
- null
- )
- expect($('#subsection-1')).not.toHaveClass('collapsed')
- expect($('#subsection-1')).not.toHaveClass('expand-on-drop')
-
- it "expands a collapsed element when something is dropped in it", ->
- $('#subsection-2').addClass('collapsed')
- 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}
- )
- expect($('#subsection-2')).not.toHaveClass('collapsed')
-
- describe "AJAX", ->
- beforeEach ->
- @savingSpies = spyOnConstructor(Notification, "Mini",
- ["show", "hide"])
- @savingSpies.show.andReturn(@savingSpies)
- @clock = sinon.useFakeTimers()
-
- afterEach ->
- @clock.restore()
-
- it "should send an update on reorder", ->
- requests = create_sinon["requests"](this)
-
- 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
- )
- Overview.overviewDragger.onDragEnd(
- {element: $('#unit-1')},
- null,
- {clientX: $('#unit-1').offset().left}
- )
- expect(requests.length).toEqual(2)
- expect(@savingSpies.constructor).toHaveBeenCalled()
- expect(@savingSpies.show).toHaveBeenCalled()
- expect(@savingSpies.hide).not.toHaveBeenCalled()
- savingOptions = @savingSpies.constructor.mostRecentCall.args[0]
- expect(savingOptions.title).toMatch(/Saving/)
- expect($('#unit-1')).toHaveClass('was-dropped')
- # We expect 2 requests to be sent-- the first for removing Unit 1 from Subsection 1,
- # and the second for adding Unit 1 to the end of Subsection 2.
- expect(requests[0].requestBody).toEqual('{"children":["second-unit-id","third-unit-id"]}')
- requests[0].respond(200)
- expect(@savingSpies.hide).not.toHaveBeenCalled()
- expect(requests[1].requestBody).toEqual('{"children":["fourth-unit-id","first-unit-id"]}')
- requests[1].respond(200)
- expect(@savingSpies.hide).toHaveBeenCalled()
- # Class is removed in a timeout.
- @clock.tick(1001)
- expect($('#unit-1')).not.toHaveClass('was-dropped')
diff --git a/cms/static/js/spec/utils/drag_and_drop_spec.js b/cms/static/js/spec/utils/drag_and_drop_spec.js
new file mode 100644
index 0000000000..74558ca22f
--- /dev/null
+++ b/cms/static/js/spec/utils/drag_and_drop_spec.js
@@ -0,0 +1,313 @@
+define(["js/utils/drag_and_drop", "js/views/feedback_notification", "js/spec/create_sinon", "jquery"],
+ function (ContentDragger, Notification, create_sinon, $) {
+ describe("Overview drag and drop functionality", function () {
+ beforeEach(function () {
+ setFixtures(readFixtures('mock/mock-outline.underscore'));
+ ContentDragger.makeDraggable('.unit', '.unit-drag-handle', 'ol.sortable-unit-list', 'li.courseware-subsection, article.subsection-body');
+ ContentDragger.makeDraggable('.courseware-subsection', '.subsection-drag-handle', '.sortable-subsection-list', 'section');
+ });
+
+ describe("findDestination", function () {
+ it("correctly finds the drop target of a drag", function () {
+ var $ele, destination;
+ $ele = $('#unit-1');
+ $ele.offset({
+ top: $ele.offset().top + 10,
+ left: $ele.offset().left
+ });
+ destination = ContentDragger.findDestination($ele, 1);
+ expect(destination.ele).toBe($('#unit-2'));
+ expect(destination.attachMethod).toBe('before');
+ });
+ it("can drag and drop across section boundaries, with special handling for single sibling", function () {
+ var $ele, $unit0, $unit4, destination;
+ $ele = $('#unit-1');
+ $unit4 = $('#unit-4');
+ $ele.offset({
+ top: $unit4.offset().top + 8,
+ left: $ele.offset().left
+ });
+ destination = ContentDragger.findDestination($ele, 1);
+ expect(destination.ele).toBe($unit4);
+ expect(destination.attachMethod).toBe('after');
+ destination = ContentDragger.findDestination($ele, -1);
+ expect(destination.ele).toBe($unit4);
+ expect(destination.attachMethod).toBe('before');
+ $ele.offset({
+ top: $unit4.offset().top + $unit4.height() + 1,
+ left: $ele.offset().left
+ });
+ destination = ContentDragger.findDestination($ele, 0);
+ expect(destination.ele).toBe($unit4);
+ expect(destination.attachMethod).toBe('after');
+ $unit0 = $('#unit-0');
+ $ele.offset({
+ top: $unit0.offset().top - 16,
+ left: $ele.offset().left
+ });
+ destination = ContentDragger.findDestination($ele, 0);
+ expect(destination.ele).toBe($unit0);
+ expect(destination.attachMethod).toBe('before');
+ });
+ it("can drop before the first element, even if element being dragged is\nslightly before the first element", function () {
+ var $ele, destination;
+ $ele = $('#subsection-2');
+ $ele.offset({
+ top: $('#subsection-0').offset().top - 5,
+ left: $ele.offset().left
+ });
+ destination = ContentDragger.findDestination($ele, -1);
+ expect(destination.ele).toBe($('#subsection-0'));
+ expect(destination.attachMethod).toBe('before');
+ });
+ it("can drag and drop across section boundaries, with special handling for last element", function () {
+ var $ele, destination;
+ $ele = $('#unit-4');
+ $ele.offset({
+ top: $('#unit-3').offset().bottom + 4,
+ left: $ele.offset().left
+ });
+ destination = ContentDragger.findDestination($ele, -1);
+ expect(destination.ele).toBe($('#unit-3'));
+ expect(destination.attachMethod).toBe('after');
+ $ele.offset({
+ top: $('#unit-3').offset().top + 4,
+ left: $ele.offset().left
+ });
+ destination = ContentDragger.findDestination($ele, -1);
+ expect(destination.ele).toBe($('#unit-3'));
+ expect(destination.attachMethod).toBe('before');
+ });
+ it("can drop past the last element, even if element being dragged is\nslightly before/taller then the last element", function () {
+ var $ele, destination;
+ $ele = $('#subsection-2');
+ $ele.offset({
+ top: $('#subsection-4').offset().top - 1,
+ left: $ele.offset().left
+ });
+ destination = ContentDragger.findDestination($ele, 1);
+ expect(destination.ele).toBe($('#subsection-4'));
+ expect(destination.attachMethod).toBe('after');
+ });
+ it("can drag into an empty list", function () {
+ var $ele, destination;
+ $ele = $('#unit-1');
+ $ele.offset({
+ top: $('#subsection-3').offset().top + 10,
+ left: $ele.offset().left
+ });
+ destination = ContentDragger.findDestination($ele, 1);
+ expect(destination.ele).toBe($('#subsection-list-3'));
+ expect(destination.attachMethod).toBe('prepend');
+ });
+ it("reports a null destination on a failed drag", function () {
+ var $ele, destination;
+ $ele = $('#unit-1');
+ $ele.offset({
+ top: $ele.offset().top + 200,
+ left: $ele.offset().left
+ });
+ destination = ContentDragger.findDestination($ele, 1);
+ expect(destination).toEqual({
+ ele: null,
+ attachMethod: ""
+ });
+ });
+ it("can drag into a collapsed list", function () {
+ var $ele, destination;
+ $('#subsection-2').addClass('collapsed');
+ $ele = $('#unit-2');
+ $ele.offset({
+ top: $('#subsection-2').offset().top + 3,
+ left: $ele.offset().left
+ });
+ destination = ContentDragger.findDestination($ele, 1);
+ expect(destination.ele).toBe($('#subsection-list-2'));
+ expect(destination.parentList).toBe($('#subsection-2'));
+ expect(destination.attachMethod).toBe('prepend');
+ });
+ });
+ describe("onDragStart", function () {
+ it("sets the dragState to its default values", function () {
+ expect(ContentDragger.dragState).toEqual({});
+ ContentDragger.onDragStart({
+ element: $('#unit-1')
+ }, null, null);
+ expect(ContentDragger.dragState).toEqual({
+ dropDestination: null,
+ attachMethod: '',
+ parentList: null,
+ lastY: 0,
+ dragDirection: 0
+ });
+ });
+ it("collapses expanded elements", function () {
+ expect($('#subsection-1')).not.toHaveClass('collapsed');
+ ContentDragger.onDragStart({
+ element: $('#subsection-1')
+ }, null, null);
+ expect($('#subsection-1')).toHaveClass('collapsed');
+ expect($('#subsection-1')).toHaveClass('expand-on-drop');
+ });
+ });
+ describe("onDragMove", function () {
+ beforeEach(function () {
+ this.scrollSpy = spyOn(window, 'scrollBy').andCallThrough();
+ });
+ it("adds the correct CSS class to the drop destination", function () {
+ var $ele, dragX, dragY;
+ $ele = $('#unit-1');
+ dragY = $ele.offset().top + 10;
+ dragX = $ele.offset().left;
+ $ele.offset({
+ top: dragY,
+ left: dragX
+ });
+ ContentDragger.onDragMove({
+ element: $ele,
+ dragPoint: {
+ y: dragY
+ }
+ }, '', {
+ clientX: dragX
+ });
+ expect($('#unit-2')).toHaveClass('drop-target drop-target-before');
+ expect($ele).toHaveClass('valid-drop');
+ });
+ it("does not add CSS class to the drop destination if out of bounds", function () {
+ var $ele, dragY;
+ $ele = $('#unit-1');
+ dragY = $ele.offset().top + 10;
+ $ele.offset({
+ top: dragY,
+ left: $ele.offset().left
+ });
+ ContentDragger.onDragMove({
+ element: $ele,
+ dragPoint: {
+ y: dragY
+ }
+ }, '', {
+ clientX: $ele.offset().left - 3
+ });
+ expect($('#unit-2')).not.toHaveClass('drop-target drop-target-before');
+ expect($ele).not.toHaveClass('valid-drop');
+ });
+ it("scrolls up if necessary", function () {
+ ContentDragger.onDragMove({
+ element: $('#unit-1')
+ }, '', {
+ clientY: 2
+ });
+ expect(this.scrollSpy).toHaveBeenCalledWith(0, -10);
+ });
+ it("scrolls down if necessary", function () {
+ ContentDragger.onDragMove({
+ element: $('#unit-1')
+ }, '', {
+ clientY: window.innerHeight - 5
+ });
+ expect(this.scrollSpy).toHaveBeenCalledWith(0, 10);
+ });
+ });
+ describe("onDragEnd", function () {
+ beforeEach(function () {
+ this.reorderSpy = spyOn(ContentDragger, 'handleReorder');
+ });
+ afterEach(function () {
+ this.reorderSpy.reset();
+ });
+ it("calls handleReorder on a successful drag", function () {
+ ContentDragger.dragState.dropDestination = $('#unit-2');
+ ContentDragger.dragState.attachMethod = "before";
+ ContentDragger.dragState.parentList = $('#subsection-1');
+ $('#unit-1').offset({
+ top: $('#unit-1').offset().top + 10,
+ left: $('#unit-1').offset().left
+ });
+ ContentDragger.onDragEnd({
+ element: $('#unit-1')
+ }, null, {
+ clientX: $('#unit-1').offset().left
+ });
+ expect(this.reorderSpy).toHaveBeenCalled();
+ });
+ it("clears out the drag state", function () {
+ ContentDragger.onDragEnd({
+ element: $('#unit-1')
+ }, null, null);
+ expect(ContentDragger.dragState).toEqual({});
+ });
+ it("sets the element to the correct position", function () {
+ ContentDragger.onDragEnd({
+ element: $('#unit-1')
+ }, null, null);
+ expect(['0px', 'auto']).toContain($('#unit-1').css('top'));
+ expect(['0px', 'auto']).toContain($('#unit-1').css('left'));
+ });
+ it("expands an element if it was collapsed on drag start", function () {
+ $('#subsection-1').addClass('collapsed');
+ $('#subsection-1').addClass('expand-on-drop');
+ ContentDragger.onDragEnd({
+ element: $('#subsection-1')
+ }, null, null);
+ expect($('#subsection-1')).not.toHaveClass('collapsed');
+ expect($('#subsection-1')).not.toHaveClass('expand-on-drop');
+ });
+ it("expands a collapsed element when something is dropped in it", function () {
+ $('#subsection-2').addClass('collapsed');
+ ContentDragger.dragState.dropDestination = $('#list-2');
+ ContentDragger.dragState.attachMethod = "prepend";
+ ContentDragger.dragState.parentList = $('#subsection-2');
+ ContentDragger.onDragEnd({
+ element: $('#unit-1')
+ }, null, {
+ clientX: $('#unit-1').offset().left
+ });
+ expect($('#subsection-2')).not.toHaveClass('collapsed');
+ });
+ });
+ describe("AJAX", function () {
+ beforeEach(function () {
+ this.savingSpies = spyOnConstructor(Notification, "Mini", ["show", "hide"]);
+ this.savingSpies.show.andReturn(this.savingSpies);
+ this.clock = sinon.useFakeTimers();
+ });
+ afterEach(function () {
+ this.clock.restore();
+ });
+ it("should send an update on reorder", function () {
+ var requests, savingOptions;
+ requests = create_sinon["requests"](this);
+ ContentDragger.dragState.dropDestination = $('#unit-4');
+ ContentDragger.dragState.attachMethod = "after";
+ ContentDragger.dragState.parentList = $('#subsection-2');
+ $('#unit-1').offset({
+ top: $('#unit-4').offset().top + 10,
+ left: $('#unit-4').offset().left
+ });
+ ContentDragger.onDragEnd({
+ element: $('#unit-1')
+ }, null, {
+ clientX: $('#unit-1').offset().left
+ });
+ expect(requests.length).toEqual(2);
+ expect(this.savingSpies.constructor).toHaveBeenCalled();
+ expect(this.savingSpies.show).toHaveBeenCalled();
+ expect(this.savingSpies.hide).not.toHaveBeenCalled();
+ savingOptions = this.savingSpies.constructor.mostRecentCall.args[0];
+ expect(savingOptions.title).toMatch(/Saving/);
+ expect($('#unit-1')).toHaveClass('was-dropped');
+ expect(requests[0].requestBody).toEqual('{"children":["second-unit-id","third-unit-id"]}');
+ requests[0].respond(200);
+ expect(this.savingSpies.hide).not.toHaveBeenCalled();
+ expect(requests[1].requestBody).toEqual('{"children":["fourth-unit-id","first-unit-id"]}');
+ requests[1].respond(200);
+ expect(this.savingSpies.hide).toHaveBeenCalled();
+ this.clock.tick(1001);
+ expect($('#unit-1')).not.toHaveClass('was-dropped');
+ });
+ });
+ });
+ });
+
diff --git a/cms/static/js/utils/drag_and_drop.js b/cms/static/js/utils/drag_and_drop.js
new file mode 100644
index 0000000000..2479ec47ba
--- /dev/null
+++ b/cms/static/js/utils/drag_and_drop.js
@@ -0,0 +1,346 @@
+define(["jquery", "jquery.ui", "underscore", "gettext", "js/views/feedback_notification", "draggabilly",
+ "js/utils/module"],
+ function ($, ui, _, gettext, NotificationView, Draggabilly, ModuleUtils) {
+
+ var contentDragger = {
+ droppableClasses: 'drop-target drop-target-prepend drop-target-before drop-target-after',
+ validDropClass: "valid-drop",
+ expandOnDropClass: "expand-on-drop",
+
+ /*
+ * Determine information about where to drop the currently dragged
+ * element. Returns the element to attach to and the method of
+ * attachment ('before', 'after', or 'prepend').
+ */
+ findDestination: function (ele, yChange) {
+ var eleY = ele.offset().top;
+ var eleYEnd = eleY + ele.height();
+ var containers = $(ele.data('droppable-class'));
+
+ for (var i = 0; i < containers.length; i++) {
+ var container = $(containers[i]);
+ // Exclude the 'new unit' buttons, and make sure we don't
+ // prepend an element to itself
+ var siblings = container.children().filter(function () {
+ return $(this).data('locator') !== undefined && !$(this).is(ele);
+ });
+ // If the container is collapsed, check to see if the
+ // element is on top of its parent list -- don't check the
+ // position of the container
+ var parentList = container.parents(ele.data('parent-location-selector')).first();
+ if (parentList.hasClass('collapsed')) {
+ var parentListTop = parentList.offset().top;
+ // To make it easier to drop subsections into collapsed sections (which have
+ // a lot of visual padding around them), allow a fudge factor around the
+ // parent element.
+ var collapseFudge = 10;
+ if (Math.abs(eleY - parentListTop) < collapseFudge ||
+ (eleY > parentListTop &&
+ eleYEnd - collapseFudge <= parentListTop + parentList.height())
+ ) {
+ return {
+ ele: container,
+ attachMethod: 'prepend',
+ parentList: parentList
+ };
+ }
+ }
+ // Otherwise, do check the container
+ else {
+ // If the list is empty, we should prepend to it,
+ // unless both elements are at the same location --
+ // this prevents the user from being unable to expand
+ // a section
+ var containerY = container.offset().top;
+ if (siblings.length === 0 &&
+ containerY !== eleY &&
+ Math.abs(eleY - containerY) < 50) {
+ return {
+ ele: container,
+ attachMethod: 'prepend'
+ };
+ }
+ // Otherwise the list is populated, and we should attach before/after a sibling
+ else {
+ for (var j = 0; j < siblings.length; j++) {
+ var $sibling = $(siblings[j]);
+ var siblingY = $sibling.offset().top;
+ var siblingHeight = $sibling.height();
+ var siblingYEnd = siblingY + siblingHeight;
+
+ // Facilitate dropping into the beginning or end of a list
+ // (coming from opposite direction) via a "fudge factor". Math.min is for Jasmine test.
+ var fudge = Math.min(Math.ceil(siblingHeight / 2), 20);
+
+ // Dragging to top or bottom of a list with only one element is tricky
+ // because the element being dragged may be the same size as the sibling.
+ if (siblings.length === 1) {
+ // Element being dragged is within the drop target. Use the direction
+ // of the drag (yChange) to determine before or after.
+ if (eleY + fudge >= siblingY && eleYEnd - fudge <= siblingYEnd) {
+ return {
+ ele: $sibling,
+ attachMethod: yChange > 0 ? 'after' : 'before'
+ };
+ }
+ // Element being dragged is before the drop target.
+ else if (Math.abs(eleYEnd - siblingY) <= fudge) {
+ return {
+ ele: $sibling,
+ attachMethod: 'before'
+ };
+ }
+ // Element being dragged is after the drop target.
+ else if (Math.abs(eleY - siblingYEnd) <= fudge) {
+ return {
+ ele: $sibling,
+ attachMethod: 'after'
+ };
+ }
+ }
+ else {
+ // Dragging up into end of list.
+ if (j === siblings.length - 1 && yChange < 0 && Math.abs(eleY - siblingYEnd) <= fudge) {
+ return {
+ ele: $sibling,
+ attachMethod: 'after'
+ };
+ }
+ // Dragging up or down into beginning of list.
+ else if (j === 0 && Math.abs(eleY - siblingY) <= fudge) {
+ return {
+ ele: $sibling,
+ attachMethod: 'before'
+ };
+ }
+ // Dragging down into end of list. Special handling required because
+ // the element being dragged may be taller then the element being dragged over
+ // (if eleY can never be >= siblingY, general case at the end does not work).
+ else if (j === siblings.length - 1 && yChange > 0 &&
+ Math.abs(eleYEnd - siblingYEnd) <= fudge) {
+ return {
+ ele: $sibling,
+ attachMethod: 'after'
+ };
+ }
+ else if (eleY >= siblingY && eleY <= siblingYEnd) {
+ return {
+ ele: $sibling,
+ attachMethod: eleY - siblingY <= siblingHeight / 2 ? 'before' : 'after'
+ };
+ }
+ }
+ }
+ }
+ }
+ }
+ // Failed drag
+ return {
+ ele: null,
+ attachMethod: ''
+ };
+ },
+
+ // Information about the current drag.
+ dragState: {},
+
+ onDragStart: function (draggie, event, pointer) {
+ var ele = $(draggie.element);
+ this.dragState = {
+ // Which element will be dropped into/onto on success
+ dropDestination: null,
+ // How we attach to the destination: 'before', 'after', 'prepend'
+ attachMethod: '',
+ // If dragging to an empty section, the parent section
+ parentList: null,
+ // The y location of the last dragMove event (to determine direction).
+ lastY: 0,
+ // The direction the drag is moving in (negative means up, positive down).
+ dragDirection: 0
+ };
+ if (!ele.hasClass('collapsed')) {
+ ele.addClass('collapsed');
+ ele.find('.expand-collapse').first().addClass('expand').removeClass('collapse');
+ // onDragStart gets called again after the collapse, so we can't just store a variable in the dragState.
+ ele.addClass(this.expandOnDropClass);
+ }
+ },
+
+ onDragMove: function (draggie, event, pointer) {
+ // Handle scrolling of the browser.
+ var scrollAmount = 0;
+ var dragBuffer = 10;
+ if (window.innerHeight - dragBuffer < pointer.clientY) {
+ scrollAmount = dragBuffer;
+ }
+ else if (dragBuffer > pointer.clientY) {
+ scrollAmount = -(dragBuffer);
+ }
+ if (scrollAmount !== 0) {
+ window.scrollBy(0, scrollAmount);
+ return;
+ }
+
+ var yChange = draggie.dragPoint.y - this.dragState.lastY;
+ if (yChange !== 0) {
+ this.dragState.direction = yChange;
+ }
+ this.dragState.lastY = draggie.dragPoint.y;
+
+ var ele = $(draggie.element);
+ var destinationInfo = this.findDestination(ele, this.dragState.direction);
+ var destinationEle = destinationInfo.ele;
+ this.dragState.parentList = destinationInfo.parentList;
+
+ // Clear out the old destination
+ if (this.dragState.dropDestination) {
+ this.dragState.dropDestination.removeClass(this.droppableClasses);
+ }
+ // Mark the new destination
+ if (destinationEle && this.pointerInBounds(pointer, ele)) {
+ ele.addClass(this.validDropClass);
+ destinationEle.addClass('drop-target drop-target-' + destinationInfo.attachMethod);
+ this.dragState.attachMethod = destinationInfo.attachMethod;
+ this.dragState.dropDestination = destinationEle;
+ }
+ else {
+ ele.removeClass(this.validDropClass);
+ this.dragState.attachMethod = '';
+ this.dragState.dropDestination = null;
+ }
+ },
+
+ onDragEnd: function (draggie, event, pointer) {
+ var ele = $(draggie.element);
+ var destination = this.dragState.dropDestination;
+
+ // Clear dragging state in preparation for the next event.
+ if (destination) {
+ destination.removeClass(this.droppableClasses);
+ }
+ ele.removeClass(this.validDropClass);
+
+ // If the drag succeeded, rearrange the DOM and send the result.
+ if (destination && this.pointerInBounds(pointer, ele)) {
+ // Make sure we don't drop into a collapsed element
+ if (this.dragState.parentList) {
+ this.expandElement(this.dragState.parentList);
+ }
+ var method = this.dragState.attachMethod;
+ destination[method](ele);
+ this.handleReorder(ele);
+ }
+ // If the drag failed, send it back
+ else {
+ $('.was-dragging').removeClass('was-dragging');
+ ele.addClass('was-dragging');
+ }
+
+ if (ele.hasClass(this.expandOnDropClass)) {
+ this.expandElement(ele);
+ ele.removeClass(this.expandOnDropClass);
+ }
+
+ // Everything in its right place
+ ele.css({
+ top: 'auto',
+ left: 'auto'
+ });
+
+ this.dragState = {};
+ },
+
+ pointerInBounds: function (pointer, ele) {
+ return pointer.clientX >= ele.offset().left && pointer.clientX < ele.offset().left + ele.width();
+ },
+
+ expandElement: function (ele) {
+ ele.removeClass('collapsed');
+ ele.find('.expand-collapse').first().removeClass('expand').addClass('collapse');
+ },
+
+ /*
+ * Find all parent-child changes and save them.
+ */
+ handleReorder: function (ele) {
+ var parentSelector = ele.data('parent-location-selector');
+ var childrenSelector = ele.data('child-selector');
+ var newParentEle = ele.parents(parentSelector).first();
+ var newParentLocator = newParentEle.data('locator');
+ var oldParentLocator = ele.data('parent');
+ // If the parent has changed, update the children of the old parent.
+ if (newParentLocator !== oldParentLocator) {
+ // Find the old parent element.
+ var oldParentEle = $(parentSelector).filter(function () {
+ return $(this).data('locator') === oldParentLocator;
+ });
+ this.saveItem(oldParentEle, childrenSelector, function () {
+ ele.data('parent', newParentLocator);
+ });
+ }
+ var saving = new NotificationView.Mini({
+ title: gettext('Saving…')
+ });
+ saving.show();
+ ele.addClass('was-dropped');
+ // Timeout interval has to match what is in the CSS.
+ setTimeout(function () {
+ ele.removeClass('was-dropped');
+ }, 1000);
+ this.saveItem(newParentEle, childrenSelector, function () {
+ saving.hide();
+ });
+ },
+
+ /*
+ * Actually save the update to the server. Takes the element
+ * representing the parent item to save, a CSS selector to find
+ * its children, and a success callback.
+ */
+ saveItem: function (ele, childrenSelector, success) {
+ // Find all current child IDs.
+ var children = _.map(
+ ele.find(childrenSelector),
+ function (child) {
+ return $(child).data('locator');
+ }
+ );
+ $.ajax({
+ url: ModuleUtils.getUpdateUrl(ele.data('locator')),
+ type: 'PUT',
+ dataType: 'json',
+ contentType: 'application/json',
+ data: JSON.stringify({
+ children: children
+ }),
+ success: success
+ });
+ },
+
+ /*
+ * Make `type` draggable using `handleClass`, able to be dropped
+ * into `droppableClass`, and with parent type
+ * `parentLocationSelector`.
+ */
+ makeDraggable: function (type, handleClass, droppableClass, parentLocationSelector) {
+ _.each(
+ $(type),
+ function (ele) {
+ // Remember data necessary to reconstruct the parent-child relationships
+ $(ele).data('droppable-class', droppableClass);
+ $(ele).data('parent-location-selector', parentLocationSelector);
+ $(ele).data('child-selector', type);
+ var draggable = new Draggabilly(ele, {
+ handle: handleClass,
+ containment: '.wrapper-dnd'
+ });
+ draggable.on('dragStart', _.bind(contentDragger.onDragStart, contentDragger));
+ draggable.on('dragMove', _.bind(contentDragger.onDragMove, contentDragger));
+ draggable.on('dragEnd', _.bind(contentDragger.onDragEnd, contentDragger));
+ }
+ );
+ }
+ };
+
+ return contentDragger;
+ });
diff --git a/cms/static/js/views/overview.js b/cms/static/js/views/overview.js
index 1a4daa826d..6fb18a39f3 100644
--- a/cms/static/js/views/overview.js
+++ b/cms/static/js/views/overview.js
@@ -1,6 +1,6 @@
-define(["domReady", "jquery", "jquery.ui", "underscore", "gettext", "js/views/feedback_notification", "draggabilly",
+define(["domReady", "jquery", "jquery.ui", "underscore", "gettext", "js/views/feedback_notification", "js/utils/drag_and_drop",
"js/utils/cancel_on_escape", "js/utils/get_date", "js/utils/module"],
- function (domReady, $, ui, _, gettext, NotificationView, Draggabilly, CancelOnEscape,
+ function (domReady, $, ui, _, gettext, NotificationView, ContentDragger, CancelOnEscape,
DateUtils, ModuleUtils) {
var modalSelector = '.edit-section-publish-settings';
@@ -207,345 +207,7 @@ define(["domReady", "jquery", "jquery.ui", "underscore", "gettext", "js/views/fe
$(this).parents('li.courseware-subsection').remove();
};
- var overviewDragger = {
- droppableClasses: 'drop-target drop-target-prepend drop-target-before drop-target-after',
- validDropClass: "valid-drop",
- expandOnDropClass: "expand-on-drop",
- /*
- * Determine information about where to drop the currently dragged
- * element. Returns the element to attach to and the method of
- * attachment ('before', 'after', or 'prepend').
- */
- findDestination: function (ele, yChange) {
- var eleY = ele.offset().top;
- var eleYEnd = eleY + ele.height();
- var containers = $(ele.data('droppable-class'));
-
- for (var i = 0; i < containers.length; i++) {
- var container = $(containers[i]);
- // Exclude the 'new unit' buttons, and make sure we don't
- // prepend an element to itself
- var siblings = container.children().filter(function () {
- return $(this).data('locator') !== undefined && !$(this).is(ele);
- });
- // If the container is collapsed, check to see if the
- // element is on top of its parent list -- don't check the
- // position of the container
- var parentList = container.parents(ele.data('parent-location-selector')).first();
- if (parentList.hasClass('collapsed')) {
- var parentListTop = parentList.offset().top;
- // To make it easier to drop subsections into collapsed sections (which have
- // a lot of visual padding around them), allow a fudge factor around the
- // parent element.
- var collapseFudge = 10;
- if (Math.abs(eleY - parentListTop) < collapseFudge ||
- (eleY > parentListTop &&
- eleYEnd - collapseFudge <= parentListTop + parentList.height())
- ) {
- return {
- ele: container,
- attachMethod: 'prepend',
- parentList: parentList
- };
- }
- }
- // Otherwise, do check the container
- else {
- // If the list is empty, we should prepend to it,
- // unless both elements are at the same location --
- // this prevents the user from being unable to expand
- // a section
- var containerY = container.offset().top;
- if (siblings.length == 0 &&
- containerY != eleY &&
- Math.abs(eleY - containerY) < 50) {
- return {
- ele: container,
- attachMethod: 'prepend'
- };
- }
- // Otherwise the list is populated, and we should attach before/after a sibling
- else {
- for (var j = 0; j < siblings.length; j++) {
- var $sibling = $(siblings[j]);
- var siblingY = $sibling.offset().top;
- var siblingHeight = $sibling.height();
- var siblingYEnd = siblingY + siblingHeight;
-
- // Facilitate dropping into the beginning or end of a list
- // (coming from opposite direction) via a "fudge factor". Math.min is for Jasmine test.
- var fudge = Math.min(Math.ceil(siblingHeight / 2), 20);
-
- // Dragging to top or bottom of a list with only one element is tricky
- // because the element being dragged may be the same size as the sibling.
- if (siblings.length == 1) {
- // Element being dragged is within the drop target. Use the direction
- // of the drag (yChange) to determine before or after.
- if (eleY + fudge >= siblingY && eleYEnd - fudge <= siblingYEnd) {
- return {
- ele: $sibling,
- attachMethod: yChange > 0 ? 'after' : 'before'
- };
- }
- // Element being dragged is before the drop target.
- else if (Math.abs(eleYEnd - siblingY) <= fudge) {
- return {
- ele: $sibling,
- attachMethod: 'before'
- };
- }
- // Element being dragged is after the drop target.
- else if (Math.abs(eleY - siblingYEnd) <= fudge) {
- return {
- ele: $sibling,
- attachMethod: 'after'
- };
- }
- }
- else {
- // Dragging up into end of list.
- if (j == siblings.length - 1 && yChange < 0 && Math.abs(eleY - siblingYEnd) <= fudge) {
- return {
- ele: $sibling,
- attachMethod: 'after'
- };
- }
- // Dragging up or down into beginning of list.
- else if (j == 0 && Math.abs(eleY - siblingY) <= fudge) {
- return {
- ele: $sibling,
- attachMethod: 'before'
- };
- }
- // Dragging down into end of list. Special handling required because
- // the element being dragged may be taller then the element being dragged over
- // (if eleY can never be >= siblingY, general case at the end does not work).
- else if (j == siblings.length - 1 && yChange > 0 &&
- Math.abs(eleYEnd - siblingYEnd) <= fudge) {
- return {
- ele: $sibling,
- attachMethod: 'after'
- };
- }
- else if (eleY >= siblingY && eleY <= siblingYEnd) {
- return {
- ele: $sibling,
- attachMethod: eleY - siblingY <= siblingHeight / 2 ? 'before' : 'after'
- };
- }
- }
- }
- }
- }
- }
- // Failed drag
- return {
- ele: null,
- attachMethod: ''
- }
- },
-
- // Information about the current drag.
- dragState: {},
-
- onDragStart: function (draggie, event, pointer) {
- var ele = $(draggie.element);
- this.dragState = {
- // Which element will be dropped into/onto on success
- dropDestination: null,
- // How we attach to the destination: 'before', 'after', 'prepend'
- attachMethod: '',
- // If dragging to an empty section, the parent section
- parentList: null,
- // The y location of the last dragMove event (to determine direction).
- lastY: 0,
- // The direction the drag is moving in (negative means up, positive down).
- dragDirection: 0
- };
- if (!ele.hasClass('collapsed')) {
- ele.addClass('collapsed');
- ele.find('.expand-collapse').first().addClass('expand').removeClass('collapse');
- // onDragStart gets called again after the collapse, so we can't just store a variable in the dragState.
- ele.addClass(this.expandOnDropClass);
- }
- },
-
- onDragMove: function (draggie, event, pointer) {
- // Handle scrolling of the browser.
- var scrollAmount = 0;
- var dragBuffer = 10;
- if (window.innerHeight - dragBuffer < pointer.clientY) {
- scrollAmount = dragBuffer;
- }
- else if (dragBuffer > pointer.clientY) {
- scrollAmount = -(dragBuffer);
- }
- if (scrollAmount !== 0) {
- window.scrollBy(0, scrollAmount);
- return;
- }
-
- var yChange = draggie.dragPoint.y - this.dragState.lastY;
- if (yChange !== 0) {
- this.dragState.direction = yChange;
- }
- this.dragState.lastY = draggie.dragPoint.y;
-
- var ele = $(draggie.element);
- var destinationInfo = this.findDestination(ele, this.dragState.direction);
- var destinationEle = destinationInfo.ele;
- this.dragState.parentList = destinationInfo.parentList;
-
- // Clear out the old destination
- if (this.dragState.dropDestination) {
- this.dragState.dropDestination.removeClass(this.droppableClasses);
- }
- // Mark the new destination
- if (destinationEle && this.pointerInBounds(pointer, ele)) {
- ele.addClass(this.validDropClass);
- destinationEle.addClass('drop-target drop-target-' + destinationInfo.attachMethod);
- this.dragState.attachMethod = destinationInfo.attachMethod;
- this.dragState.dropDestination = destinationEle;
- }
- else {
- ele.removeClass(this.validDropClass);
- this.dragState.attachMethod = '';
- this.dragState.dropDestination = null;
- }
- },
-
- onDragEnd: function (draggie, event, pointer) {
- var ele = $(draggie.element);
- var destination = this.dragState.dropDestination;
-
- // Clear dragging state in preparation for the next event.
- if (destination) {
- destination.removeClass(this.droppableClasses);
- }
- ele.removeClass(this.validDropClass);
-
- // If the drag succeeded, rearrange the DOM and send the result.
- if (destination && this.pointerInBounds(pointer, ele)) {
- // Make sure we don't drop into a collapsed element
- if (this.dragState.parentList) {
- this.expandElement(this.dragState.parentList);
- }
- var method = this.dragState.attachMethod;
- destination[method](ele);
- this.handleReorder(ele);
- }
- // If the drag failed, send it back
- else {
- $('.was-dragging').removeClass('was-dragging');
- ele.addClass('was-dragging');
- }
-
- if (ele.hasClass(this.expandOnDropClass)) {
- this.expandElement(ele);
- ele.removeClass(this.expandOnDropClass);
- }
-
- // Everything in its right place
- ele.css({
- top: 'auto',
- left: 'auto'
- });
-
- this.dragState = {};
- },
-
- pointerInBounds: function (pointer, ele) {
- return pointer.clientX >= ele.offset().left && pointer.clientX < ele.offset().left + ele.width();
- },
-
- expandElement: function (ele) {
- ele.removeClass('collapsed');
- ele.find('.expand-collapse').first().removeClass('expand').addClass('collapse');
- },
-
- /*
- * Find all parent-child changes and save them.
- */
- handleReorder: function (ele) {
- var parentSelector = ele.data('parent-location-selector');
- var childrenSelector = ele.data('child-selector');
- var newParentEle = ele.parents(parentSelector).first();
- var newParentLocator = newParentEle.data('locator');
- var oldParentLocator = ele.data('parent');
- // If the parent has changed, update the children of the old parent.
- if (newParentLocator !== oldParentLocator) {
- // Find the old parent element.
- var oldParentEle = $(parentSelector).filter(function () {
- return $(this).data('locator') === oldParentLocator;
- });
- this.saveItem(oldParentEle, childrenSelector, function () {
- ele.data('parent', newParentLocator);
- });
- }
- var saving = new NotificationView.Mini({
- title: gettext('Saving…')
- });
- saving.show();
- ele.addClass('was-dropped');
- // Timeout interval has to match what is in the CSS.
- setTimeout(function () {
- ele.removeClass('was-dropped');
- }, 1000);
- this.saveItem(newParentEle, childrenSelector, function () {
- saving.hide();
- });
- },
-
- /*
- * Actually save the update to the server. Takes the element
- * representing the parent item to save, a CSS selector to find
- * its children, and a success callback.
- */
- saveItem: function (ele, childrenSelector, success) {
- // Find all current child IDs.
- var children = _.map(
- ele.find(childrenSelector),
- function (child) {
- return $(child).data('locator');
- }
- );
- $.ajax({
- url: ModuleUtils.getUpdateUrl(ele.data('locator')),
- type: 'PUT',
- dataType: 'json',
- contentType: 'application/json',
- data: JSON.stringify({
- children: children
- }),
- success: success
- });
- },
-
- /*
- * Make `type` draggable using `handleClass`, able to be dropped
- * into `droppableClass`, and with parent type
- * `parentLocationSelector`.
- */
- makeDraggable: function (type, handleClass, droppableClass, parentLocationSelector) {
- _.each(
- $(type),
- function (ele) {
- // Remember data necessary to reconstruct the parent-child relationships
- $(ele).data('droppable-class', droppableClass);
- $(ele).data('parent-location-selector', parentLocationSelector);
- $(ele).data('child-selector', type);
- var draggable = new Draggabilly(ele, {
- handle: handleClass,
- containment: '.wrapper-dnd'
- });
- draggable.on('dragStart', _.bind(overviewDragger.onDragStart, overviewDragger));
- draggable.on('dragMove', _.bind(overviewDragger.onDragMove, overviewDragger));
- draggable.on('dragEnd', _.bind(overviewDragger.onDragEnd, overviewDragger));
- }
- );
- }
- };
domReady(function() {
// toggling overview section details
@@ -566,21 +228,21 @@ define(["domReady", "jquery", "jquery.ui", "underscore", "gettext", "js/views/fe
$('.new-subsection-item').bind('click', addNewSubsection);
// Section
- overviewDragger.makeDraggable(
+ ContentDragger.makeDraggable(
'.courseware-section',
'.section-drag-handle',
'.courseware-overview',
'article.courseware-overview'
);
// Subsection
- overviewDragger.makeDraggable(
+ ContentDragger.makeDraggable(
'.id-holder',
'.subsection-drag-handle',
'.subsection-list > ol',
'.courseware-section'
);
// Unit
- overviewDragger.makeDraggable(
+ ContentDragger.makeDraggable(
'.unit',
'.unit-drag-handle',
'ol.sortable-unit-list',
@@ -589,7 +251,6 @@ define(["domReady", "jquery", "jquery.ui", "underscore", "gettext", "js/views/fe
});
return {
- overviewDragger: overviewDragger,
saveSetSectionScheduleDate: saveSetSectionScheduleDate
};
});
diff --git a/cms/templates/js/mock/mock-outline.underscore b/cms/templates/js/mock/mock-outline.underscore
new file mode 100644
index 0000000000..2991935827
--- /dev/null
+++ b/cms/templates/js/mock/mock-outline.underscore
@@ -0,0 +1,29 @@
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+
+
\ No newline at end of file