Commit fbd2e3a0 by Will Daly

Merge pull request #2724 from edx/will/studio-xblock-save

Native XBlock Studio Save UI
parents 8f7497cf 5f099531
......@@ -210,6 +210,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
......
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
\ 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
......@@ -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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment