From 5f09953125026d362cb608a5c75324b25c56a6e5 Mon Sep 17 00:00:00 2001 From: Will Daly Date: Tue, 25 Feb 2014 08:38:05 -0500 Subject: [PATCH] Hide save/cancel buttons for native XBlocks in Studio Add Studio client-side runtime notifications for saving/cancelling XBlock edits. --- cms/djangoapps/contentstore/views/item.py | 4 ++ .../contentstore/views/tests/test_item.py | 31 ++++++++++++++ cms/envs/test.py | 6 +++ .../coffee/src/xblock/cms.runtime.v1.coffee | 41 ++++++++++++++++++- cms/templates/component.html | 3 ++ .../coffee/src/xblock/runtime.v1.coffee | 11 ++++- requirements/edx/base.txt | 2 +- 7 files changed, 95 insertions(+), 3 deletions(-) diff --git a/cms/djangoapps/contentstore/views/item.py b/cms/djangoapps/contentstore/views/item.py index 4945a0e6f2..7f8e282702 100644 --- a/cms/djangoapps/contentstore/views/item.py +++ b/cms/djangoapps/contentstore/views/item.py @@ -208,6 +208,10 @@ def xblock_view_handler(request, package_id, view_name, tag=None, branch=None, v fragment.content = render_to_string('component.html', { 'preview': fragment.content, '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: raise Http404 diff --git a/cms/djangoapps/contentstore/views/tests/test_item.py b/cms/djangoapps/contentstore/views/tests/test_item.py index c366626f76..931a2be012 100644 --- a/cms/djangoapps/contentstore/views/tests/test_item.py +++ b/cms/djangoapps/contentstore/views/tests/test_item.py @@ -626,3 +626,34 @@ class TestComponentHandler(TestCase): self.descriptor.handle = create_response 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 + resp_content = json.loads(resp.content) + resp = self.client.get('/xblock/' + native_loc + '/student_view', HTTP_ACCEPT='application/x-fragment+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) diff --git a/cms/envs/test.py b/cms/envs/test.py index 5edbc89686..37dec4f2ec 100644 --- a/cms/envs/test.py +++ b/cms/envs/test.py @@ -156,6 +156,12 @@ INSTALLED_APPS += ('external_auth', ) # hide ratelimit warnings while running tests filterwarnings('ignore', message='No request passed to the backend, unable to rate-limit') + +################################# XBLOCK ###################################### +from xmodule.x_module import prefer_xmodules +XBLOCK_SELECT_FUNCTION = prefer_xmodules + + ################################# CELERY ###################################### CELERY_ALWAYS_EAGER = True diff --git a/cms/static/coffee/src/xblock/cms.runtime.v1.coffee b/cms/static/coffee/src/xblock/cms.runtime.v1.coffee index b420a6f33c..2849eed222 100644 --- a/cms/static/coffee/src/xblock/cms.runtime.v1.coffee +++ b/cms/static/coffee/src/xblock/cms.runtime.v1.coffee @@ -1,4 +1,7 @@ -define ["jquery", "xblock/runtime.v1", "URI"], ($, XBlock, URI) -> +define [ + "jquery", "xblock/runtime.v1", "URI", "gettext", + "js/utils/modal", "js/views/feedback_notification" +], ($, XBlock, URI, gettext, ModalUtils, NotificationView) -> @PreviewRuntime = {} class PreviewRuntime.v1 extends XBlock.Runtime.v1 @@ -13,6 +16,11 @@ define ["jquery", "xblock/runtime.v1", "URI"], ($, XBlock, URI) -> @StudioRuntime = {} class StudioRuntime.v1 extends XBlock.Runtime.v1 + constructor: () -> + super() + @savingNotification = new NotificationView.Mini + title: gettext('Saving…') + handlerUrl: (element, handlerName, suffix, query, thirdparty) -> uri = URI("/xblock").segment($(element).data('usage-id')) .segment('handler') @@ -20,3 +28,34 @@ define ["jquery", "xblock/runtime.v1", "URI"], ($, XBlock, URI) -> if suffix? then uri.segment(suffix) if query? then uri.search(query) uri.toString() + + # Notify the Studio client-side runtime so it can update + # the UI in a consistent way. Currently, this is used + # for save / cancel when editing an XBlock. + # Although native XBlocks should handle their own persistence, + # Studio still needs to update the UI in a consistent way + # (showing the "Saving..." notification, closing the modal editing dialog, etc.) + notify: (name, data) -> + if name == 'save' + if 'state' of data + + # Starting to save, so show the "Saving..." notification + if data.state == 'start' + @_hide_editor() + @savingNotification.show() + + # Finished saving, so hide the "Saving..." notification + else if data.state == 'end' + $('.component.editing').removeClass('editing') + @savingNotification.hide() + + else if name == 'cancel' + @_hide_editor() + + _hide_editor: () -> + # This will close all open component editors, which works + # if we assume that <= 1 are open at a time. + el = $('.component.editing') + el.removeClass('editing') + el.find('.component-editor').slideUp(150) + ModalUtils.hideModalCover() diff --git a/cms/templates/component.html b/cms/templates/component.html index c979945e8b..7d06be01ca 100644 --- a/cms/templates/component.html +++ b/cms/templates/component.html @@ -18,10 +18,13 @@
+ ## Native XBlocks render their own save/cancel buttons + % if show_save_cancel: + % endif
diff --git a/common/static/coffee/src/xblock/runtime.v1.coffee b/common/static/coffee/src/xblock/runtime.v1.coffee index e7955306be..fbe70dc878 100644 --- a/common/static/coffee/src/xblock/runtime.v1.coffee +++ b/common/static/coffee/src/xblock/runtime.v1.coffee @@ -2,4 +2,13 @@ class XBlock.Runtime.v1 children: (block) => $(block).prop('xblock_children') childMap: (block, childName) => for child in @children(block) - return child if child.name == childName \ No newline at end of file + return child if child.name == childName + + # Notify the client-side runtime that an event has occurred. + # This allows the runtime to update the UI in a consistent way + # for different XBlocks. + # `name` is an arbitrary string (for example, "save") + # `data` is an object (for example, {state: 'starting'}) + # The default implementation is a no-op. + # WARNING: This is an interim solution and not officially supported! + notify: (name, data) -> undefined diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index b6c823b880..ec3914f563 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -106,7 +106,7 @@ django-debug-toolbar-mongo # Used for testing chrono==1.0.2 coverage==3.7 -ddt==0.6.0 +ddt==0.7.0 django-crum==0.5 django_nose==1.1 factory_boy==2.2.1