Commit 1b011af7 by muhammad-ammar Committed by Mushtaq Ali

move dialog

TNL-6047
parent 52f8c976
......@@ -129,8 +129,8 @@ def edit_component(index=0):
# Verify that the "loading" indication has been hidden.
world.wait_for_loading()
# Verify that the "edit" button is present.
world.wait_for(lambda _driver: world.css_visible('a.edit-button'))
world.css_click('a.edit-button', index)
world.wait_for(lambda _driver: world.css_visible('.edit-button'))
world.css_click('.edit-button', index)
world.wait_for_ajax_complete()
......
......@@ -38,7 +38,7 @@ def not_see_any_static_pages(step):
@step(u'I "(edit|delete)" the static page$')
def click_edit_or_delete(step, edit_or_delete):
button_css = 'ul.component-actions a.%s-button' % edit_or_delete
button_css = 'ul.component-actions .%s-button' % edit_or_delete
world.css_click(button_css)
......
......@@ -54,9 +54,10 @@ class StudioPageTestCase(CourseTestCase):
# Verify that there are no action buttons for public blocks
expected_button_html = [
'<a href="#" class="edit-button action-button">',
'<a href="#" data-tooltip="Delete" class="delete-button action-button">',
'<a href="#" data-tooltip="Duplicate" class="duplicate-button action-button">'
'<button class="btn-default edit-button action-button">',
'<button data-tooltip="Delete" class="btn-default delete-button action-button">',
'<button data-tooltip="Duplicate" class="btn-default duplicate-button action-button">',
'<button data-tooltip="Move" class="btn-default move-button action-button">'
]
for button_html in expected_button_html:
self.assertIn(button_html, html)
......
......@@ -282,6 +282,7 @@
'js/spec/views/pages/library_users_spec',
'js/spec/views/modals/base_modal_spec',
'js/spec/views/modals/edit_xblock_spec',
'js/spec/views/modals/move_xblock_spec',
'js/spec/views/modals/validation_error_modal_spec',
'js/spec/views/settings/main_spec',
'js/spec/factories/xblock_validation_spec',
......
......@@ -69,7 +69,7 @@ define(['jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'js/spec_
// Give the leaf elements some height to mimic actual components. Otherwise
// drag and drop fails as the elements on bunched on top of each other.
$('.level-element').css('height', 200);
$('.level-element').css('height', 230);
return requests;
};
......@@ -92,7 +92,7 @@ define(['jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'js/spec_
var targetElement = getComponent(targetLocator),
targetTop = targetElement.offset().top + 1,
handle = getDragHandle(sourceLocator),
handleY = handle.offset().top + (handle.height() / 2),
handleY = handle.offset().top,
dy = targetTop - handleY;
handle.simulate('drag', {dy: dy});
};
......
define(['jquery', 'underscore', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers',
'common/js/spec_helpers/template_helpers', 'js/views/modals/move_xblock_modal', 'js/models/xblock_info'],
function($, _, AjaxHelpers, TemplateHelpers, MoveXBlockModal, XBlockInfo) {
'use strict';
describe('MoveXBlockModal', function() {
var modal,
showModal,
DISPLAY_NAME = 'HTML 101';
showModal = function() {
modal = new MoveXBlockModal({
sourceXBlockInfo: new XBlockInfo({
id: 'testCourse/branch/draft/block/verticalFFF',
display_name: DISPLAY_NAME,
category: 'html'
}),
XBlockUrlRoot: '/xblock'
});
modal.show();
};
beforeEach(function() {
TemplateHelpers.installTemplates([
'basic-modal',
'modal-button',
'move-xblock-modal'
]);
showModal();
});
it('rendered as expected', function() {
expect(modal.$el.find('.modal-header .title').text()).toEqual('Move: ' + DISPLAY_NAME);
expect(modal.$el.find('.modal-actions .action-primary.action-move').text()).toEqual('Move');
});
});
});
......@@ -20,7 +20,8 @@ define(['jquery', 'underscore', 'underscore.string', 'edx-ui-toolkit/js/utils/sp
mockXBlockVisibilityEditorHtml = readFixtures('mock/mock-xblock-visibility-editor.underscore'),
PageClass = globalPageOptions.page,
pagedSpecificTests = globalPageOptions.pagedSpecificTests,
hasVisibilityEditor = globalPageOptions.hasVisibilityEditor;
hasVisibilityEditor = globalPageOptions.hasVisibilityEditor,
hasMoveModal = globalPageOptions.hasMoveModal;
beforeEach(function() {
var newDisplayName = 'New Display Name';
......@@ -250,6 +251,19 @@ define(['jquery', 'underscore', 'underscore.string', 'edx-ui-toolkit/js/utils/sp
expect(visibilityButtons.length).toBe(0);
}
});
it('can show a move modal for a child xblock', function() {
var moveButtons;
renderContainerPage(this, mockContainerXBlockHtml);
moveButtons = containerPage.$('.wrapper-xblock .move-button');
if (hasMoveModal) {
expect(moveButtons.length).toBe(6);
moveButtons[0].click();
expect(EditHelpers.isShowingModal()).toBeTruthy();
} else {
expect(moveButtons.length).toBe(0);
}
});
});
describe('Editing an xmodule', function() {
......@@ -798,7 +812,8 @@ define(['jquery', 'underscore', 'underscore.string', 'edx-ui-toolkit/js/utils/sp
initial: 'mock/mock-container-xblock.underscore',
addResponse: 'mock/mock-xblock.underscore',
hasVisibilityEditor: true,
pagedSpecificTests: false
pagedSpecificTests: false,
hasMoveModal: true
}
);
......@@ -811,7 +826,8 @@ define(['jquery', 'underscore', 'underscore.string', 'edx-ui-toolkit/js/utils/sp
initial: 'mock/mock-container-paged-xblock.underscore',
addResponse: 'mock/mock-xblock-paged.underscore',
hasVisibilityEditor: false,
pagedSpecificTests: true
pagedSpecificTests: true,
hasMoveModal: false
}
);
});
......@@ -16,8 +16,10 @@
* size of the modal.
* viewSpecificClasses: A string of CSS classes to be attached to
* the modal window.
* addSaveButton: A boolean indicating whether to include a save
* addPrimaryActionButton: A boolean indicating whether to include a primary action
* button on the modal.
* primaryActionButtonType: A string to be used as type for primary action button.
* primaryActionButtonTitle: A string to be used as title for primary action button.
*/
define(['jquery', 'underscore', 'gettext', 'js/views/baseview'],
function($, _, gettext, BaseView) {
......@@ -36,7 +38,10 @@ define(['jquery', 'underscore', 'gettext', 'js/views/baseview'],
title: '',
modalWindowClass: '.modal-window',
// A list of class names, separated by space.
viewSpecificClasses: ''
viewSpecificClasses: '',
addPrimaryActionButton: false,
primaryActionButtonType: 'save',
primaryActionButtonTitle: gettext('Save')
}),
initialize: function() {
......@@ -84,14 +89,17 @@ define(['jquery', 'underscore', 'gettext', 'js/views/baseview'],
return '';
},
show: function() {
show: function(focusModal) {
var focusModalWindow = focusModal === undefined;
this.render();
this.resize();
$(window).resize(_.bind(this.resize, this));
// after showing and resizing, send focus
var modal = this.$el.find(this.options.modalWindowClass);
modal.focus();
// child may want to have its own focus management
if (focusModalWindow) {
// after showing and resizing, send focus
this.$el.find(this.options.modalWindowClass).focus();
}
},
hide: function() {
......@@ -112,8 +120,12 @@ define(['jquery', 'underscore', 'gettext', 'js/views/baseview'],
* Adds the action buttons to the modal.
*/
addActionButtons: function() {
if (this.options.addSaveButton) {
this.addActionButton('save', gettext('Save'), true);
if (this.options.addPrimaryActionButton) {
this.addActionButton(
this.options.primaryActionButtonType,
this.options.primaryActionButtonTitle,
true
);
}
this.addActionButton('cancel', gettext('Cancel'));
},
......
......@@ -25,7 +25,7 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
options: $.extend({}, BaseModal.prototype.options, {
modalName: 'course-outline',
modalType: 'edit-settings',
addSaveButton: true,
addPrimaryActionButton: true,
modalSize: 'med',
viewSpecificClasses: 'confirm',
editors: []
......
......@@ -4,9 +4,9 @@
* and upon save an optional refresh function can be invoked to update the display.
*/
define(['jquery', 'underscore', 'gettext', 'js/views/modals/base_modal', 'common/js/components/utils/view_utils',
'js/models/xblock_info', 'js/views/xblock_editor'],
function($, _, gettext, BaseModal, ViewUtils, XBlockInfo, XBlockEditorView) {
'strict mode';
'js/views/utils/xblock_utils', 'js/views/xblock_editor'],
function($, _, gettext, BaseModal, ViewUtils, XBlockViewUtils, XBlockEditorView) {
'use strict';
var EditXBlockModal = BaseModal.extend({
events: _.extend({}, BaseModal.prototype.events, {
......@@ -16,11 +16,11 @@ define(['jquery', 'underscore', 'gettext', 'js/views/modals/base_modal', 'common
options: $.extend({}, BaseModal.prototype.options, {
modalName: 'edit-xblock',
addSaveButton: true,
view: 'studio_view',
viewSpecificClasses: 'modal-editor confirm',
// Translators: "title" is the name of the current component being edited.
titleFormat: gettext('Editing: %(title)s')
titleFormat: gettext('Editing: %(title)s'),
addPrimaryActionButton: true
}),
initialize: function() {
......@@ -37,7 +37,7 @@ define(['jquery', 'underscore', 'gettext', 'js/views/modals/base_modal', 'common
*/
edit: function(xblockElement, rootXBlockInfo, options) {
this.xblockElement = xblockElement;
this.xblockInfo = this.findXBlockInfo(xblockElement, rootXBlockInfo);
this.xblockInfo = XBlockViewUtils.findXBlockInfo(xblockElement, rootXBlockInfo);
this.options.modalType = this.xblockInfo.get('category');
this.editOptions = options;
this.render();
......@@ -183,28 +183,6 @@ define(['jquery', 'underscore', 'gettext', 'js/views/modals/base_modal', 'common
this.editorView.notifyRuntime('modal-hidden');
},
findXBlockInfo: function(xblockWrapperElement, defaultXBlockInfo) {
var xblockInfo = defaultXBlockInfo,
xblockElement,
displayName;
if (xblockWrapperElement.length > 0) {
xblockElement = xblockWrapperElement.find('.xblock');
displayName = xblockWrapperElement.find('.xblock-header .header-details .xblock-display-name').text().trim();
// If not found, try looking for the old unit page style rendering.
// Only used now by static pages.
if (!displayName) {
displayName = this.xblockElement.find('.component-header').text().trim();
}
xblockInfo = new XBlockInfo({
id: xblockWrapperElement.data('locator'),
courseKey: xblockWrapperElement.data('course-key'),
category: xblockElement.data('block-type'),
display_name: displayName
});
}
return xblockInfo;
},
addModeButton: function(mode, displayName) {
var buttonPanel = this.$('.editor-modes');
buttonPanel.append(this.editorModeButtonTemplate({
......
/**
* The MoveXblockModal to move XBlocks in course.
*/
define([
'jquery', 'backbone', 'underscore', 'gettext',
'js/views/baseview', 'js/views/modals/base_modal',
'common/js/components/views/feedback',
'edx-ui-toolkit/js/utils/string-utils',
'text!templates/move-xblock-modal.underscore'
],
function($, Backbone, _, gettext, BaseView, BaseModal, Feedback, StringUtils, MoveXblockModalTemplate) {
'use strict';
var MoveXblockModal = BaseModal.extend({
options: $.extend({}, BaseModal.prototype.options, {
modalName: 'move-xblock',
modalSize: 'med',
addPrimaryActionButton: true,
primaryActionButtonType: 'move',
primaryActionButtonTitle: gettext('Move')
}),
initialize: function() {
BaseModal.prototype.initialize.call(this);
this.sourceXBlockInfo = this.options.sourceXBlockInfo;
this.XBlockUrlRoot = this.options.sourceXBlockInfo;
this.options.title = this.getTitle();
},
getTitle: function() {
return StringUtils.interpolate(
gettext('Move: {display_name}'),
{display_name: this.sourceXBlockInfo.get('display_name')}
);
},
getContentHtml: function() {
return _.template(MoveXblockModalTemplate)({});
},
show: function() {
BaseModal.prototype.show.apply(this, [false]);
Feedback.prototype.inFocus.apply(this, [this.options.modalWindowClass]);
},
hide: function() {
BaseModal.prototype.hide.apply(this);
Feedback.prototype.outFocus.apply(this);
}
});
return MoveXblockModal;
});
......@@ -4,11 +4,11 @@
*/
define(['jquery', 'underscore', 'gettext', 'js/views/pages/base_page', 'common/js/components/utils/view_utils',
'js/views/container', 'js/views/xblock', 'js/views/components/add_xblock', 'js/views/modals/edit_xblock',
'js/models/xblock_info', 'js/views/xblock_string_field_editor', 'js/views/pages/container_subviews',
'js/views/unit_outline', 'js/views/utils/xblock_utils'],
'js/views/modals/move_xblock_modal', 'js/models/xblock_info', 'js/views/xblock_string_field_editor',
'js/views/pages/container_subviews', 'js/views/unit_outline', 'js/views/utils/xblock_utils'],
function($, _, gettext, BasePage, ViewUtils, ContainerView, XBlockView, AddXBlockComponent,
EditXBlockModal, XBlockInfo, XBlockStringFieldEditor, ContainerSubviews, UnitOutlineView,
XBlockUtils) {
EditXBlockModal, MoveXBlockModal, XBlockInfo, XBlockStringFieldEditor, ContainerSubviews,
UnitOutlineView, XBlockUtils) {
'use strict';
var XBlockContainerPage = BasePage.extend({
// takes XBlockInfo as a model
......@@ -17,6 +17,7 @@ define(['jquery', 'underscore', 'gettext', 'js/views/pages/base_page', 'common/j
'click .edit-button': 'editXBlock',
'click .visibility-button': 'editVisibilitySettings',
'click .duplicate-button': 'duplicateXBlock',
'click .move-button': 'showMoveXBlockModal',
'click .delete-button': 'deleteXBlock',
'click .new-component-button': 'scrollToNewComponentButtons'
},
......@@ -191,6 +192,17 @@ define(['jquery', 'underscore', 'gettext', 'js/views/pages/base_page', 'common/j
this.duplicateComponent(this.findXBlockElement(event.target));
},
showMoveXBlockModal: function(event) {
var xblockElement = this.findXBlockElement(event.target),
modal = new MoveXBlockModal({
sourceXBlockInfo: XBlockUtils.findXBlockInfo(xblockElement, this.model),
XBlockUrlRoot: this.getURLRoot()
});
event.preventDefault();
modal.show();
},
deleteXBlock: function(event) {
event.preventDefault();
this.deleteComponent(this.findXBlockElement(event.target));
......
......@@ -2,11 +2,11 @@
* Provides utilities for views to work with xblocks.
*/
define(['jquery', 'underscore', 'gettext', 'common/js/components/utils/view_utils', 'js/utils/module',
'edx-ui-toolkit/js/utils/string-utils'],
function($, _, gettext, ViewUtils, ModuleUtils, StringUtils) {
'js/models/xblock_info', 'edx-ui-toolkit/js/utils/string-utils'],
function($, _, gettext, ViewUtils, ModuleUtils, XBlockInfo, StringUtils) {
'use strict';
var addXBlock, duplicateXBlock, deleteXBlock, createUpdateRequestData, updateXBlockField, VisibilityState,
getXBlockVisibilityClass, getXBlockListTypeClass, updateXBlockFields, getXBlockType;
getXBlockVisibilityClass, getXBlockListTypeClass, updateXBlockFields, getXBlockType, findXBlockInfo;
/**
* Represents the possible visibility states for an xblock:
......@@ -240,15 +240,40 @@ define(['jquery', 'underscore', 'gettext', 'common/js/components/utils/view_util
return xblockType;
};
findXBlockInfo = function(xblockWrapperElement, defaultXBlockInfo) {
var xblockInfo = defaultXBlockInfo,
xblockElement,
displayName;
if (xblockWrapperElement.length > 0) {
xblockElement = xblockWrapperElement.find('.xblock');
displayName = xblockWrapperElement.find(
'.xblock-header .header-details .xblock-display-name'
).text().trim();
// If not found, try looking for the old unit page style rendering.
// Only used now by static pages.
if (!displayName) {
displayName = xblockElement.find('.component-header').text().trim();
}
xblockInfo = new XBlockInfo({
id: xblockWrapperElement.data('locator'),
courseKey: xblockWrapperElement.data('course-key'),
category: xblockElement.data('block-type'),
display_name: displayName
});
}
return xblockInfo;
};
return {
'VisibilityState': VisibilityState,
'addXBlock': addXBlock,
VisibilityState: VisibilityState,
addXBlock: addXBlock,
duplicateXBlock: duplicateXBlock,
'deleteXBlock': deleteXBlock,
'updateXBlockField': updateXBlockField,
'getXBlockVisibilityClass': getXBlockVisibilityClass,
'getXBlockListTypeClass': getXBlockListTypeClass,
'updateXBlockFields': updateXBlockFields,
'getXBlockType': getXBlockType
deleteXBlock: deleteXBlock,
updateXBlockField: updateXBlockField,
getXBlockVisibilityClass: getXBlockVisibilityClass,
getXBlockListTypeClass: getXBlockListTypeClass,
updateXBlockFields: updateXBlockFields,
getXBlockType: getXBlockType,
findXBlockInfo: findXBlockInfo
};
});
......@@ -205,7 +205,7 @@
text-shadow: 0 1px 0 $btn-lms-shadow;
background-clip: padding-box;
font-size: 0.8125em;
&:focus,
&:hover {
box-shadow: inset 0 1px 0 0 $btn-lms-shadow-hover;
......@@ -214,7 +214,7 @@
background-image: -webkit-linear-gradient($btn-lms-background-hover,$btn-lms-gradient-hover);
background-image: linear-gradient($btn-lms-background-hover,$btn-lms-gradient-hover);
}
&:active {
border: 1px solid $btn-lms-border;
box-shadow: inset 0 0 8px 4px $btn-lms-shadow-active,inset 0 0 8px 4px $btn-lms-shadow-active;
......@@ -336,6 +336,14 @@
&.toggle-action {
// TODO: generalize and move checkbox styling in from static-pages and assets sass
}
.btn-default.delete-button {
border: none;
}
.btn-default.edit-button {
font-weight: 300;
}
}
}
......
......@@ -8,22 +8,28 @@
</div>
<ul class="component-actions">
<li class="action-item action-edit">
<a href="#" class="edit-button action-button">
<button class="btn-default edit-button action-button">
<span class="icon fa fa-pencil" aria-hidden="true"></span>
<span class="action-button-text">${_("Edit")}</span>
</a>
</button>
</li>
<li class="action-item action-duplicate">
<a href="#" data-tooltip="${_("Duplicate")}" class="duplicate-button action-button">
<button data-tooltip="${_("Duplicate")}" class="btn-default duplicate-button action-button">
<span class="icon fa fa-copy" aria-hidden="true"></span>
<span class="sr">${_("Duplicate this component")}</span>
</a>
</button>
</li>
<li class="action-item action-move">
<button data-tooltip="${_("Move")}" class="btn-default move-button action-button">
<span class="icon fa fa-folder-o" aria-hidden="true"></span>
<span class="sr">${_("Move")}</span>
</button>
</li>
<li class="action-item action-delete">
<a href="#" data-tooltip="${_("Delete")}" class="delete-button action-button">
<button data-tooltip="${_("Delete")}" class="btn-default delete-button action-button">
<span class="icon fa fa-trash-o" aria-hidden="true"></span>
<span class="sr">${_("Delete this component")}</span>
</a>
</button>
</li>
</ul>
</div>
......
<div class='breadcrumb-container'>
</div>
<div class='treeview-container'>
</div>
......@@ -69,35 +69,42 @@ messages = xblock.validate().to_json()
% if can_edit:
% if not show_inline:
<li class="action-item action-edit">
<a href="#" class="edit-button action-button">
<button class="btn-default edit-button action-button">
<span class="icon fa fa-pencil" aria-hidden="true"></span>
<span class="action-button-text">${_("Edit")}</span>
</a>
</button>
</li>
% if can_edit_visibility:
<li class="action-item action-visibility">
<a href="#" data-tooltip="${_("Visibility Settings")}" class="visibility-button action-button">
<button data-tooltip="${_("Visibility Settings")}" class="btn-default visibility-button action-button">
<span class="icon fa fa-eye" aria-hidden="true"></span>
<span class="sr">${_("Visibility")}</span>
</a>
</button>
</li>
% endif
% if can_add:
<li class="action-item action-duplicate">
<a href="#" data-tooltip="${_("Duplicate")}" class="duplicate-button action-button">
<button data-tooltip="${_("Duplicate")}" class="btn-default duplicate-button action-button">
<span class="icon fa fa-copy" aria-hidden="true"></span>
<span class="sr">${_("Duplicate")}</span>
</a>
</button>
</li>
<li class="action-item action-move">
<button data-tooltip="${_("Move")}" class="btn-default move-button action-button">
<span class="icon fa fa-folder-o" aria-hidden="true"></span>
<span class="sr">${_("Move")}</span>
</button>
</li>
% endif
% endif
% if can_add:
<!-- If we can add, we can delete. -->
<li class="action-item action-delete">
<a href="#" data-tooltip="${_("Delete")}" class="delete-button action-button">
<button data-tooltip="${_("Delete")}" class="btn-default delete-button action-button">
<span class="icon fa fa-trash-o" aria-hidden="true"></span>
<span class="sr">${_("Delete")}</span>
</a>
</button>
</li>
% endif
% if is_reorderable:
......@@ -149,7 +156,7 @@ messages = xblock.validate().to_json()
${content}
</div>
% endif
% endif
% endif
% if not is_root:
<!-- footer for xblock_aside -->
......
......@@ -78,16 +78,17 @@
return this;
},
inFocus: function() {
inFocus: function(wrapperElementSelector) {
var wrapper = wrapperElementSelector || '.wrapper',
tabbables;
this.options.outFocusElement = this.options.outFocusElement || document.activeElement;
// Set focus to the container.
this.$('.wrapper').first().focus();
this.$(wrapper).first().focus();
// Make tabs within the prompt loop rather than setting focus
// back to the main content of the page.
var tabbables = this.$(tabbable_elements.join());
tabbables = this.$(tabbable_elements.join());
tabbables.on('keydown', function(event) {
// On tab backward from the first tabbable item in the prompt
if (event.which === 9 && event.shiftKey && event.target === tabbables.first()[0]) {
......
......@@ -243,7 +243,7 @@ class ContainerPage(PageObject, HelpMixin):
"""
Duplicate the item with index source_index (based on vertical placement in page).
"""
click_css(self, 'a.duplicate-button', source_index)
click_css(self, '.duplicate-button', source_index)
def delete(self, source_index):
"""
......@@ -252,7 +252,7 @@ class ContainerPage(PageObject, HelpMixin):
The index of the first item is 0.
"""
# Click the delete button
click_css(self, 'a.delete-button', source_index, require_notification=False)
click_css(self, '.delete-button', source_index, require_notification=False)
# Click the confirmation dialog button
confirm_prompt(self)
......@@ -451,14 +451,14 @@ class XBlockWrapper(PageObject):
"""
Returns true if this xblock has a 'duplicate' button
"""
return self.q(css=self._bounded_selector('a.duplicate-button'))
return self.q(css=self._bounded_selector('.duplicate-button'))
@property
def has_delete_button(self):
"""
Returns true if this xblock has a 'delete' button
"""
return self.q(css=self._bounded_selector('a.delete-button'))
return self.q(css=self._bounded_selector('.delete-button'))
@property
def has_edit_visibility_button(self):
......
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