Hide save/cancel buttons for native XBlocks in Studio
Add Studio client-side runtime notifications for saving/cancelling XBlock edits.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -18,10 +18,13 @@
|
||||
<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">
|
||||
|
||||
@@ -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
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user