Fix duplicate save and cancel buttons in Studio

STUD-1531

Also add support for refreshing the modal on custom save
This commit is contained in:
Andy Armstrong
2014-04-14 11:40:02 -04:00
parent f1eefc1117
commit 1b2d1858fa
21 changed files with 277 additions and 192 deletions

View File

@@ -5,9 +5,9 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers
describe("EditXBlockModal", function() {
var model, modal, showModal;
showModal = function(requests, mockHtml) {
showModal = function(requests, mockHtml, options) {
var xblockElement = $('.xblock');
return edit_helpers.showEditModal(requests, xblockElement, model, mockHtml);
return edit_helpers.showEditModal(requests, xblockElement, model, mockHtml, options);
};
beforeEach(function () {
@@ -45,6 +45,13 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers
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() {
var requests = create_sinon.requests(this);
modal = showModal(requests, mockXBlockEditorHtml);
@@ -56,6 +63,43 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers
modal = showModal(requests, mockXBlockEditorHtml);
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() {
@@ -64,12 +108,11 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers
mockXModuleEditorHtml = readFixtures('mock/mock-xmodule-editor.underscore');
beforeEach(function() {
// Mock the VerticalDescriptor so that the module can be rendered
window.VerticalDescriptor = XModule.Descriptor;
edit_helpers.installMockXModule();
});
afterEach(function () {
window.VerticalDescriptor = null;
edit_helpers.uninstallMockXModule();
});
it('can render itself', function() {
@@ -140,12 +183,11 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers
mockXModuleEditorHtml = readFixtures('mock/mock-xmodule-settings-only-editor.underscore');
beforeEach(function() {
// Mock the VerticalDescriptor so that the module can be rendered
window.VerticalDescriptor = XModule.Descriptor;
edit_helpers.installMockXModule();
});
afterEach(function () {
window.VerticalDescriptor = null;
edit_helpers.uninstallMockXModule();
});
it('can render itself', function() {

View File

@@ -107,6 +107,29 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/edit_helpers"
});
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() {
var editButtons, modal, mockUpdatedXBlockHtml;
@@ -117,7 +140,7 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/edit_helpers"
expect(editButtons.length).toBe(6);
editButtons.first().click();
create_sinon.respondWithJson(requests, {
html: mockXBlockEditorHtml,
html: mockXModuleEditor,
resources: []
});

View File

@@ -49,20 +49,6 @@ define([ "jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helper
expect(editor.$el.select('.xblock-header')).toBeTruthy();
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() {

View File

@@ -1,9 +1,9 @@
/**
* 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",
"xmodule", "coffee/src/main", "xblock/cms.runtime.v1"],
function($, create_sinon, modal_helpers, EditXBlockModal) {
define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers/modal_helpers",
"js/views/modals/edit_xblock", "xmodule", "coffee/src/main", "xblock/cms.runtime.v1"],
function($, _, create_sinon, modal_helpers, EditXBlockModal) {
var editorTemplate = readFixtures('metadata-editor.underscore'),
numberEntryTemplate = readFixtures('metadata-number-entry.underscore'),
@@ -12,19 +12,15 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/modal_helpers
editorModeButtonTemplate = readFixtures('editor-mode-button.underscore'),
installMockXBlock,
uninstallMockXBlock,
hasSavedMockXBlock,
installMockXModule,
uninstallMockXModule,
hasSavedMockXModule,
installEditTemplates,
showEditModal;
installMockXBlock = function(mockResult) {
window.MockXBlock = function(runtime, element) {
return {
save: function() {
return mockResult;
}
runtime: runtime
};
};
};
@@ -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));
};
showEditModal = function(requests, xblockElement, model, mockHtml) {
showEditModal = function(requests, xblockElement, model, mockHtml, options) {
var modal = new EditXBlockModal({});
modal.edit(xblockElement, model);
modal.edit(xblockElement, model, options);
create_sinon.respondWithJson(requests, {
html: mockHtml,
"resources": []

View File

@@ -47,7 +47,7 @@ define(["jquery"],
cancelModal = function(modal) {
var modalElement, cancelButton;
modalElement = getModalElement(modal);
cancelButton = modalElement.find('.action-cancel');
cancelButton = modalElement.find('.action-cancel:visible');
expect(cancelButton.length).toBe(1);
cancelButton.click();
};

View File

@@ -71,8 +71,10 @@ define(["jquery", "underscore", "gettext", "js/views/baseview"],
},
cancel: function(event) {
event.preventDefault();
event.stopPropagation(); // Make sure parent modals don't see the click
if (event) {
event.preventDefault();
event.stopPropagation(); // Make sure parent modals don't see the click
}
this.hide();
},
@@ -98,7 +100,21 @@ define(["jquery", "underscore", "gettext", "js/views/baseview"],
name: name,
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() {

View File

@@ -37,6 +37,10 @@ define(["jquery", "underscore", "gettext", "js/views/modals/base_modal",
this.editOptions = options;
this.render();
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
// that depend upon being visible when they initialize, e.g. the problem xmodule.
this.displayXBlock();
@@ -60,7 +64,17 @@ define(["jquery", "underscore", "gettext", "js/views/modals/base_modal",
onDisplayXBlock: function() {
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()) {
// Hide the modal's header as the custom editor provides its own
this.$('.modal-header').hide();
@@ -74,9 +88,28 @@ define(["jquery", "underscore", "gettext", "js/views/modals/base_modal",
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();
},
disableSave: function() {
var saveButton = this.getActionButton('save'),
cancelButton = this.getActionButton('cancel');
saveButton.hide();
cancelButton.text(gettext('OK'));
cancelButton.addClass('action-primary');
},
getTitle: function() {
var displayName = this.xblockElement.find('.xblock-header .header-details').text().trim();
// If not found, try the old unit page style rendering
@@ -117,23 +150,28 @@ define(["jquery", "underscore", "gettext", "js/views/modals/base_modal",
},
save: function(event) {
var self = this,
xblockInfo = this.xblockInfo,
refresh = self.editOptions.refresh;
event.preventDefault();
this.editorView.save({
success: function() {
self.hide();
if (refresh) {
refresh(xblockInfo);
}
}
success: _.bind(this.onSave, this)
});
},
onSave: function() {
var refresh = this.editOptions.refresh;
this.hide();
if (refresh) {
refresh(this.xblockInfo);
}
},
hide: function() {
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
this.undelegateEvents();
this.$el.html("");

View File

@@ -49,6 +49,10 @@ define(["jquery", "underscore", "gettext", "js/views/feedback_notification", "js
return this.$('.editor-with-tabs').length > 0;
},
hasCustomButtons: function() {
return this.$('.editor-with-buttons').length > 0;
},
createMetadataEditor: function() {
var metadataEditor,
metadataData,
@@ -88,27 +92,36 @@ define(["jquery", "underscore", "gettext", "js/views/feedback_notification", "js
var xblockInfo = this.model,
data,
saving;
data = this.getXBlockData();
saving = new NotificationView.Mini({
title: gettext('Saving&hellip;')
});
saving.show();
return xblockInfo.save(data).done(function() {
var success = options.success;
saving.hide();
if (success) {
success();
}
});
data = this.getXModuleData();
if (data) {
saving = new NotificationView.Mini({
title: gettext('Saving&hellip;')
});
saving.show();
return xblockInfo.save(data).done(function() {
var success = options.success;
saving.hide();
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,
metadataEditor = this.getMetadataEditor(),
data;
data = xblock.save();
if (metadataEditor) {
data.metadata = _.extend(data.metadata || {}, this.getChangedMetadata());
data = null;
if (xblock.save) {
data = xblock.save();
if (metadataEditor) {
data.metadata = _.extend(data.metadata || {}, this.getChangedMetadata());
}
} else {
console.error('Cannot save xblock as it has no save method');
}
return data;
},