diff --git a/cms/static/js/views/xblock_editor.js b/cms/static/js/views/xblock_editor.js
index 40ab22d022fc..52d08dc76fb4 100644
--- a/cms/static/js/views/xblock_editor.js
+++ b/cms/static/js/views/xblock_editor.js
@@ -78,7 +78,7 @@ function($, _, gettext, BaseView, XBlockView, MetadataView, MetadataCollection)
el: metadataEditor,
collection: new MetadataCollection(models)
});
- if (xblock.setMetadataEditor) {
+ if (xblock && xblock.setMetadataEditor) {
xblock.setMetadataEditor(metadataView);
}
}
diff --git a/common/templates/xblock_v2/xblock_iframe.html b/common/templates/xblock_v2/xblock_iframe.html
index 57ff4684ddbd..add3c46bce55 100644
--- a/common/templates/xblock_v2/xblock_iframe.html
+++ b/common/templates/xblock_v2/xblock_iframe.html
@@ -35,23 +35,43 @@
@@ -269,6 +289,82 @@
// const passElement = isStudioView && (window as any).$ ? (window as any).$(element) : element;
const blockJS = new InitFunction(runtime, element, data) || {};
blockJS.element = element;
+
+ if (['MetadataOnlyEditingDescriptor', 'SequenceDescriptor'].includes(data['xmodule-type'])) {
+ // The xmodule type `MetadataOnlyEditingDescriptor` and `SequenceDescriptor` renders a `
` with
+ // the block metadata in the `data-metadata` attribute. But is necessary
+ // to call `XBlockEditorView.xblockReady()` to run the scripts to build the
+ // editor using the metadata.
+ require(['{{ cms_root_url }}/static/studio/js/views/xblock_editor.js'], function(XBlockEditorView) {
+ var editorView = new XBlockEditorView({
+ el: element,
+ xblock: blockJS,
+ });
+ // To render block using metadata
+ editorView.xblockReady(blockJS);
+
+ // Adding cancel and save buttons
+ var xblockActions = `
+
+ `;
+ element.innerHTML += xblockActions;
+
+ const views = editorView.getMetadataEditor().views;
+ Object.values(views).forEach(view => {
+ const uniqueId = view.uniqueId;
+ const input = element.querySelector(`#${uniqueId}`);
+ if (input) {
+ input.addEventListener("input", function(event) {
+ view.model.setValue(event.target.value);
+ });
+ }
+ });
+
+ // Adding cancel functionality
+ $('.cancel-button', element).bind('click', function() {
+ runtime.notify('cancel', {});
+ event.preventDefault();
+ });
+
+ // Adding save functionality
+ $('.save-button', element).bind('click', function() {
+ //event.preventDefault();
+ var error_message_div = $('.xblock-editor-error-message', element);
+ const modifiedData = editorView.getChangedMetadata();
+
+ error_message_div.html();
+ error_message_div.css('display', 'none');
+
+ var handlerUrl = runtime.handlerUrl(element, 'studio_submit');
+
+ runtime.notify('save', {state: 'start', message: gettext("Saving")});
+
+ $.post(handlerUrl, JSON.stringify(modifiedData)).done(function(response) {
+ if (response.result === 'success') {
+ runtime.notify('save', {state: 'end'})
+ window.location.reload(false);
+ } else {
+ runtime.notify('error', {
+ 'title': 'Error saving changes',
+ 'message': response.message,
+ });
+ error_message_div.html('Error: '+response.message);
+ error_message_div.css('display', 'block');
+ }
+ });
+ });
+ });
+ }
+
callback(blockJS);
} else {
const blockJS = { element };
diff --git a/xmodule/tests/test_word_cloud.py b/xmodule/tests/test_word_cloud.py
index 6c928465262b..9fbd02a612db 100644
--- a/xmodule/tests/test_word_cloud.py
+++ b/xmodule/tests/test_word_cloud.py
@@ -6,6 +6,7 @@
from django.test import TestCase
from fs.memoryfs import MemoryFS
from lxml import etree
+from webob import Request
from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator
from webob.multidict import MultiDict
from xblock.field_data import DictFieldData
@@ -115,3 +116,29 @@ def test_indexibility(self):
{'content_type': 'Word Cloud',
'content': {'display_name': 'Word Cloud Block',
'instructions': 'Enter some random words that comes to your mind'}}
+
+ def test_studio_submit_handler(self):
+ """
+ Test studio_submint handler
+ """
+ TEST_SUBMIT_DATA = {
+ 'display_name': "New Word Cloud",
+ 'instructions': "This is a Test",
+ 'num_inputs': 5,
+ 'num_top_words': 10,
+ 'display_student_percents': 'False',
+ }
+ module_system = get_test_system()
+ block = WordCloudBlock(module_system, DictFieldData(self.raw_field_data), Mock())
+ body = json.dumps(TEST_SUBMIT_DATA)
+ request = Request.blank('/')
+ request.method = 'POST'
+ request.body = body.encode('utf-8')
+ res = block.handle('studio_submit', request)
+ assert json.loads(res.body.decode('utf8')) == {'result': 'success'}
+
+ assert block.display_name == TEST_SUBMIT_DATA['display_name']
+ assert block.instructions == TEST_SUBMIT_DATA['instructions']
+ assert block.num_inputs == TEST_SUBMIT_DATA['num_inputs']
+ assert block.num_top_words == TEST_SUBMIT_DATA['num_top_words']
+ assert block.display_student_percents == (TEST_SUBMIT_DATA['display_student_percents'] == "True")
diff --git a/xmodule/word_cloud_block.py b/xmodule/word_cloud_block.py
index 37e82400df78..f83cda8c0c83 100644
--- a/xmodule/word_cloud_block.py
+++ b/xmodule/word_cloud_block.py
@@ -315,6 +315,26 @@ def index_dictionary(self):
return xblock_body
+ @XBlock.json_handler
+ def studio_submit(self, submissions, suffix=''): # pylint: disable=unused-argument
+ """
+ Change the settings for this XBlock given by the Studio user
+ """
+ if 'display_name' in submissions:
+ self.display_name = submissions['display_name']
+ if 'instructions' in submissions:
+ self.instructions = submissions['instructions']
+ if 'num_inputs' in submissions:
+ self.num_inputs = submissions['num_inputs']
+ if 'num_top_words' in submissions:
+ self.num_top_words = submissions['num_top_words']
+ if 'display_student_percents' in submissions:
+ self.display_student_percents = submissions['display_student_percents'] == 'True'
+
+ return {
+ 'result': 'success',
+ }
+
WordCloudBlock = (
_ExtractedWordCloudBlock if settings.USE_EXTRACTED_WORD_CLOUD_BLOCK