Commit e9b8e17f by Mushtaq Ali

Enable disable move - TNL-6063

parent e856f07b
......@@ -735,6 +735,7 @@ class TestDuplicateItem(ItemTest, DuplicateHelper):
verify_name(self.seq_usage_key, self.chapter_usage_key, "customized name", display_name="customized name")
@ddt.ddt
class TestMoveItem(ItemTest):
"""
Tests for move item.
......@@ -744,7 +745,16 @@ class TestMoveItem(ItemTest):
Creates the test course structure to build course outline tree.
"""
super(TestMoveItem, self).setUp()
self.setup_course()
def setup_course(self, default_store=None):
"""
Helper method to create the course.
"""
if not default_store:
default_store = self.store.default_modulestore.get_modulestore_type()
self.course = CourseFactory.create(default_store=default_store)
# Create a parent chapter
chap1 = self.create_xblock(parent_usage_key=self.course.location, display_name='chapter1', category='chapter')
self.chapter_usage_key = self.response_usage_key(chap1)
......@@ -821,10 +831,15 @@ class TestMoveItem(ItemTest):
self.assertEqual(new_parent_loc, target_usage_key)
self.assertNotEqual(parent_loc, new_parent_loc)
def test_move_component(self):
@ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split)
def test_move_component(self, store_type):
"""
Test move component with different xblock types.
Arguments:
store_type (ModuleStoreEnum.Type): Type of modulestore to create test course in.
"""
self.setup_course(default_store=store_type)
for source_usage_key, target_usage_key in [
(self.html_usage_key, self.vert2_usage_key),
(self.vert_usage_key, self.seq2_usage_key),
......
......@@ -59,10 +59,10 @@
success: callback
});
};
$.postJSON = function(url, data, callback) {
$.postJSON = function(url, data, callback) { // eslint-disable-line no-param-reassign
return sendJSON(url, data, callback, 'POST');
};
$.patchJSON = function(url, data, callback) {
$.patchJSON = function(url, data, callback) { // eslint-disable-line no-param-reassign
return sendJSON(url, data, callback, 'PATCH');
};
return domReady(function() {
......
......@@ -2,25 +2,29 @@
* The MoveXblockModal to move XBlocks in course.
*/
define([
'jquery', 'backbone', 'underscore', 'gettext',
'js/views/baseview', 'js/views/modals/base_modal',
'js/models/xblock_info', 'js/views/move_xblock_list', 'js/views/move_xblock_breadcrumb',
'common/js/components/views/feedback',
'jquery',
'backbone',
'underscore',
'gettext',
'js/views/baseview',
'js/views/utils/xblock_utils',
'js/views/utils/move_xblock_utils',
'edx-ui-toolkit/js/utils/html-utils',
'edx-ui-toolkit/js/utils/string-utils',
'common/js/components/views/feedback',
'js/models/xblock_info',
'js/views/modals/base_modal',
'js/views/move_xblock_list',
'js/views/move_xblock_breadcrumb',
'text!templates/move-xblock-modal.underscore'
],
function($, Backbone, _, gettext, BaseView, BaseModal, XBlockInfoModel, MoveXBlockListView, MoveXBlockBreadcrumbView,
Feedback, XBlockViewUtils, MoveXBlockUtils, HtmlUtils, StringUtils, MoveXblockModalTemplate) {
function($, Backbone, _, gettext, BaseView, XBlockViewUtils, MoveXBlockUtils, HtmlUtils, StringUtils, Feedback,
XBlockInfoModel, BaseModal, MoveXBlockListView, MoveXBlockBreadcrumbView, MoveXblockModalTemplate) {
'use strict';
var MoveXblockModal = BaseModal.extend({
modalSRTitle: gettext('Choose a location to move your component to'),
events: _.extend({}, BaseModal.prototype.events, {
'click .action-move': 'moveXBlock'
'click .action-move:not(.is-disabled)': 'moveXBlock'
}),
options: $.extend({}, BaseModal.prototype.options, {
......@@ -40,6 +44,7 @@ function($, Backbone, _, gettext, BaseView, BaseModal, XBlockInfoModel, MoveXBlo
this.listenTo(Backbone, 'move:breadcrumbRendered', this.focusModal);
this.sourceXBlockInfo = this.options.sourceXBlockInfo;
this.sourceParentXBlockInfo = this.options.sourceParentXBlockInfo;
this.targetParentXBlockInfo = null;
this.XBlockURLRoot = this.options.XBlockURLRoot;
this.XBlockAncestorInfoURL = StringUtils.interpolate(
'{urlRoot}/{usageId}?fields=ancestorInfo',
......@@ -52,10 +57,9 @@ function($, Backbone, _, gettext, BaseView, BaseModal, XBlockInfoModel, MoveXBlo
$('.breadcrumb-container').removeClass('is-hidden');
self.renderViews(courseOutlineInfo, ancestorInfo);
});
this.targetParentXBlockInfo = null;
this.movedAlertView = null;
this.moveXBlockBreadcrumbView = null;
this.moveXBlockListView = null;
this.isValidMove = false;
this.listenTo(Backbone, 'move:enableMoveOperation', this.enableMoveOperation);
},
getTitle: function() {
......@@ -71,7 +75,8 @@ function($, Backbone, _, gettext, BaseView, BaseModal, XBlockInfoModel, MoveXBlo
show: function() {
BaseModal.prototype.show.apply(this, [false]);
Feedback.prototype.inFocus.apply(this, [this.options.modalWindowClass]);
this.updateMoveState(false);
MoveXBlockUtils.hideMovedNotification();
},
hide: function() {
......@@ -122,18 +127,35 @@ function($, Backbone, _, gettext, BaseView, BaseModal, XBlockInfoModel, MoveXBlo
);
},
updateMoveState: function(isValidMove) {
var $moveButton = this.$el.find('.action-move');
if (isValidMove) {
$moveButton.removeClass('is-disabled');
} else {
$moveButton.addClass('is-disabled');
}
},
enableMoveOperation: function(targetParentXBlockInfo) {
var isValidMove = false,
sourceParentType = this.sourceParentXBlockInfo.get('category'),
targetParentType = targetParentXBlockInfo.get('category');
if (targetParentType === sourceParentType && this.sourceParentXBlockInfo.id !== targetParentXBlockInfo.id) {
isValidMove = true;
this.targetParentXBlockInfo = targetParentXBlockInfo;
}
this.updateMoveState(isValidMove);
},
moveXBlock: function() {
var self = this;
XBlockViewUtils.moveXBlock(self.sourceXBlockInfo.id, self.moveXBlockListView.parent_info.parent.id)
XBlockViewUtils.moveXBlock(self.sourceXBlockInfo.id, self.targetParentXBlockInfo.id)
.done(function(response) {
if (response.move_source_locator) {
// hide modal
self.hide();
// hide xblock element
$("li.studio-xblock-wrapper[data-locator='" + self.sourceXBlockInfo.id + "']").hide();
if (self.movedAlertView) {
self.movedAlertView.hide();
}
self.movedAlertView = MoveXBlockUtils.showMovedNotification(
StringUtils.interpolate(
gettext('Success! "{displayName}" has been moved.'),
......@@ -141,30 +163,14 @@ function($, Backbone, _, gettext, BaseView, BaseModal, XBlockInfoModel, MoveXBlo
displayName: self.sourceXBlockInfo.get('display_name')
}
),
StringUtils.interpolate(
gettext('{link_start}Take me to the new location{link_end}'),
{
link_start: HtmlUtils.HTML('<a href="/container/' + response.parent_locator + '">'),
link_end: HtmlUtils.HTML('</a>')
}
),
HtmlUtils.interpolateHtml(
HtmlUtils.HTML(
'<a class="action-undo-move" href="#" data-source-display-name="{displayName}" ' +
'data-source-locator="{sourceLocator}" ' +
'data-source-parent-locator="{sourceParentLocator}" ' +
'data-target-index="{targetIndex}">{undoMove}</a>'
),
{
displayName: self.sourceXBlockInfo.get('display_name'),
sourceDisplayName: self.sourceXBlockInfo.get('display_name'),
sourceLocator: self.sourceXBlockInfo.id,
sourceParentLocator: self.sourceParentXBlockInfo.id,
targetIndex: response.source_index,
undoMove: gettext('Undo move')
targetParentLocator: response.parent_locator,
targetIndex: response.source_index
}
)
);
}
});
}
});
......
......@@ -63,6 +63,7 @@ function($, Backbone, _, gettext, HtmlUtils, StringUtils, XBlockUtils, MoveXBloc
)
);
Backbone.trigger('move:childrenRendered', this.breadcrumbInfo());
Backbone.trigger('move:enableMoveOperation', this.parentInfo.parent);
return this;
},
......
......@@ -198,7 +198,7 @@ define(['jquery', 'underscore', 'gettext', 'js/views/pages/base_page', 'common/j
modal = new MoveXBlockModal({
sourceXBlockInfo: XBlockUtils.findXBlockInfo(xblockElement, this.model),
sourceParentXBlockInfo: XBlockUtils.findXBlockInfo(parentXBlockElement, this.model),
XBlockUrlRoot: this.getURLRoot(),
XBlockURLRoot: this.getURLRoot(),
outlineURL: this.options.outlineURL
});
......
/**
* Provides utilities for move xblock.
*/
define(['jquery', 'underscore', 'common/js/components/views/feedback_alert', 'js/views/utils/xblock_utils',
'js/views/utils/move_xblock_utils', 'edx-ui-toolkit/js/utils/string-utils'],
function($, _, AlertView, XBlockViewUtils, MoveXBlockUtils, StringUtils) {
define([
'jquery',
'underscore',
'common/js/components/views/feedback',
'common/js/components/views/feedback_alert',
'js/views/utils/xblock_utils',
'js/views/utils/move_xblock_utils',
'edx-ui-toolkit/js/utils/string-utils'
],
function($, _, Feedback, AlertView, XBlockViewUtils, MoveXBlockUtils, StringUtils) {
'use strict';
var MovedAlertView, showMovedNotification;
var redirectLink, undoMoveXBlock, showMovedNotification, hideMovedNotification;
MovedAlertView = AlertView.Confirmation.extend({
events: _.extend({}, AlertView.Confirmation.prototype.events, {
'click .action-undo-move': 'undoMoveXBlock'
}),
options: $.extend({}, AlertView.Confirmation.prototype.options),
initialize: function() {
AlertView.prototype.initialize.apply(this, arguments);
this.movedAlertView = null;
},
redirectLink = function(link) {
window.location.href = link;
};
undoMoveXBlock: function(event) {
var self = this,
$moveButton = $(event.target),
sourceLocator = $moveButton.data('source-locator'),
sourceDisplayName = $moveButton.data('source-display-name'),
sourceParentLocator = $moveButton.data('source-parent-locator'),
targetIndex = $moveButton.data('target-index');
XBlockViewUtils.moveXBlock(sourceLocator, sourceParentLocator, targetIndex)
undoMoveXBlock = function(data) {
XBlockViewUtils.moveXBlock(data.sourceLocator, data.sourceParentLocator, data.targetIndex)
.done(function(response) {
// show XBlock element
$('.studio-xblock-wrapper[data-locator="' + response.move_source_locator + '"]').show();
if (self.movedAlertView) {
self.movedAlertView.hide();
}
self.movedAlertView = showMovedNotification(
showMovedNotification(
StringUtils.interpolate(
gettext('Move cancelled. "{sourceDisplayName}" has been moved back to its original ' +
'location.'),
gettext('Move cancelled. "{sourceDisplayName}" has been moved back to its original location.'),
{
sourceDisplayName: sourceDisplayName
sourceDisplayName: data.sourceDisplayName
}
)
);
});
}
});
};
showMovedNotification = function(title, titleHtml, messageHtml) {
var movedAlertView = new MovedAlertView({
showMovedNotification = function(title, data) {
var movedAlertView;
// data is provided when we click undo move button.
if (data) {
movedAlertView = new AlertView.Confirmation({
title: title,
titleHtml: titleHtml,
messageHtml: messageHtml,
maxShown: 10000
actions: {
primary: {
text: gettext('Undo move'),
class: 'action-save',
data: JSON.stringify({
sourceDisplayName: data.sourceDisplayName,
sourceLocator: data.sourceLocator,
sourceParentLocator: data.sourceParentLocator,
targetIndex: data.targetIndex
}),
click: function() {
undoMoveXBlock(
{
sourceDisplayName: data.sourceDisplayName,
sourceLocator: data.sourceLocator,
sourceParentLocator: data.sourceParentLocator,
targetIndex: data.targetIndex
}
);
}
},
secondary: [
{
text: gettext('Take me to the new location'),
class: 'action-cancel',
data: JSON.stringify({
targetParentLocator: data.targetParentLocator
}),
click: function() {
redirectLink('/container/' + data.targetParentLocator);
}
}
]
}
});
} else {
movedAlertView = new AlertView.Confirmation({
title: title
});
}
movedAlertView.show();
// scroll to top
$.smoothScroll({
......@@ -60,10 +87,20 @@ define(['jquery', 'underscore', 'common/js/components/views/feedback_alert', 'js
easing: 'swing',
speed: 1000
});
movedAlertView.$('.wrapper').first().focus();
return movedAlertView;
};
hideMovedNotification = function() {
var movedAlertView = Feedback.active_alert;
if (movedAlertView) {
AlertView.prototype.hide.apply(movedAlertView);
}
};
return {
showMovedNotification: showMovedNotification
redirectLink: redirectLink,
showMovedNotification: showMovedNotification,
hideMovedNotification: hideMovedNotification
};
});
});
......@@ -94,10 +94,10 @@ define(['jquery', 'underscore', 'gettext', 'common/js/components/utils/view_util
/**
* Moves the specified xblock in a new parent xblock.
* @param {String} sourceLocator The xblock element to be moved.
* @param {String} targetParentLocator Target parent xblock locator of the xblock to be moved,
* new moved xblock would be placed under this xblock.
* @param {String} targetIndex Intended index position of the xblock in parent xblock. If provided,
* @param {String} sourceLocator Locator of xblock element to be moved.
* @param {String} targetParentLocator Locator of the target parent xblock, moved xblock would be placed
* under this xblock.
* @param {Integer} targetIndex Intended index position of the xblock in parent xblock. If provided,
* xblock would be placed at the particular index in the parent xblock.
* @returns {jQuery promise} A promise representing the moving of the xblock.
*/
......@@ -110,8 +110,8 @@ define(['jquery', 'underscore', 'gettext', 'common/js/components/utils/view_util
move_source_locator: sourceLocator,
parent_locator: targetParentLocator,
target_index: targetIndex
}, function(data) {
moveOperation.resolve(data);
}, function(response) {
moveOperation.resolve(response);
})
.fail(function() {
moveOperation.reject();
......
......@@ -297,6 +297,11 @@
.ui-loading {
box-shadow: none;
}
.modal-actions .action-move.is-disabled {
border: 1px solid $gray-l1 !important;
background: $gray-l1 !important;
}
}
// upload modal
......
......@@ -21,8 +21,6 @@
options: {
title: '',
message: '',
titleHtml: '', // an optional html that comes after the title.
messageHtml: '', // an optional html that comes after the message.
intent: null, // "warning", "confirmation", "error", "announcement", "step-required", etc
type: null, // "alert", "notification", or "prompt": set by subclass
shown: true, // is this view currently being shown?
......
/**
* The MovedAlertView to show confirmation message when moving XBlocks.
*/
(function(define) {
'use strict';
define(['jquery', 'underscore', 'common/js/components/views/feedback_alert', 'js/views/utils/xblock_utils',
'js/views/utils/move_xblock_utils', 'edx-ui-toolkit/js/utils/string-utils'],
function($, _, AlertView, XBlockViewUtils, MoveXBlockUtils, StringUtils) {
var MovedAlertView = AlertView.Confirmation.extend({
events: _.extend({}, AlertView.Confirmation.prototype.events, {
'click .action-undo-move': 'undoMoveXBlock'
}),
options: $.extend({}, AlertView.Confirmation.prototype.options),
initialize: function() {
AlertView.prototype.initialize.apply(this, arguments);
this.movedAlertView = null;
},
undoMoveXBlock: function(event) {
var self = this,
$moveButton = $(event.target),
sourceLocator = $moveButton.data('source-locator'),
sourceDisplayName = $moveButton.data('source-display-name'),
sourceParentLocator = $moveButton.data('source-parent-locator'),
targetIndex = $moveButton.data('target-index');
XBlockViewUtils.moveXBlock(sourceLocator, sourceParentLocator, targetIndex)
.done(function(response) {
// show XBlock element
$('.studio-xblock-wrapper[data-locator="' + response.move_source_locator + '"]').show();
if (self.movedAlertView) {
self.movedAlertView.hide();
}
self.movedAlertView = MoveXBlockUtils.showMovedNotification(
StringUtils.interpolate(
gettext('Move cancelled. "{sourceDisplayName}" has been moved back to its original ' +
'location.'),
{
sourceDisplayName: sourceDisplayName
}
)
);
});
}
});
return MovedAlertView;
});
}).call(this, define || RequireJS.define);
......@@ -15,9 +15,8 @@
<% } %>
<div class="copy">
<h2 class="title title-3" id="<%= type %>-<%= intent %>-title"><%- title %><% if(titleHtml) { %> <%= titleHtml %> <% } %></h2>
<h2 class="title title-3" id="<%= type %>-<%= intent %>-title"><%- title %></h2>
<% if(obj.message) { %><p class="message" id="<%= type %>-<%= intent %>-description"><%- message %></p><% } %>
<% if(messageHtml) { %> <%= messageHtml %> <% } %>
</div>
<% if(obj.actions) { %>
......
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