Commit e9b8e17f by Mushtaq Ali

Enable disable move - TNL-6063

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