Add a request-token to identify which xblock html was rendered as part of the current request

[STUD-2903]
This commit is contained in:
Calen Pennington
2014-08-14 11:41:21 -04:00
committed by cahrens
parent 5f14959231
commit 50082387eb
13 changed files with 201 additions and 58 deletions

View File

@@ -6,6 +6,7 @@ import datetime
import json
import logging
import static_replace
import uuid
from django.conf import settings
from django.utils.timezone import UTC
@@ -32,7 +33,19 @@ def wrap_fragment(fragment, new_content):
return wrapper_frag
def wrap_xblock(runtime_class, block, view, frag, context, usage_id_serializer, display_name_only=False, extra_data=None): # pylint: disable=unused-argument
def request_token(request):
"""
Return a unique token for the supplied request.
This token will be the same for all calls to `request_token`
made on the same request object.
"""
if not hasattr(request, '_xblock_token'):
request._xblock_token = uuid.uuid1().get_hex()
return request._xblock_token
def wrap_xblock(runtime_class, block, view, frag, context, usage_id_serializer, request_token, display_name_only=False, extra_data=None): # pylint: disable=unused-argument
"""
Wraps the results of rendering an XBlock view in a standard <section> with identifying
data so that the appropriate javascript module can be loaded onto it.
@@ -44,6 +57,8 @@ def wrap_xblock(runtime_class, block, view, frag, context, usage_id_serializer,
:param context: The context passed to the view being rendered
:param usage_id_serializer: A function to serialize the block's usage_id for use by the
front-end Javascript Runtime.
:param request_token: An identifier that is unique per-request, so that only xblocks
rendered as part of this request will have their javascript initialized.
:param display_name_only: If true, don't render the fragment content at all.
Instead, just render the `display_name` of `block`
:param extra_data: A dictionary with extra data values to be set on the wrapper
@@ -56,7 +71,7 @@ def wrap_xblock(runtime_class, block, view, frag, context, usage_id_serializer,
data = {}
data.update(extra_data)
css_classes = ['xblock', 'xblock-' + view]
css_classes = ['xblock', 'xblock-{}'.format(view)]
if isinstance(block, (XModule, XModuleDescriptor)):
if view in PREVIEW_VIEWS:
@@ -76,6 +91,7 @@ def wrap_xblock(runtime_class, block, view, frag, context, usage_id_serializer,
data['runtime-version'] = frag.js_init_version
data['block-type'] = block.scope_ids.block_type
data['usage-id'] = usage_id_serializer(block.scope_ids.usage_id)
data['request-token'] = request_token
template_context = {
'content': block.display_name if display_name_only else frag.content,

View File

@@ -32,4 +32,6 @@ class @Conditional
else
$(element).show()
XBlock.initializeBlocks @el
# The children are rendered with a new request, so they have a different request-token.
# Use that token instead of @requestToken by simply not passing a token into initializeBlocks.
XBlock.initializeBlocks(@el)

View File

@@ -1,5 +1,6 @@
class @Sequence
constructor: (element) ->
@requestToken = $(element).data('request-token')
@el = $(element).find('.sequence')
@contents = @$('.seq_contents')
@content_container = @$('#seq_content')
@@ -102,7 +103,7 @@ class @Sequence
current_tab = @contents.eq(new_position - 1)
@content_container.html(current_tab.text()).attr("aria-labelledby", current_tab.attr("aria-labelledby"))
XBlock.initializeBlocks(@content_container)
XBlock.initializeBlocks(@content_container, @requestToken)
window.update_schematics() # For embedded circuit simulator exercises in 6.002x

View File

@@ -18,7 +18,7 @@ function ABTestSelector(runtime, elem) {
var child_group_id = $(this).data('group-id').toString();
if(child_group_id === group_id) {
_this.content_container.html($(this).text());
XBlock.initializeBlocks(_this.content_container);
XBlock.initializeBlocks(_this.content_container, $(elem).data('request-token'));
}
});
}

View File

@@ -2,9 +2,21 @@ describe "XBlock", ->
beforeEach ->
setFixtures """
<div>
<div class='xblock' id='vA' data-runtime-version="A" data-runtime-class="TestRuntime" data-init="initFnA" data-name="a-name"/>
<div class='xblock'
id='vA'
data-runtime-version="A"
data-runtime-class="TestRuntime"
data-init="initFnA"
data-name="a-name"
/>
<div>
<div class='xblock' id='vZ' data-runtime-version="Z" data-runtime-class="TestRuntime" data-init="initFnZ"/>
<div class='xblock'
id='vZ'
data-runtime-version="Z"
data-runtime-class="TestRuntime"
data-init="initFnZ"
data-request-token="req-token-z"
/>
</div>
<div class='xblock' id='missing-version' data-init='initFnA' data-name='no-version'/>
<div class='xblock' id='missing-init' data-runtime-version="A" data-name='no-init'/>
@@ -25,8 +37,11 @@ describe "XBlock", ->
@fakeChildren = ['list', 'of', 'children']
spyOn(XBlock, 'initializeBlocks').andReturn(@fakeChildren)
@vABlock = XBlock.initializeBlock($('#vA')[0])
@vZBlock = XBlock.initializeBlock($('#vZ')[0])
@vANode = $('#vA')[0]
@vZNode = $('#vZ')[0]
@vABlock = XBlock.initializeBlock(@vANode, 'req-token-a')
@vZBlock = XBlock.initializeBlock(@vZNode)
@missingVersionBlock = XBlock.initializeBlock($('#missing-version')[0])
@missingInitBlock = XBlock.initializeBlock($('#missing-init')[0])
@@ -35,8 +50,8 @@ describe "XBlock", ->
expect(TestRuntime.vZ).toHaveBeenCalledWith()
it "loads the right init function", ->
expect(window.initFnA).toHaveBeenCalledWith(@runtimeA, $('#vA')[0])
expect(window.initFnZ).toHaveBeenCalledWith(@runtimeZ, $('#vZ')[0])
expect(window.initFnA).toHaveBeenCalledWith(@runtimeA, @vANode)
expect(window.initFnZ).toHaveBeenCalledWith(@runtimeZ, @vZNode)
it "loads when missing versions", ->
expect(@missingVersionBlock.element).toBe($('#missing-version'))
@@ -53,15 +68,29 @@ describe "XBlock", ->
expect(@vZBlock.name).toBeUndefined()
it "attaches the element to the block", ->
expect(@vABlock.element).toBe($('#vA')[0])
expect(@vZBlock.element).toBe($('#vZ')[0])
expect(@vABlock.element).toBe(@vANode)
expect(@vZBlock.element).toBe(@vZNode)
expect(@missingVersionBlock.element).toBe($('#missing-version')[0])
expect(@missingInitBlock.element).toBe($('#missing-init')[0])
it "passes through the request token", ->
expect(XBlock.initializeBlocks).toHaveBeenCalledWith($(@vANode), 'req-token-a')
expect(XBlock.initializeBlocks).toHaveBeenCalledWith($(@vZNode), 'req-token-z')
describe "initializeBlocks", ->
it "initializes children", ->
beforeEach ->
spyOn(XBlock, 'initializeBlock')
@vANode = $('#vA')[0]
@vZNode = $('#vZ')[0]
it "initializes children", ->
XBlock.initializeBlocks($('#jasmine-fixtures'))
expect(XBlock.initializeBlock).toHaveBeenCalledWith($('#vA')[0])
expect(XBlock.initializeBlock).toHaveBeenCalledWith($('#vZ')[0])
expect(XBlock.initializeBlock).toHaveBeenCalledWith(@vANode, undefined)
expect(XBlock.initializeBlock).toHaveBeenCalledWith(@vZNode, undefined)
it "only initializes matching request tokens", ->
XBlock.initializeBlocks($('#jasmine-fixtures'), 'req-token-z')
expect(XBlock.initializeBlock).not.toHaveBeenCalledWith(@vANode, jasmine.any(Object))
expect(XBlock.initializeBlock).toHaveBeenCalledWith(@vZNode, 'req-token-z')

View File

@@ -1,9 +1,16 @@
@XBlock =
Runtime: {}
initializeBlock: (element) ->
###
Initialize the javascript for a single xblock element, and for all of it's
xblock children that match requestToken. If requestToken is omitted, use the
data-request-token attribute from element, or use the request-tokens specified on
the children themselves.
###
initializeBlock: (element, requestToken) ->
$element = $(element)
children = @initializeBlocks($element)
requestToken = requestToken or $element.data('request-token')
children = @initializeBlocks($element, requestToken)
runtime = $element.data("runtime-class")
version = $element.data("runtime-version")
initFnName = $element.data("init")
@@ -26,7 +33,17 @@
$element.addClass("xblock-initialized")
block
initializeBlocks: (element) ->
$(element).immediateDescendents(".xblock").map((idx, elem) =>
@initializeBlock elem
###
Initialize all XBlocks inside element that were rendered with requestToken.
If requestToken is omitted, and element has a 'data-request-token' attribute, use that.
If neither is available, then use the request tokens of the immediateDescendent xblocks.
###
initializeBlocks: (element, requestToken) ->
requestToken = requestToken or $(element).data('request-token')
if requestToken
selector = ".xblock[data-request-token='#{requestToken}']"
else
selector = ".xblock"
$(element).immediateDescendents(selector).map((idx, elem) =>
@initializeBlock(elem, requestToken)
).toArray()