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