Commit 650bdc2f by cahrens

Drag and drop support on the course outline page.

parent e8ae3d1b
...@@ -131,14 +131,3 @@ def all_sections_are_collapsed_or_expanded(step, text): ...@@ -131,14 +131,3 @@ def all_sections_are_collapsed_or_expanded(step, text):
def change_grading_status(step): def change_grading_status(step):
world.css_find('a.menu-toggle').click() world.css_find('a.menu-toggle').click()
world.css_find('.menu li').first.click() world.css_find('.menu li').first.click()
@step(u'I reorder subsections')
def reorder_subsections(_step):
draggable_css = '.subsection-drag-handle'
ele = world.css_find(draggable_css).first
ele.action_chains.drag_and_drop_by_offset(
ele._element,
0,
25
).perform()
define(["js/utils/drag_and_drop", "js/views/feedback_notification", "js/spec_helpers/create_sinon", "jquery"], define(["js/utils/drag_and_drop", "js/views/feedback_notification", "js/spec_helpers/create_sinon", "jquery", "underscore"],
function (ContentDragger, Notification, create_sinon, $) { function (ContentDragger, Notification, create_sinon, $, _) {
describe("Overview drag and drop functionality", function () { describe("Overview drag and drop functionality", function () {
beforeEach(function () { beforeEach(function () {
setFixtures(readFixtures('mock/mock-outline.underscore')); setFixtures(readFixtures('mock/mock-outline.underscore'));
ContentDragger.makeDraggable('.unit', '.unit-drag-handle', 'ol.sortable-unit-list', 'li.courseware-subsection, article.subsection-body'); _.each(
ContentDragger.makeDraggable('.courseware-subsection', '.subsection-drag-handle', '.sortable-subsection-list', 'section'); $('.unit'),
function (element) {
ContentDragger.makeDraggable(element, {
type: '.unit',
handleClass: '.unit-drag-handle',
droppableClass: 'ol.sortable-unit-list',
parentLocationSelector: 'li.courseware-subsection',
refresh: jasmine.createSpy('Spy on Unit')
});
}
);
_.each(
$('.courseware-subsection'),
function (element) {
ContentDragger.makeDraggable(element, {
type: '.courseware-subsection',
handleClass: '.subsection-drag-handle',
droppableClass: '.sortable-subsection-list',
parentLocationSelector: 'section',
refresh: jasmine.createSpy('Spy on Subsection')
});
}
);
}); });
describe("findDestination", function () { describe("findDestination", function () {
...@@ -115,7 +137,7 @@ define(["js/utils/drag_and_drop", "js/views/feedback_notification", "js/spec_hel ...@@ -115,7 +137,7 @@ define(["js/utils/drag_and_drop", "js/views/feedback_notification", "js/spec_hel
}); });
it("can drag into a collapsed list", function () { it("can drag into a collapsed list", function () {
var $ele, destination; var $ele, destination;
$('#subsection-2').addClass('collapsed'); $('#subsection-2').addClass('is-collapsed');
$ele = $('#unit-2'); $ele = $('#unit-2');
$ele.offset({ $ele.offset({
top: $('#subsection-2').offset().top + 3, top: $('#subsection-2').offset().top + 3,
...@@ -142,11 +164,11 @@ define(["js/utils/drag_and_drop", "js/views/feedback_notification", "js/spec_hel ...@@ -142,11 +164,11 @@ define(["js/utils/drag_and_drop", "js/views/feedback_notification", "js/spec_hel
}); });
}); });
it("collapses expanded elements", function () { it("collapses expanded elements", function () {
expect($('#subsection-1')).not.toHaveClass('collapsed'); expect($('#subsection-1')).not.toHaveClass('is-collapsed');
ContentDragger.onDragStart({ ContentDragger.onDragStart({
element: $('#subsection-1') element: $('#subsection-1')
}, null, null); }, null, null);
expect($('#subsection-1')).toHaveClass('collapsed'); expect($('#subsection-1')).toHaveClass('is-collapsed');
expect($('#subsection-1')).toHaveClass('expand-on-drop'); expect($('#subsection-1')).toHaveClass('expand-on-drop');
}); });
}); });
...@@ -246,16 +268,16 @@ define(["js/utils/drag_and_drop", "js/views/feedback_notification", "js/spec_hel ...@@ -246,16 +268,16 @@ define(["js/utils/drag_and_drop", "js/views/feedback_notification", "js/spec_hel
expect(['0px', 'auto']).toContain($('#unit-1').css('left')); expect(['0px', 'auto']).toContain($('#unit-1').css('left'));
}); });
it("expands an element if it was collapsed on drag start", function () { it("expands an element if it was collapsed on drag start", function () {
$('#subsection-1').addClass('collapsed'); $('#subsection-1').addClass('is-collapsed');
$('#subsection-1').addClass('expand-on-drop'); $('#subsection-1').addClass('expand-on-drop');
ContentDragger.onDragEnd({ ContentDragger.onDragEnd({
element: $('#subsection-1') element: $('#subsection-1')
}, null, null); }, null, null);
expect($('#subsection-1')).not.toHaveClass('collapsed'); expect($('#subsection-1')).not.toHaveClass('is-collapsed');
expect($('#subsection-1')).not.toHaveClass('expand-on-drop'); expect($('#subsection-1')).not.toHaveClass('expand-on-drop');
}); });
it("expands a collapsed element when something is dropped in it", function () { it("expands a collapsed element when something is dropped in it", function () {
$('#subsection-2').addClass('collapsed'); $('#subsection-2').addClass('is-collapsed');
ContentDragger.dragState.dropDestination = $('#list-2'); ContentDragger.dragState.dropDestination = $('#list-2');
ContentDragger.dragState.attachMethod = "prepend"; ContentDragger.dragState.attachMethod = "prepend";
ContentDragger.dragState.parentList = $('#subsection-2'); ContentDragger.dragState.parentList = $('#subsection-2');
...@@ -264,7 +286,7 @@ define(["js/utils/drag_and_drop", "js/views/feedback_notification", "js/spec_hel ...@@ -264,7 +286,7 @@ define(["js/utils/drag_and_drop", "js/views/feedback_notification", "js/spec_hel
}, null, { }, null, {
clientX: $('#unit-1').offset().left clientX: $('#unit-1').offset().left
}); });
expect($('#subsection-2')).not.toHaveClass('collapsed'); expect($('#subsection-2')).not.toHaveClass('is-collapsed');
}); });
}); });
describe("AJAX", function () { describe("AJAX", function () {
...@@ -306,6 +328,10 @@ define(["js/utils/drag_and_drop", "js/views/feedback_notification", "js/spec_hel ...@@ -306,6 +328,10 @@ define(["js/utils/drag_and_drop", "js/views/feedback_notification", "js/spec_hel
expect(this.savingSpies.hide).toHaveBeenCalled(); expect(this.savingSpies.hide).toHaveBeenCalled();
this.clock.tick(1001); this.clock.tick(1001);
expect($('#unit-1')).not.toHaveClass('was-dropped'); expect($('#unit-1')).not.toHaveClass('was-dropped');
// source
expect($('#subsection-1').data('refresh')).toHaveBeenCalled();
// target
expect($('#subsection-2').data('refresh')).toHaveBeenCalled();
}); });
}); });
}); });
......
...@@ -179,7 +179,7 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers" ...@@ -179,7 +179,7 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
create_sinon.expectJsonRequest(requests, 'GET', '/xblock/outline/mock-course'); create_sinon.expectJsonRequest(requests, 'GET', '/xblock/outline/mock-course');
create_sinon.respondWithJson(requests, mockSingleSectionCourseJSON); create_sinon.respondWithJson(requests, mockSingleSectionCourseJSON);
expect(outlinePage.$('.no-content')).not.toExist(); expect(outlinePage.$('.no-content')).not.toExist();
expect(outlinePage.$('.list-sections li').data('locator')).toEqual('mock-section'); expect(outlinePage.$('.list-sections li.outline-section').data('locator')).toEqual('mock-section');
}); });
it('can add a second section', function() { it('can add a second section', function() {
...@@ -237,7 +237,7 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers" ...@@ -237,7 +237,7 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
create_sinon.expectJsonRequest(requests, 'GET', '/xblock/outline/mock-course'); create_sinon.expectJsonRequest(requests, 'GET', '/xblock/outline/mock-course');
create_sinon.respondWithJson(requests, mockSingleSectionCourseJSON); create_sinon.respondWithJson(requests, mockSingleSectionCourseJSON);
expect(outlinePage.$('.no-content')).not.toExist(); expect(outlinePage.$('.no-content')).not.toExist();
expect(outlinePage.$('.list-sections li').data('locator')).toEqual('mock-section'); expect(outlinePage.$('.list-sections li.outline-section').data('locator')).toEqual('mock-section');
}); });
it('remains empty if an add fails', function() { it('remains empty if an add fails', function() {
...@@ -303,7 +303,7 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers" ...@@ -303,7 +303,7 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
requestCount = requests.length; requestCount = requests.length;
create_sinon.respondWithError(requests); create_sinon.respondWithError(requests);
expect(requests.length).toBe(requestCount); // No additional requests should be made expect(requests.length).toBe(requestCount); // No additional requests should be made
expect(outlinePage.$('.list-sections li').data('locator')).toEqual('mock-section'); expect(outlinePage.$('.list-sections li.outline-section').data('locator')).toEqual('mock-section');
}); });
it('can add a subsection', function() { it('can add a subsection', function() {
......
...@@ -9,15 +9,20 @@ ...@@ -9,15 +9,20 @@
* - adding units will automatically redirect to the unit page rather than showing them inline * - adding units will automatically redirect to the unit page rather than showing them inline
*/ */
define(["jquery", "underscore", "js/views/xblock_outline", "js/views/utils/view_utils", define(["jquery", "underscore", "js/views/xblock_outline", "js/views/utils/view_utils",
"js/models/xblock_outline_info", "js/models/xblock_outline_info", "js/views/modals/edit_outline_item", "js/utils/drag_and_drop"],
"js/views/modals/edit_outline_item"], function($, _, XBlockOutlineView, ViewUtils, XBlockOutlineInfo, EditSectionXBlockModal, ContentDragger) {
function($, _, XBlockOutlineView, ViewUtils, XBlockOutlineInfo, EditSectionXBlockModal) {
var CourseOutlineView = XBlockOutlineView.extend({ var CourseOutlineView = XBlockOutlineView.extend({
// takes XBlockOutlineInfo as a model // takes XBlockOutlineInfo as a model
templateName: 'course-outline', templateName: 'course-outline',
render: function() {
var renderResult = XBlockOutlineView.prototype.render.call(this);
this.makeContentDraggable(this.el);
return renderResult;
},
shouldExpandChildren: function() { shouldExpandChildren: function() {
// Expand the children if this xblock's locator is in the initially expanded state // Expand the children if this xblock's locator is in the initially expanded state
if (this.initialState && _.contains(this.initialState.expanded_locators, this.model.id)) { if (this.initialState && _.contains(this.initialState.expanded_locators, this.model.id)) {
...@@ -154,6 +159,36 @@ define(["jquery", "underscore", "js/views/xblock_outline", "js/views/utils/view_ ...@@ -154,6 +159,36 @@ define(["jquery", "underscore", "js/views/xblock_outline", "js/views/utils/view_
event.preventDefault(); event.preventDefault();
this.editXBlock(); this.editXBlock();
}.bind(this)); }.bind(this));
},
makeContentDraggable: function(element) {
if ($(element).hasClass("outline-section")) {
ContentDragger.makeDraggable(element, {
type: '.outline-section',
handleClass: '.section-drag-handle',
droppableClass: 'ol.list-sections',
parentLocationSelector: 'article.outline',
refresh: this.refresh.bind(this)
});
}
else if ($(element).hasClass("outline-subsection")) {
ContentDragger.makeDraggable(element, {
type: '.outline-subsection',
handleClass: '.subsection-drag-handle',
droppableClass: 'ol.list-subsections',
parentLocationSelector: 'li.outline-section',
refresh: this.refresh.bind(this)
});
}
else if ($(element).hasClass("outline-unit")) {
ContentDragger.makeDraggable(element, {
type: '.outline-unit',
handleClass: '.unit-drag-handle',
droppableClass: 'ol.list-units',
parentLocationSelector: 'li.outline-subsection',
refresh: this.refresh.bind(this)
});
}
} }
}); });
......
define(["domReady", "jquery", "jquery.ui", "underscore", "gettext", "js/views/feedback_notification", "js/utils/drag_and_drop", define(["domReady", "jquery", "jquery.ui", "underscore", "gettext", "js/views/feedback_notification",
"js/utils/cancel_on_escape", "js/utils/date_utils", "js/utils/module"], "js/utils/cancel_on_escape", "js/utils/date_utils", "js/utils/module"],
function (domReady, $, ui, _, gettext, NotificationView, ContentDragger, CancelOnEscape, function (domReady, $, ui, _, gettext, NotificationView, CancelOnEscape,
DateUtils, ModuleUtils) { DateUtils, ModuleUtils) {
var modalSelector = '.edit-section-publish-settings'; var modalSelector = '.edit-section-publish-settings';
...@@ -37,9 +37,9 @@ define(["domReady", "jquery", "jquery.ui", "underscore", "gettext", "js/views/fe ...@@ -37,9 +37,9 @@ define(["domReady", "jquery", "jquery.ui", "underscore", "gettext", "js/views/fe
var closeModalNew = function (e) { var closeModalNew = function (e) {
if (e) { if (e) {
e.preventDefault(); e.preventDefault();
}; }
$('body').removeClass('modal-window-is-shown'); $('body').removeClass('modal-window-is-shown');
$('.edit-section-publish-settings').removeClass('is-shown'); $('.edit-section-publish-settings').removeClass('is-shown');
}; };
...@@ -230,27 +230,6 @@ define(["domReady", "jquery", "jquery.ui", "underscore", "gettext", "js/views/fe ...@@ -230,27 +230,6 @@ define(["domReady", "jquery", "jquery.ui", "underscore", "gettext", "js/views/fe
$('.new-courseware-section-button').bind('click', addNewSection); $('.new-courseware-section-button').bind('click', addNewSection);
$('.new-subsection-item').bind('click', addNewSubsection); $('.new-subsection-item').bind('click', addNewSubsection);
// Section
ContentDragger.makeDraggable(
'.courseware-section',
'.section-drag-handle',
'.courseware-overview',
'article.courseware-overview'
);
// Subsection
ContentDragger.makeDraggable(
'.id-holder',
'.subsection-drag-handle',
'.subsection-list > ol',
'.courseware-section'
);
// Unit
ContentDragger.makeDraggable(
'.unit',
'.unit-drag-handle',
'ol.sortable-unit-list',
'li.courseware-subsection, article.subsection-body'
);
}); });
return { return {
......
...@@ -394,6 +394,9 @@ ...@@ -394,6 +394,9 @@
box-shadow: 0 1px 2px 0 $blue-t2; box-shadow: 0 1px 2px 0 $blue-t2;
} }
} }
.was-dragging {
@include transition(transform $tmg-f2 ease-in-out 0);
}
// UI: drag state - was dragging // UI: drag state - was dragging
.was-dragging { .was-dragging {
......
...@@ -224,6 +224,10 @@ ...@@ -224,6 +224,10 @@
color: $blue; color: $blue;
} }
} }
&.is-dragging {
@include transition-property(none);
}
} }
// item: title // item: title
......
...@@ -35,6 +35,8 @@ if (statusType === 'warning') { ...@@ -35,6 +35,8 @@ if (statusType === 'warning') {
<li class="outline-item outline-<%= xblockType %> <%= visibilityClass %> is-draggable <%= includesChildren ? 'is-collapsible' : '' %> <%= isCollapsed ? 'is-collapsed' : '' %>" <li class="outline-item outline-<%= xblockType %> <%= visibilityClass %> is-draggable <%= includesChildren ? 'is-collapsible' : '' %> <%= isCollapsed ? 'is-collapsed' : '' %>"
data-parent="<%= parentInfo.get('id') %>" data-locator="<%= xblockInfo.get('id') %>"> data-parent="<%= parentInfo.get('id') %>" data-locator="<%= xblockInfo.get('id') %>">
<span class="draggable-drop-indicator draggable-drop-indicator-before"><i class="icon-caret-right"></i></span>
<div class="<%= xblockType %>-header"> <div class="<%= xblockType %>-header">
<% if (includesChildren) { %> <% if (includesChildren) { %>
<h3 class="<%= xblockType %>-header-details expand-collapse <%= isCollapsed ? 'expand' : 'collapse' %> ui-toggle-expansion" title="<%= gettext('Collapse/Expand this Checklist') %>"> <h3 class="<%= xblockType %>-header-details expand-collapse <%= isCollapsed ? 'expand' : 'collapse' %> ui-toggle-expansion" title="<%= gettext('Collapse/Expand this Checklist') %>">
...@@ -69,6 +71,12 @@ if (statusType === 'warning') { ...@@ -69,6 +71,12 @@ if (statusType === 'warning') {
<span class="sr action-button-text"><%= gettext('Delete') %></span> <span class="sr action-button-text"><%= gettext('Delete') %></span>
</a> </a>
</li> </li>
<li class="action-item action-drag">
<span data-tooltip="<%= gettext('Drag to reorder') %>"
class="drag-handle <%= xblockType %>-drag-handle action">
<span class="sr"><%= gettext('Drag to reorder') %></span>
</span>
</li>
</ul> </ul>
</div> </div>
</div> </div>
...@@ -125,10 +133,14 @@ if (statusType === 'warning') { ...@@ -125,10 +133,14 @@ if (statusType === 'warning') {
</a> </a>
</p> </p>
</div> </div>
<% } else { %> <% } else if (category !== 'vertical') { %>
<div class="outline-content <%= xblockType %>-content"> <div class="outline-content <%= xblockType %>-content">
<ol class="<%= typeListClass %> is-sortable"> <ol class="<%= typeListClass %> is-sortable">
<li class="ui-splint ui-splint-indicator">
<span class="draggable-drop-indicator draggable-drop-indicator-initial"><i class="icon-caret-right"></i></span>
</li>
</ol> </ol>
<% if (childType) { %> <% if (childType) { %>
<div class="add-<%= childType %> add-item"> <div class="add-<%= childType %> add-item">
<a href="#" class="button button-new" data-category="<%= childCategory %>" <a href="#" class="button button-new" data-category="<%= childCategory %>"
...@@ -141,5 +153,6 @@ if (statusType === 'warning') { ...@@ -141,5 +153,6 @@ if (statusType === 'warning') {
<% } %> <% } %>
<% if (parentInfo) { %> <% if (parentInfo) { %>
<span class="draggable-drop-indicator draggable-drop-indicator-after"><i class="icon-caret-right"></i></span>
</li> </li>
<% } %> <% } %>
...@@ -29,11 +29,6 @@ ...@@ -29,11 +29,6 @@
<span class="sr"><%= gettext('Delete') %></span> <span class="sr"><%= gettext('Delete') %></span>
</a> </a>
</li> </li>
<li class="actions-item drag">
<span data-tooltip="<%= gettext('Drag to reorder') %>" class="drag-handle">
<span class="sr"><%= gettext('Drag to reorder') %></span>
</span>
</li>
</ul> </ul>
</div> </div>
</div> </div>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment