Commit 1b2d1858 by Andy Armstrong

Fix duplicate save and cancel buttons in Studio

STUD-1531

Also add support for refreshing the modal on custom save
parent f1eefc11
...@@ -246,10 +246,6 @@ def xblock_view_handler(request, package_id, view_name, tag=None, branch=None, v ...@@ -246,10 +246,6 @@ def xblock_view_handler(request, package_id, view_name, tag=None, branch=None, v
fragment.content = render_to_string('component.html', { fragment.content = render_to_string('component.html', {
'preview': fragment.content, 'preview': fragment.content,
'label': component.display_name or component.scope_ids.block_type, 'label': component.display_name or component.scope_ids.block_type,
# Native XBlocks are responsible for persisting their own data,
# so they are also responsible for providing save/cancel buttons.
'show_save_cancel': isinstance(component, xmodule.x_module.XModuleDescriptor),
}) })
else: else:
raise Http404 raise Http404
......
...@@ -765,34 +765,3 @@ class TestComponentHandler(TestCase): ...@@ -765,34 +765,3 @@ class TestComponentHandler(TestCase):
self.descriptor.handle = create_response self.descriptor.handle = create_response
self.assertEquals(component_handler(self.request, self.usage_id, 'dummy_handler').status_code, status_code) self.assertEquals(component_handler(self.request, self.usage_id, 'dummy_handler').status_code, status_code)
@ddt.ddt
class TestNativeXBlock(ItemTest):
"""
Test a "native" XBlock (not an XModule shim).
"""
@ddt.data(('problem', True), ('acid', False))
@ddt.unpack
def test_save_cancel_buttons(self, category, include_buttons):
"""
Native XBlocks handle their own persistence, so Studio
should not render Save/Cancel buttons for them.
"""
# Create the XBlock
resp = self.create_xblock(category=category)
self.assertEqual(resp.status_code, 200)
native_loc = json.loads(resp.content)['locator']
# Render the XBlock
view_url = '/xblock/{locator}/student_view'.format(locator=native_loc)
resp = self.client.get(view_url, HTTP_ACCEPT='application/json')
self.assertEqual(resp.status_code, 200)
# Check that the save and cancel buttons are hidden for native XBlocks,
# but shown for XModule shim XBlocks
resp_html = json.loads(resp.content)['html']
assert_func = self.assertIn if include_buttons else self.assertNotIn
assert_func('save-button', resp_html)
assert_func('cancel-button', resp_html)
...@@ -25,7 +25,6 @@ define ["jquery", "js/spec_helpers/edit_helpers", "coffee/src/views/module_edit" ...@@ -25,7 +25,6 @@ define ["jquery", "js/spec_helpers/edit_helpers", "coffee/src/views/module_edit"
</section> </section>
</li> </li>
</ul> </ul>
<div class="edit-xblock-modal"/>
""" """
edit_helpers.installEditTemplates(true); edit_helpers.installEditTemplates(true);
spyOn($, 'ajax').andReturn(@moduleData) spyOn($, 'ajax').andReturn(@moduleData)
......
...@@ -47,7 +47,6 @@ define ["jquery", "underscore", "gettext", "xblock/runtime.v1", ...@@ -47,7 +47,6 @@ define ["jquery", "underscore", "gettext", "xblock/runtime.v1",
clickEditButton: (event) -> clickEditButton: (event) ->
event.preventDefault() event.preventDefault()
modal = new EditXBlockModal({ modal = new EditXBlockModal({
el: $('.edit-xblock-modal'),
view: 'student_view' view: 'student_view'
}); });
modal.edit(this.$el, self.model, { refresh: _.bind(@render, this) }) modal.edit(this.$el, self.model, { refresh: _.bind(@render, this) })
...@@ -5,74 +5,76 @@ define [ ...@@ -5,74 +5,76 @@ define [
@PreviewRuntime = {} @PreviewRuntime = {}
class PreviewRuntime.v1 extends XBlock.Runtime.v1 class PreviewRuntime.v1 extends XBlock.Runtime.v1
handlerUrl: (element, handlerName, suffix, query, thirdparty) -> handlerUrl: (element, handlerName, suffix, query, thirdparty) ->
uri = URI("/preview/xblock").segment($(element).data('usage-id')) uri = URI("/preview/xblock").segment($(element).data('usage-id'))
.segment('handler') .segment('handler')
.segment(handlerName) .segment(handlerName)
if suffix? then uri.segment(suffix) if suffix? then uri.segment(suffix)
if query? then uri.search(query) if query? then uri.search(query)
uri.toString() uri.toString()
@StudioRuntime = {} @StudioRuntime = {}
class StudioRuntime.v1 extends XBlock.Runtime.v1 class StudioRuntime.v1 extends XBlock.Runtime.v1
constructor: () -> constructor: () ->
super() super()
@savingNotification = new NotificationView.Mini @savingNotification = new NotificationView.Mini
title: gettext('Saving&hellip;') title: gettext('Saving&hellip;')
@alert = new NotificationView.Error @alert = new NotificationView.Error
title: "OpenAssessment Save Error", title: "OpenAssessment Save Error",
closeIcon: false, closeIcon: false,
shown: false shown: false
handlerUrl: (element, handlerName, suffix, query, thirdparty) -> handlerUrl: (element, handlerName, suffix, query, thirdparty) ->
uri = URI("/xblock").segment($(element).data('usage-id')) uri = URI("/xblock").segment($(element).data('usage-id'))
.segment('handler') .segment('handler')
.segment(handlerName) .segment(handlerName)
if suffix? then uri.segment(suffix) if suffix? then uri.segment(suffix)
if query? then uri.search(query) if query? then uri.search(query)
uri.toString() uri.toString()
# Notify the Studio client-side runtime so it can update # Notify the Studio client-side runtime so it can update
# the UI in a consistent way. Currently, this is used # the UI in a consistent way. Currently, this is used
# for save / cancel when editing an XBlock. # for save / cancel when editing an XBlock.
# Although native XBlocks should handle their own persistence, # Although native XBlocks should handle their own persistence,
# Studio still needs to update the UI in a consistent way # Studio still needs to update the UI in a consistent way
# (showing the "Saving..." notification, closing the modal editing dialog, etc.) # (showing the "Saving..." notification, closing the modal editing dialog, etc.)
notify: (name, data) -> notify: (name, data) ->
if name == 'save' if name == 'save'
if 'state' of data if 'state' of data
# Starting to save, so show the "Saving..." notification # Starting to save, so show the "Saving..." notification
if data.state == 'start' if data.state == 'start'
@savingNotification.show() @savingNotification.show()
# Finished saving, so hide the "Saving..." notification # Finished saving, so hide the "Saving..." notification
else if data.state == 'end' else if data.state == 'end'
@_hideAlerts()
# Hide the editor *after* we finish saving in case there are validation # Notify the modal that the save has completed so that it can hide itself
# errors that the user needs to correct. # and then refresh the xblock.
@_hideEditor() if @modal
@modal.onSave()
$('.component.editing').removeClass('editing') @savingNotification.hide()
@savingNotification.hide()
else if name == 'cancel' else if name == 'edit-modal-shown'
@_hideEditor() @modal = data
else if name == 'error' else if name == 'edit-modal-hidden'
if 'msg' of data @modal = null
@alert.options.message = data.msg
@alert.show()
_hideEditor: () -> else if name == 'cancel'
# This will close all open component editors, which works @_hideAlerts()
# if we assume that <= 1 are open at a time. if @modal
el = $('.component.editing') @modal.cancel()
el.removeClass('editing')
el.find('.component-editor').slideUp(150)
ModalUtils.hideModalCover()
# Hide any alerts that are being shown else if name == 'error'
if @alert.options.shown if 'msg' of data
@alert.hide() @alert.options.message = data.msg
@alert.show()
_hideAlerts: () ->
# Hide any alerts that are being shown
if @alert.options.shown
@alert.hide()
...@@ -5,9 +5,9 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers ...@@ -5,9 +5,9 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers
describe("EditXBlockModal", function() { describe("EditXBlockModal", function() {
var model, modal, showModal; var model, modal, showModal;
showModal = function(requests, mockHtml) { showModal = function(requests, mockHtml, options) {
var xblockElement = $('.xblock'); var xblockElement = $('.xblock');
return edit_helpers.showEditModal(requests, xblockElement, model, mockHtml); return edit_helpers.showEditModal(requests, xblockElement, model, mockHtml, options);
}; };
beforeEach(function () { beforeEach(function () {
...@@ -45,6 +45,13 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers ...@@ -45,6 +45,13 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers
expect(edit_helpers.isShowingModal(modal)).toBeFalsy(); expect(edit_helpers.isShowingModal(modal)).toBeFalsy();
}); });
it('does not show the "Save" button', function() {
var requests = create_sinon.requests(this);
modal = showModal(requests, mockXBlockEditorHtml);
expect(modal.$('.action-save')).not.toBeVisible();
expect(modal.$('.action-cancel').text()).toBe('OK');
});
it('shows the correct title', function() { it('shows the correct title', function() {
var requests = create_sinon.requests(this); var requests = create_sinon.requests(this);
modal = showModal(requests, mockXBlockEditorHtml); modal = showModal(requests, mockXBlockEditorHtml);
...@@ -56,6 +63,43 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers ...@@ -56,6 +63,43 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers
modal = showModal(requests, mockXBlockEditorHtml); modal = showModal(requests, mockXBlockEditorHtml);
expect(modal.$('.editor-modes a').length).toBe(0); expect(modal.$('.editor-modes a').length).toBe(0);
}); });
it('hides itself and refreshes after save notification', function() {
var requests = create_sinon.requests(this),
refreshed = false,
refresh = function() {
refreshed = true;
};
modal = showModal(requests, mockXBlockEditorHtml, { refresh: refresh });
modal.runtime.notify('save', { state: 'start' });
modal.runtime.notify('save', { state: 'end' });
expect(edit_helpers.isShowingModal(modal)).toBeFalsy();
expect(refreshed).toBeTruthy();
});
it('hides itself and does not refresh after cancel notification', function() {
var requests = create_sinon.requests(this),
refreshed = false,
refresh = function() {
refreshed = true;
};
modal = showModal(requests, mockXBlockEditorHtml, { refresh: refresh });
modal.runtime.notify('cancel');
expect(edit_helpers.isShowingModal(modal)).toBeFalsy();
expect(refreshed).toBeFalsy();
});
describe("Custom Buttons", function() {
var mockCustomButtonsHtml;
mockCustomButtonsHtml = readFixtures('mock/mock-xblock-editor-with-custom-buttons.underscore');
it('hides the modal\'s button bar', function() {
var requests = create_sinon.requests(this);
modal = showModal(requests, mockCustomButtonsHtml);
expect(modal.$('.modal-actions')).toBeHidden();
});
});
}); });
describe("XModule Editor", function() { describe("XModule Editor", function() {
...@@ -64,12 +108,11 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers ...@@ -64,12 +108,11 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers
mockXModuleEditorHtml = readFixtures('mock/mock-xmodule-editor.underscore'); mockXModuleEditorHtml = readFixtures('mock/mock-xmodule-editor.underscore');
beforeEach(function() { beforeEach(function() {
// Mock the VerticalDescriptor so that the module can be rendered edit_helpers.installMockXModule();
window.VerticalDescriptor = XModule.Descriptor;
}); });
afterEach(function () { afterEach(function () {
window.VerticalDescriptor = null; edit_helpers.uninstallMockXModule();
}); });
it('can render itself', function() { it('can render itself', function() {
...@@ -140,12 +183,11 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers ...@@ -140,12 +183,11 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers
mockXModuleEditorHtml = readFixtures('mock/mock-xmodule-settings-only-editor.underscore'); mockXModuleEditorHtml = readFixtures('mock/mock-xmodule-settings-only-editor.underscore');
beforeEach(function() { beforeEach(function() {
// Mock the VerticalDescriptor so that the module can be rendered edit_helpers.installMockXModule();
window.VerticalDescriptor = XModule.Descriptor;
}); });
afterEach(function () { afterEach(function () {
window.VerticalDescriptor = null; edit_helpers.uninstallMockXModule();
}); });
it('can render itself', function() { it('can render itself', function() {
......
...@@ -107,6 +107,29 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/edit_helpers" ...@@ -107,6 +107,29 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/edit_helpers"
}); });
expect(edit_helpers.isShowingModal()).toBeTruthy(); expect(edit_helpers.isShowingModal()).toBeTruthy();
}); });
});
describe("Editing an xmodule", function() {
var mockContainerXBlockHtml,
mockXModuleEditor,
newDisplayName = 'New Display Name';
beforeEach(function () {
edit_helpers.installMockXModule({
data: "<p>Some HTML</p>",
metadata: {
display_name: newDisplayName
}
});
});
afterEach(function() {
edit_helpers.uninstallMockXModule();
edit_helpers.cancelModalIfShowing();
});
mockContainerXBlockHtml = readFixtures('mock/mock-container-xblock.underscore');
mockXModuleEditor = readFixtures('mock/mock-xmodule-editor.underscore');
it('can save changes to settings', function() { it('can save changes to settings', function() {
var editButtons, modal, mockUpdatedXBlockHtml; var editButtons, modal, mockUpdatedXBlockHtml;
...@@ -117,7 +140,7 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/edit_helpers" ...@@ -117,7 +140,7 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/edit_helpers"
expect(editButtons.length).toBe(6); expect(editButtons.length).toBe(6);
editButtons.first().click(); editButtons.first().click();
create_sinon.respondWithJson(requests, { create_sinon.respondWithJson(requests, {
html: mockXBlockEditorHtml, html: mockXModuleEditor,
resources: [] resources: []
}); });
......
...@@ -49,20 +49,6 @@ define([ "jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helper ...@@ -49,20 +49,6 @@ define([ "jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helper
expect(editor.$el.select('.xblock-header')).toBeTruthy(); expect(editor.$el.select('.xblock-header')).toBeTruthy();
expect(editor.getMode()).toEqual('settings'); expect(editor.getMode()).toEqual('settings');
}); });
it('saves any custom metadata', function() {
var requests = create_sinon.requests(this), request, response;
editor.render();
create_sinon.respondWithJson(requests, {
html: mockXBlockEditorHtml,
resources: []
});
editor.save();
request = requests[requests.length - 1];
response = JSON.parse(request.requestBody);
expect(response.metadata.display_name).toBe(testDisplayName);
expect(response.metadata.custom_field).toBe('Custom Value');
});
}); });
describe("Editing an xmodule", function() { describe("Editing an xmodule", function() {
......
/** /**
* Provides helper methods for invoking Studio editors in Jasmine tests. * Provides helper methods for invoking Studio editors in Jasmine tests.
*/ */
define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/modal_helpers", "js/views/modals/edit_xblock", define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers/modal_helpers",
"xmodule", "coffee/src/main", "xblock/cms.runtime.v1"], "js/views/modals/edit_xblock", "xmodule", "coffee/src/main", "xblock/cms.runtime.v1"],
function($, create_sinon, modal_helpers, EditXBlockModal) { function($, _, create_sinon, modal_helpers, EditXBlockModal) {
var editorTemplate = readFixtures('metadata-editor.underscore'), var editorTemplate = readFixtures('metadata-editor.underscore'),
numberEntryTemplate = readFixtures('metadata-number-entry.underscore'), numberEntryTemplate = readFixtures('metadata-number-entry.underscore'),
...@@ -12,19 +12,15 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/modal_helpers ...@@ -12,19 +12,15 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/modal_helpers
editorModeButtonTemplate = readFixtures('editor-mode-button.underscore'), editorModeButtonTemplate = readFixtures('editor-mode-button.underscore'),
installMockXBlock, installMockXBlock,
uninstallMockXBlock, uninstallMockXBlock,
hasSavedMockXBlock,
installMockXModule, installMockXModule,
uninstallMockXModule, uninstallMockXModule,
hasSavedMockXModule,
installEditTemplates, installEditTemplates,
showEditModal; showEditModal;
installMockXBlock = function(mockResult) { installMockXBlock = function(mockResult) {
window.MockXBlock = function(runtime, element) { window.MockXBlock = function(runtime, element) {
return { return {
save: function() { runtime: runtime
return mockResult;
}
}; };
}; };
}; };
...@@ -58,9 +54,9 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/modal_helpers ...@@ -58,9 +54,9 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/modal_helpers
appendSetFixtures($("<script>", {id: "metadata-string-entry", type: "text/template"}).text(stringEntryTemplate)); appendSetFixtures($("<script>", {id: "metadata-string-entry", type: "text/template"}).text(stringEntryTemplate));
}; };
showEditModal = function(requests, xblockElement, model, mockHtml) { showEditModal = function(requests, xblockElement, model, mockHtml, options) {
var modal = new EditXBlockModal({}); var modal = new EditXBlockModal({});
modal.edit(xblockElement, model); modal.edit(xblockElement, model, options);
create_sinon.respondWithJson(requests, { create_sinon.respondWithJson(requests, {
html: mockHtml, html: mockHtml,
"resources": [] "resources": []
......
...@@ -47,7 +47,7 @@ define(["jquery"], ...@@ -47,7 +47,7 @@ define(["jquery"],
cancelModal = function(modal) { cancelModal = function(modal) {
var modalElement, cancelButton; var modalElement, cancelButton;
modalElement = getModalElement(modal); modalElement = getModalElement(modal);
cancelButton = modalElement.find('.action-cancel'); cancelButton = modalElement.find('.action-cancel:visible');
expect(cancelButton.length).toBe(1); expect(cancelButton.length).toBe(1);
cancelButton.click(); cancelButton.click();
}; };
......
...@@ -71,8 +71,10 @@ define(["jquery", "underscore", "gettext", "js/views/baseview"], ...@@ -71,8 +71,10 @@ define(["jquery", "underscore", "gettext", "js/views/baseview"],
}, },
cancel: function(event) { cancel: function(event) {
event.preventDefault(); if (event) {
event.stopPropagation(); // Make sure parent modals don't see the click event.preventDefault();
event.stopPropagation(); // Make sure parent modals don't see the click
}
this.hide(); this.hide();
}, },
...@@ -98,7 +100,21 @@ define(["jquery", "underscore", "gettext", "js/views/baseview"], ...@@ -98,7 +100,21 @@ define(["jquery", "underscore", "gettext", "js/views/baseview"],
name: name, name: name,
isPrimary: isPrimary isPrimary: isPrimary
}); });
this.$('.modal-actions ul').append(html); this.getActionBar().find('ul').append(html);
},
/**
* Returns the action bar that contains the modal's action buttons.
*/
getActionBar: function() {
return this.$('.modal-window > div > .modal-actions');
},
/**
* Returns the action button of the specified type.
*/
getActionButton: function(type) {
return this.getActionBar().find('.action-' + type);
}, },
resize: function() { resize: function() {
......
...@@ -37,6 +37,10 @@ define(["jquery", "underscore", "gettext", "js/views/modals/base_modal", ...@@ -37,6 +37,10 @@ define(["jquery", "underscore", "gettext", "js/views/modals/base_modal",
this.editOptions = options; this.editOptions = options;
this.render(); this.render();
this.show(); this.show();
// Hide the action bar until we know which buttons we want
this.getActionBar().hide();
// Display the xblock after the modal is shown as there are some xblocks // Display the xblock after the modal is shown as there are some xblocks
// that depend upon being visible when they initialize, e.g. the problem xmodule. // that depend upon being visible when they initialize, e.g. the problem xmodule.
this.displayXBlock(); this.displayXBlock();
...@@ -60,7 +64,17 @@ define(["jquery", "underscore", "gettext", "js/views/modals/base_modal", ...@@ -60,7 +64,17 @@ define(["jquery", "underscore", "gettext", "js/views/modals/base_modal",
onDisplayXBlock: function() { onDisplayXBlock: function() {
var editorView = this.editorView, var editorView = this.editorView,
title = this.getTitle(); title = this.getTitle(),
xblock = editorView.xblock,
runtime = xblock.runtime;
// Notify the runtime that the modal has been shown
if (runtime) {
this.runtime = runtime;
runtime.notify("edit-modal-shown", this);
}
// Update the modal's header
if (editorView.hasCustomTabs()) { if (editorView.hasCustomTabs()) {
// Hide the modal's header as the custom editor provides its own // Hide the modal's header as the custom editor provides its own
this.$('.modal-header').hide(); this.$('.modal-header').hide();
...@@ -74,9 +88,28 @@ define(["jquery", "underscore", "gettext", "js/views/modals/base_modal", ...@@ -74,9 +88,28 @@ define(["jquery", "underscore", "gettext", "js/views/modals/base_modal",
this.selectMode(editorView.mode); this.selectMode(editorView.mode);
} }
} }
// If the xblock is not using custom buttons then choose which buttons to show
if (!editorView.hasCustomButtons()) {
// If the xblock does not support save then disable the save button
if (!xblock.save) {
this.disableSave();
}
this.getActionBar().show();
}
// Resize the modal to fit the window
this.resize(); this.resize();
}, },
disableSave: function() {
var saveButton = this.getActionButton('save'),
cancelButton = this.getActionButton('cancel');
saveButton.hide();
cancelButton.text(gettext('OK'));
cancelButton.addClass('action-primary');
},
getTitle: function() { getTitle: function() {
var displayName = this.xblockElement.find('.xblock-header .header-details').text().trim(); var displayName = this.xblockElement.find('.xblock-header .header-details').text().trim();
// If not found, try the old unit page style rendering // If not found, try the old unit page style rendering
...@@ -117,23 +150,28 @@ define(["jquery", "underscore", "gettext", "js/views/modals/base_modal", ...@@ -117,23 +150,28 @@ define(["jquery", "underscore", "gettext", "js/views/modals/base_modal",
}, },
save: function(event) { save: function(event) {
var self = this,
xblockInfo = this.xblockInfo,
refresh = self.editOptions.refresh;
event.preventDefault(); event.preventDefault();
this.editorView.save({ this.editorView.save({
success: function() { success: _.bind(this.onSave, this)
self.hide();
if (refresh) {
refresh(xblockInfo);
}
}
}); });
}, },
onSave: function() {
var refresh = this.editOptions.refresh;
this.hide();
if (refresh) {
refresh(this.xblockInfo);
}
},
hide: function() { hide: function() {
BaseModal.prototype.hide.call(this); BaseModal.prototype.hide.call(this);
// Notify the runtime that the modal has been hidden
if (this.runtime) {
this.runtime.notify('edit-modal-hidden');
}
// Completely clear the contents of the modal // Completely clear the contents of the modal
this.undelegateEvents(); this.undelegateEvents();
this.$el.html(""); this.$el.html("");
......
...@@ -49,6 +49,10 @@ define(["jquery", "underscore", "gettext", "js/views/feedback_notification", "js ...@@ -49,6 +49,10 @@ define(["jquery", "underscore", "gettext", "js/views/feedback_notification", "js
return this.$('.editor-with-tabs').length > 0; return this.$('.editor-with-tabs').length > 0;
}, },
hasCustomButtons: function() {
return this.$('.editor-with-buttons').length > 0;
},
createMetadataEditor: function() { createMetadataEditor: function() {
var metadataEditor, var metadataEditor,
metadataData, metadataData,
...@@ -88,27 +92,36 @@ define(["jquery", "underscore", "gettext", "js/views/feedback_notification", "js ...@@ -88,27 +92,36 @@ define(["jquery", "underscore", "gettext", "js/views/feedback_notification", "js
var xblockInfo = this.model, var xblockInfo = this.model,
data, data,
saving; saving;
data = this.getXBlockData(); data = this.getXModuleData();
saving = new NotificationView.Mini({ if (data) {
title: gettext('Saving&hellip;') saving = new NotificationView.Mini({
}); title: gettext('Saving&hellip;')
saving.show(); });
return xblockInfo.save(data).done(function() { saving.show();
var success = options.success; return xblockInfo.save(data).done(function() {
saving.hide(); var success = options.success;
if (success) { saving.hide();
success(); if (success) {
} success();
}); }
});
}
}, },
getXBlockData: function() { /**
* Returns the data saved for the xmodule. Note that this *does not* work for XBlocks.
*/
getXModuleData: function() {
var xblock = this.xblock, var xblock = this.xblock,
metadataEditor = this.getMetadataEditor(), metadataEditor = this.getMetadataEditor(),
data; data = null;
data = xblock.save(); if (xblock.save) {
if (metadataEditor) { data = xblock.save();
data.metadata = _.extend(data.metadata || {}, this.getChangedMetadata()); if (metadataEditor) {
data.metadata = _.extend(data.metadata || {}, this.getChangedMetadata());
}
} else {
console.error('Cannot save xblock as it has no save method');
} }
return data; return data;
}, },
......
...@@ -18,7 +18,6 @@ ...@@ -18,7 +18,6 @@
.modal-content { .modal-content {
position: relative; position: relative;
box-shadow: 0 0 3px $shadow-d1;
background-color: $white; background-color: $white;
padding: 5%; padding: 5%;
} }
...@@ -52,7 +51,9 @@ ...@@ -52,7 +51,9 @@
} }
} }
// TODO: need to sync up (alongside general editing mode) with xblocks.scss UI
.modal-chin, .modal-chin,
.xblock-actions,
.modal-actions { .modal-actions {
padding: ($baseline*0.75) 2% ($baseline/2) 2%; padding: ($baseline*0.75) 2% ($baseline/2) 2%;
...@@ -190,6 +191,22 @@ ...@@ -190,6 +191,22 @@
} }
} }
// xblock custom actions
.modal-window .editor-with-buttons {
margin-bottom: ($baseline*3);
// TODO: need to sync up (alongside general editing mode) with xblocks.scss UI
.xblock-actions {
background-color: $gray-l4;
position: absolute;
width: 100%;
bottom: 0;
}
}
// special overrides for video module editor/hidden tab editors // special overrides for video module editor/hidden tab editors
.modal-lg.modal-type-video { .modal-lg.modal-type-video {
......
<%! from django.utils.translation import ugettext as _ %> <%! from django.utils.translation import ugettext as _ %>
<%namespace name='static' file='static_content.html'/> <%namespace name='static' file='static_content.html'/>
<div class="wrapper wrapper-component-editor">
<div class="component-editor">
<div class="component-edit-header">
<span class="component-name"></span>
<ul class="nav-edit-modes">
<li id="editor-mode" class="mode active-mode" aria-controls="editor-tab" role="tab">
<a href="#">${_("Editor")}</a>
</li>
<li id="settings-mode" class="mode active-mode" aria-controls="settings-tab" role="tab">
<a href="#">${_("Settings")}</a>
</li>
</ul>
</div> <!-- Editor Header -->
<div class="component-edit-modes">
<div class="module-editor"/>
</div>
## Native XBlocks render their own save/cancel buttons
% if show_save_cancel:
<div class="row module-actions">
<a href="#" class="save-button action-primary action">${_("Save")}</a>
<a href="#" class="cancel-button action-secondary action">${_("Cancel")}</a>
</div> <!-- Module Actions-->
% endif
</div>
</div>
<div class="wrapper wrapper-component-action-header"> <div class="wrapper wrapper-component-action-header">
<div class="component-header"> <div class="component-header">
${label} ${label}
......
...@@ -130,7 +130,4 @@ main_xblock_info = { ...@@ -130,7 +130,4 @@ main_xblock_info = {
</section> </section>
</div> </div>
</div> </div>
<div class="edit-xblock-modal"></div>
</%block> </%block>
...@@ -178,6 +178,4 @@ ...@@ -178,6 +178,4 @@
<span class="label">${_("close modal")}</span> <span class="label">${_("close modal")}</span>
</a> </a>
</div> </div>
<div class="edit-xblock-modal"></div>
</%block> </%block>
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
role="dialog"> role="dialog">
<div class="modal-window-overlay"></div> <div class="modal-window-overlay"></div>
<div class="modal-window confirm modal-editor modal-<%= size %> modal-type-<%= type %>"> <div class="modal-window confirm modal-editor modal-<%= size %> modal-type-<%= type %>">
<div class="<%= name %>-modal" action="#"> <div class="<%= name %>-modal">
<div class="modal-header"> <div class="modal-header">
<h2 class="title modal-window-title"><%= title %></h2> <h2 class="title modal-window-title"><%= title %></h2>
<ul class="editor-modes action-list action-modes"> <ul class="editor-modes action-list action-modes">
......
<div class="xblock xblock-studio_view" data-runtime-version="1" data-usage-id="i4x:;_;_edX;_mock"
data-init="MockXBlock" data-runtime-class="StudioRuntime" data-block-type="mock" tabindex="0">
<div class="mock-xblock editor-with-buttons">
<h3>Mock XBlock Editor</h3>
<div class="modal-actions">
<h3 class="sr">Actions</h3>
<ul>
<li class="action-item">
<a href="#" class="button action-primary action-save">Save</a>
</li>
<li class="action-item">
<a href="#" class="button action-cancel">Cancel</a>
</li>
</ul>
</div>
</div>
</div>
...@@ -225,5 +225,4 @@ require(["domReady!", "jquery", "js/models/module_info", "coffee/src/views/unit" ...@@ -225,5 +225,4 @@ require(["domReady!", "jquery", "js/models/module_info", "coffee/src/views/unit"
</div> </div>
</div> </div>
</div> </div>
<div class="edit-xblock-modal"></div>
</%block> </%block>
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
runtime = new window[runtime]["v#{version}"] runtime = new window[runtime]["v#{version}"]
initFn = window[initFnName] initFn = window[initFnName]
block = initFn(runtime, element) ? {} block = initFn(runtime, element) ? {}
block.runtime = runtime
else else
elementTag = $('<div>').append($element.clone()).html(); elementTag = $('<div>').append($element.clone()).html();
console.log("Block #{elementTag} is missing data-runtime, data-runtime-version or data-init, and can't be initialized") console.log("Block #{elementTag} is missing data-runtime, data-runtime-version or data-init, and can't be initialized")
......
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