Commit ce6fbae6 by Calen Pennington

Add an XBlock javascript runtime, and use it to run XModules

[LMS-57]
parent 637b6619
......@@ -107,7 +107,7 @@ def add_a_multi_step_component(step, is_advanced, category):
def see_a_multi_step_component(step, category):
# Wait for all components to finish rendering
selector = 'li.component section.xmodule_display'
selector = 'li.component section.xblock-student_view'
world.wait_for(lambda _: len(world.css_find(selector)) == len(step.hashes))
for idx, step_hash in enumerate(step.hashes):
......
......@@ -8,7 +8,7 @@ from django.core.urlresolvers import reverse
from django.contrib.auth.decorators import login_required
from mitxmako.shortcuts import render_to_response, render_to_string
from xmodule_modifiers import replace_static_urls, wrap_xmodule
from xmodule_modifiers import replace_static_urls, wrap_xblock
from xmodule.error_module import ErrorDescriptor
from xmodule.errortracker import exc_info_to_str
from xmodule.exceptions import NotFoundError, ProcessingError
......@@ -77,7 +77,7 @@ def preview_component(request, location):
component = modulestore().get_item(location)
# Wrap the generated fragment in the xmodule_editor div so that the javascript
# can bind to it correctly
component.runtime.wrappers.append(partial(wrap_xmodule, 'xmodule_edit.html'))
component.runtime.wrappers.append(wrap_xblock)
try:
content = component.render('studio_view').content
......@@ -105,11 +105,6 @@ def preview_module_system(request, preview_id, descriptor):
course_id = get_course_for_item(descriptor.location).location.course_id
if descriptor.location.category == 'static_tab':
wrapper_template = 'xmodule_tab_display.html'
else:
wrapper_template = 'xmodule_display.html'
return ModuleSystem(
static_url=settings.STATIC_URL,
ajax_url=reverse('preview_dispatch', args=[preview_id, descriptor.location.url(), '']).rstrip('/'),
......@@ -129,7 +124,7 @@ def preview_module_system(request, preview_id, descriptor):
# Set up functions to modify the fragment produced by student_view
wrappers=(
# This wrapper wraps the module in the template specified above
partial(wrap_xmodule, wrapper_template),
partial(wrap_xblock, display_name_only=descriptor.location.category == 'static_tab'),
# This wrapper replaces urls in the output that start with /static
# with the correct course-specific url for the static content
......
......@@ -16,6 +16,7 @@ requirejs.config({
"jquery.fileupload": "xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.fileupload",
"jquery.iframe-transport": "xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.iframe-transport",
"jquery.inputnumber": "xmodule_js/common_static/js/vendor/html5-input-polyfills/number-polyfill",
"jquery.immediateDescendents": "xmodule_js/common_static/coffee/src/jquery.immediateDescendents",
"datepair": "xmodule_js/common_static/js/vendor/timepicker/datepair",
"date": "xmodule_js/common_static/js/vendor/date",
"underscore": "xmodule_js/common_static/js/vendor/underscore-min",
......@@ -27,6 +28,7 @@ requirejs.config({
"jquery.tinymce": "xmodule_js/common_static/js/vendor/tiny_mce/jquery.tinymce",
"mathjax": "https://edx-static.s3.amazonaws.com/mathjax-MathJax-727332c/MathJax.js?config=TeX-MML-AM_HTMLorMML-full",
"xmodule": "xmodule_js/src/xmodule",
"xblock": "xmodule_js/common_static/coffee/src/xblock",
"utility": "xmodule_js/common_static/js/src/utility",
"sinon": "xmodule_js/common_static/js/vendor/sinon-1.7.1",
"squire": "xmodule_js/common_static/js/vendor/Squire",
......@@ -124,6 +126,14 @@ requirejs.config({
deps: ["jasmine"],
exports: "AsyncSpec"
},
"xblock/core": {
exports: "XBlock",
deps: ["jquery", "jquery.immediateDescendents"]
},
"xblock/runtime.v1": {
exports: "XBlock",
deps: ["xblock/core"]
},
"coffee/src/main": {
deps: ["coffee/src/ajax_prefix"]
......
......@@ -16,6 +16,7 @@ requirejs.config({
"jquery.fileupload": "xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.fileupload",
"jquery.iframe-transport": "xmodule_js/common_static/js/vendor/jQuery-File-Upload/js/jquery.iframe-transport",
"jquery.inputnumber": "xmodule_js/common_static/js/vendor/html5-input-polyfills/number-polyfill",
"jquery.immediateDescendents": "xmodule_js/common_static/coffee/src/jquery.immediateDescendents",
"datepair": "xmodule_js/common_static/js/vendor/timepicker/datepair",
"date": "xmodule_js/common_static/js/vendor/date",
"underscore": "xmodule_js/common_static/js/vendor/underscore-min",
......@@ -27,6 +28,7 @@ requirejs.config({
"jquery.tinymce": "xmodule_js/common_static/js/vendor/tiny_mce/jquery.tinymce",
"mathjax": "https://edx-static.s3.amazonaws.com/mathjax-MathJax-727332c/MathJax.js?config=TeX-MML-AM_HTMLorMML-full",
"xmodule": "xmodule_js/src/xmodule",
"xblock": "xmodule_js/common_static/coffee/src/xblock",
"utility": "xmodule_js/common_static/js/src/utility",
"sinon": "xmodule_js/common_static/js/vendor/sinon-1.7.1",
"squire": "xmodule_js/common_static/js/vendor/Squire",
......@@ -122,6 +124,14 @@ requirejs.config({
deps: ["jasmine"],
exports: "AsyncSpec"
},
"xblock/core": {
exports: "XBlock",
deps: ["jquery", "jquery.immediateDescendents"]
},
"xblock/runtime.v1": {
exports: "XBlock",
deps: ["xblock/core"]
},
"coffee/src/main": {
deps: ["coffee/src/ajax_prefix"]
......
......@@ -19,7 +19,7 @@ define ["coffee/src/views/module_edit", "xmodule"], (ModuleEdit) ->
<a href="#" class="delete-button"><span class="delete-icon white"></span>Delete</a>
</div>
<span class="drag-handle"></span>
<section class="xmodule_display xmodule_stub" data-type="StubModule">
<section class="xblock xblock-student_view xmodule_display xmodule_stub" data-type="StubModule">
<div id="stub-module-content"/>
</section>
</li>
......@@ -66,10 +66,9 @@ define ["coffee/src/views/module_edit", "xmodule"], (ModuleEdit) ->
describe "loadDisplay", ->
beforeEach ->
spyOn(XModule, 'loadModule')
spyOn(XBlock, 'initializeBlock')
@moduleEdit.loadDisplay()
it "loads the .xmodule-display inside the module editor", ->
expect(XModule.loadModule).toHaveBeenCalled()
expect(XModule.loadModule.mostRecentCall.args[0]).toBe($('.xmodule_display'))
expect(XBlock.initializeBlock).toHaveBeenCalled()
expect(XBlock.initializeBlock.mostRecentCall.args[0]).toBe($('.xblock-student_view'))
define ["backbone", "jquery", "underscore", "gettext", "xmodule",
define ["backbone", "jquery", "underscore", "gettext", "xblock/runtime.v1",
"js/views/feedback_notification", "js/views/metadata", "js/collections/metadata"
"jquery.inputnumber"],
(Backbone, $, _, gettext, XModule, NotificationView, MetadataView, MetadataCollection) ->
"jquery.inputnumber", "xmodule"],
(Backbone, $, _, gettext, XBlock, NotificationView, MetadataView, MetadataCollection) ->
class ModuleEdit extends Backbone.View
tagName: 'li'
className: 'component'
......@@ -21,11 +21,11 @@ define ["backbone", "jquery", "underscore", "gettext", "xmodule",
$component_editor: => @$el.find('.component-editor')
loadDisplay: ->
XModule.loadModule(@$el.find('.xmodule_display'))
XBlock.initializeBlock(@$el.find('.xblock-student_view'))
loadEdit: ->
if not @module
@module = XModule.loadModule(@$el.find('.xmodule_edit'))
@module = XBlock.initializeBlock(@$el.find('.xblock-studio_view'))
# At this point, metadata-edit.html will be loaded, and the metadata (as JSON) is available.
metadataEditor = @$el.find('.metadata_edit')
metadataData = metadataEditor.data('metadata')
......
......@@ -54,6 +54,8 @@ lib_paths:
- xmodule_js/common_static/js/vendor/date.js
- xmodule_js/common_static/js/vendor/domReady.js
- xmodule_js/common_static/js/vendor/jquery.smooth-scroll.min.js
- xmodule_js/common_static/coffee/src/jquery.immediateDescendents.js
- xmodule_js/common_static/coffee/src/xblock
# Paths to source JavaScript files
src_paths:
......
......@@ -49,6 +49,7 @@ lib_paths:
- xmodule_js/common_static/js/vendor/jasmine.async.js
- xmodule_js/common_static/js/vendor/CodeMirror/codemirror.js
- xmodule_js/src/xmodule.js
- xmodule_js/common_static/coffee/src/jquery.immediateDescendents.js
- xmodule_js/common_static/js/test/i18n.js
# Paths to source JavaScript files
......
......@@ -54,6 +54,7 @@ var require = {
"jquery.fileupload": "js/vendor/jQuery-File-Upload/js/jquery.fileupload",
"jquery.iframe-transport": "js/vendor/jQuery-File-Upload/js/jquery.iframe-transport",
"jquery.inputnumber": "js/vendor/html5-input-polyfills/number-polyfill",
"jquery.immediateDescendents": "coffee/src/jquery.immediateDescendents",
"datepair": "js/vendor/timepicker/datepair",
"date": "js/vendor/date",
"tzAbbr": "js/vendor/tzAbbr",
......@@ -66,6 +67,7 @@ var require = {
"jquery.tinymce": "js/vendor/tiny_mce/jquery.tinymce",
"mathjax": "https://edx-static.s3.amazonaws.com/mathjax-MathJax-727332c/MathJax.js?config=TeX-MML-AM_HTMLorMML-full",
"xmodule": "/xmodule/xmodule",
"xblock": "coffee/src/xblock",
"utility": "js/src/utility",
"draggabilly": "js/vendor/draggabilly.pkgd"
},
......@@ -150,6 +152,14 @@ var require = {
"mathjax": {
exports: "MathJax"
},
"xblock/core": {
exports: "XBlock",
deps: ["jquery", "jquery.immediateDescendents"]
},
"xblock/runtime.v1": {
exports: "XBlock",
deps: ["xblock/core"]
},
"coffee/src/main": {
deps: ["coffee/src/ajax_prefix"]
......
<section class="xmodule_display xmodule_${class_}" data-type="${module_name}">
${display_name}
</section>
......@@ -70,6 +70,7 @@ def wait_for_js_variable_truthy(variable):
def wait_for_xmodule():
"Wait until the XModule Javascript has loaded on the page."
world.wait_for_js_variable_truthy("XModule")
world.wait_for_js_variable_truthy("XBlock")
@world.absorb
......
......@@ -14,6 +14,7 @@ from xblock.fragment import Fragment
from xmodule.seq_module import SequenceModule
from xmodule.vertical_module import VerticalModule
from xmodule.x_module import shim_xmodule_js, XModuleDescriptor, XModule
log = logging.getLogger(__name__)
......@@ -28,32 +29,50 @@ def wrap_fragment(fragment, new_content):
return wrapper_frag
def wrap_xmodule(template, block, view, frag, context): # pylint: disable=unused-argument
def wrap_xblock(block, view, frag, context, display_name_only=False): # pylint: disable=unused-argument
"""
Wraps the results of get_html in a standard <section> with identifying
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.
get_html: An XModule.get_html method or an XModuleDescriptor.get_html method
module: An XModule
template: A template that takes the variables:
content: the results of get_html,
display_name: the display name of the xmodule, if available (None otherwise)
class_: the module class name
module_name: the js_module_name of the module
:param block: An XBlock (that may be an XModule or XModuleDescriptor)
:param view: The name of the view that rendered the fragment being wrapped
:param frag: The :class:`Fragment` to be wrapped
:param context: The context passed to the view being rendered
:param display_name_only: If true, don't render the fragment content at all.
Instead, just render the `display_name` of `block`
"""
# If XBlock generated this class, then use the first baseclass
# as the name (since that's the original, unmixed class)
# If any mixins have been applied, then use the unmixed class
class_name = getattr(block, 'unmixed_class', block.__class__).__name__
data = {}
css_classes = ['xblock', 'xblock-' + view]
if isinstance(block, (XModule, XModuleDescriptor)):
if view == 'student_view':
# The block is acting as an XModule
css_classes.append('xmodule_display')
elif view == 'studio_view':
# The block is acting as an XModuleDescriptor
css_classes.append('xmodule_edit')
css_classes.append('xmodule_' + class_name)
data['type'] = block.js_module_name
shim_xmodule_js(frag)
if frag.js_init_fn:
data['init'] = frag.js_init_fn
data['runtime-version'] = frag.js_init_version
data['usage-id'] = block.scope_ids.usage_id
data['block-type'] = block.scope_ids.block_type
template_context = {
'content': frag.content,
'display_name': block.display_name,
'class_': class_name,
'module_name': block.js_module_name,
'content': block.display_name if display_name_only else frag.content,
'classes': css_classes,
'data_attributes': ' '.join('data-{}="{}"'.format(key, value) for key, value in data.items()),
}
return wrap_fragment(frag, render_to_string(template, template_context))
return wrap_fragment(frag, render_to_string('xblock_wrapper.html', template_context))
def replace_jump_to_id_urls(course_id, jump_to_id_base_url, block, view, frag, context): # pylint: disable=unused-argument
......
<section class='xmodule_display xmodule_AnnotatableModule' data-type='Annotatable'>
<section class='xblock xblock-student_view xmodule_display xmodule_AnnotatableModule' data-type='Annotatable'>
<div class="annotatable-wrapper">
<div class="annotatable-header">
<div class="annotatable-title">First Annotation Exercise</div>
......
<section class="course-content">
<section class="xmodule_display xmodule_CombinedOpenEndedModule" data-type="CombinedOpenEnded">
<section class="xblock xblock-student_view xmodule_display xmodule_CombinedOpenEndedModule" data-type="CombinedOpenEnded">
<section id="combined-open-ended" class="combined-open-ended" data-ajax-url="/courses/MITx/6.002x/2012_Fall/modx/i4x://MITx/6.002x/combinedopenended/CombinedOE" data-allow_reset="False" data-state="assessing" data-task-count="2" data-task-number="1">
<h2>Problem 1</h2>
<div class="status-container">
......@@ -8,7 +8,7 @@
<section id="combined-open-ended-status" class="combined-open-ended-status">
<div class="statusitem" data-status-number="0">
Step 1 (Problem complete) : 1 / 1
<span class="correct" id="status"></span>
<span class="correct" id="status"></span>
</div>
<div class="statusitem statusitem-current" data-status-number="1">
Step 2 (Being scored) : None / 1
......@@ -109,14 +109,14 @@ location = i4x://MITx/6.002x/combinedopenended/CombinedOE
github = <a href="https://github.com/MITx/content-mit-6002x/tree/master/combinedopenended/CombinedOE.xml">https://github.com/MITx/content-mit-6002x/tree/master/combinedopenended/CombinedOE.xml</a>
definition = <pre>None</pre>
metadata = {
"showanswer": "attempted",
"display_name": "Problem 1",
"graceperiod": "1 day 12 hours 59 minutes 59 seconds",
"xqa_key": "KUBrWtK3RAaBALLbccHrXeD3RHOpmZ2A",
"rerandomize": "never",
"start": "2012-09-05T12:00",
"attempts": "10000",
"data_dir": "content-mit-6002x",
"showanswer": "attempted",
"display_name": "Problem 1",
"graceperiod": "1 day 12 hours 59 minutes 59 seconds",
"xqa_key": "KUBrWtK3RAaBALLbccHrXeD3RHOpmZ2A",
"rerandomize": "never",
"start": "2012-09-05T12:00",
"attempts": "10000",
"data_dir": "content-mit-6002x",
"max_score": "1"
}
category = CombinedOpenEndedModule
......
<li id="vert-0" data-id="i4x://Me/19.002/crowdsource_hinter/crowdsource_hinter_def7a1142dd0">
<section class="xmodule_display xmodule_CrowdsourceHinterModule" data-type="Hinter" id="hinter-root">
<section class="xblock xblock-student_view xmodule_display xmodule_CrowdsourceHinterModule" data-type="Hinter" id="hinter-root">
<section class="xmodule_display xmodule_CapaModule" data-type="Problem" id="problem">
<section class="xblock xblock-student_view xmodule_display xmodule_CapaModule" data-type="Problem" id="problem">
<section id="problem_i4x-Me-19_002-problem-Numerical_Input" class="problems-wrapper" data-problem-id="i4x://Me/19.002/problem/Numerical_Input" data-url="/courses/Me/19.002/Test/modx/i4x://Me/19.002/problem/Numerical_Input" data-progress_status="done" data-progress_detail="1/1">
......
<section class='xmodule_display xmodule_CapaModule' data-type='Problem'>
<section id='problem_1'
<section class='xblock xblock-student_view xmodule_display xmodule_CapaModule' data-type='Problem'>
<section id='problem_1'
class='problems-wrapper'
data-problem-id='i4x://edX/101/problem/Problem1'
data-problem-id='i4x://edX/101/problem/Problem1'
data-url='/problem/Problem1'>
</section>
</section>
\ No newline at end of file
......@@ -2,7 +2,7 @@ describe 'Annotatable', ->
beforeEach ->
loadFixtures 'annotatable.html'
describe 'constructor', ->
el = $('.xmodule_display.xmodule_AnnotatableModule')
el = $('.xblock-student_view.xmodule_AnnotatableModule')
beforeEach ->
@annotatable = new Annotatable(el)
it 'works', ->
......
......@@ -25,7 +25,7 @@ describe 'Problem', ->
it 'set the element from html', ->
@problem999 = new Problem ("
<section class='xmodule_display xmodule_CapaModule' data-type='Problem'>
<section class='xblock xblock-student_view xmodule_display xmodule_CapaModule' data-type='Problem'>
<section id='problem_999'
class='problems-wrapper'
data-problem-id='i4x://edX/999/problem/Quiz'
......@@ -36,14 +36,14 @@ describe 'Problem', ->
expect(@problem999.element_id).toBe 'problem_999'
it 'set the element from loadFixtures', ->
@problem1 = new Problem($('.xmodule_display'))
@problem1 = new Problem($('.xblock-student_view'))
expect(@problem1.element_id).toBe 'problem_1'
describe 'bind', ->
beforeEach ->
spyOn window, 'update_schematics'
MathJax.Hub.getAllJax.andReturn [@stubbedJax]
@problem = new Problem($('.xmodule_display'))
@problem = new Problem($('.xblock-student_view'))
it 'set mathjax typeset', ->
expect(MathJax.Hub.Queue).toHaveBeenCalled()
......@@ -78,7 +78,7 @@ describe 'Problem', ->
describe 'renderProgressState', ->
beforeEach ->
@problem = new Problem($('.xmodule_display'))
@problem = new Problem($('.xblock-student_view'))
#@renderProgressState = @problem.renderProgressState
describe 'with a status of "none"', ->
......@@ -97,7 +97,7 @@ describe 'Problem', ->
describe 'render', ->
beforeEach ->
@problem = new Problem($('.xmodule_display'))
@problem = new Problem($('.xblock-student_view'))
@bind = @problem.bind
spyOn @problem, 'bind'
......@@ -130,7 +130,7 @@ describe 'Problem', ->
describe 'check', ->
beforeEach ->
@problem = new Problem($('.xmodule_display'))
@problem = new Problem($('.xblock-student_view'))
@problem.answers = 'foo=1&bar=2'
it 'log the problem_check event', ->
......@@ -177,7 +177,7 @@ describe 'Problem', ->
describe 'reset', ->
beforeEach ->
@problem = new Problem($('.xmodule_display'))
@problem = new Problem($('.xblock-student_view'))
it 'log the problem_reset event', ->
@problem.answers = 'foo=1&bar=2'
......@@ -198,7 +198,7 @@ describe 'Problem', ->
describe 'show', ->
beforeEach ->
@problem = new Problem($('.xmodule_display'))
@problem = new Problem($('.xblock-student_view'))
@problem.el.prepend '<div id="answer_1_1" /><div id="answer_1_2" />'
describe 'when the answer has not yet shown', ->
......@@ -331,7 +331,7 @@ describe 'Problem', ->
describe 'save', ->
beforeEach ->
@problem = new Problem($('.xmodule_display'))
@problem = new Problem($('.xblock-student_view'))
@problem.answers = 'foo=1&bar=2'
it 'log the problem_save event', ->
......@@ -353,7 +353,7 @@ describe 'Problem', ->
describe 'refreshMath', ->
beforeEach ->
@problem = new Problem($('.xmodule_display'))
@problem = new Problem($('.xblock-student_view'))
$('#input_example_1').val 'E=mc^2'
@problem.refreshMath target: $('#input_example_1').get(0)
......@@ -363,7 +363,7 @@ describe 'Problem', ->
describe 'updateMathML', ->
beforeEach ->
@problem = new Problem($('.xmodule_display'))
@problem = new Problem($('.xblock-student_view'))
@stubbedJax.root.toMathML.andReturn '<MathML>'
describe 'when there is no exception', ->
......@@ -383,7 +383,7 @@ describe 'Problem', ->
describe 'refreshAnswers', ->
beforeEach ->
@problem = new Problem($('.xmodule_display'))
@problem = new Problem($('.xblock-student_view'))
@problem.el.html '''
<textarea class="CodeMirror" />
<input id="input_1_1" name="input_1_1" class="schematic" value="one" />
......
......@@ -32,4 +32,4 @@ class @Conditional
else
$(element).show()
XModule.loadModules @el
XBlock.initializeBlocks @el
......@@ -92,7 +92,7 @@ class @Sequence
@el.trigger "sequence:change"
@mark_active new_position
@$('#seq_content').html @contents.eq(new_position - 1).text()
XModule.loadModules(@$('#seq_content'))
XBlock.initializeBlocks(@$('#seq_content'))
MathJax.Hub.Queue(["Typeset", MathJax.Hub, "seq_content"]) # NOTE: Actually redundant. Some other MathJax call also being performed
window.update_schematics() # For embedded circuit simulator exercises in 6.002x
......
@XModule =
@XModule = {}
@XBlockToXModuleShim = (runtime, element) ->
###
Load a single module (either an edit module or a display module)
from the supplied element, which should have a data-type attribute
specifying the class to load
###
loadModule: (element) ->
moduleType = $(element).data('type')
if moduleType == 'None'
return
try
module = new window[moduleType](element)
if $(element).hasClass('xmodule_edit')
$(document).trigger('XModule.loaded.edit', [element, module])
if $(element).hasClass('xmodule_display')
$(document).trigger('XModule.loaded.display', [element, module])
moduleType = $(element).data('type')
if moduleType == 'None'
return
return module
try
module = new window[moduleType](element)
if $(element).hasClass('xmodule_edit')
$(document).trigger('XModule.loaded.edit', [element, module])
catch error
if window.console and console.log
console.error "Unable to load #{moduleType}: #{error.message}"
else
throw error
if $(element).hasClass('xmodule_display')
$(document).trigger('XModule.loaded.display', [element, module])
###
Load all modules on the page of the specified type.
If container is provided, only load modules inside that element
Type is one of 'display' or 'edit'
###
loadModules: (container) ->
selector = ".xmodule_edit, .xmodule_display"
return module
if container?
modules = $(container).find(selector)
catch error
if window.console and console.log
console.error "Unable to load #{moduleType}: #{error.message}"
else
modules = $(selector)
throw error
modules.each((idx, element) -> XModule.loadModule element)
class @XModule.Descriptor
......
......@@ -85,6 +85,14 @@ class HTMLSnippet(object):
.format(self.__class__))
def shim_xmodule_js(fragment):
"""
Set up the XBlock -> XModule shim on the supplied :class:`xblock.fragment.Fragment`
"""
if not fragment.js_init_fn:
fragment.initialize_js('XBlockToXModuleShim')
class XModuleMixin(XBlockMixin):
"""
Fields and methods used by XModules internally.
......@@ -92,6 +100,29 @@ class XModuleMixin(XBlockMixin):
Adding this Mixin to an :class:`XBlock` allows it to cooperate with old-style :class:`XModules`
"""
# Attributes for inspection of the descriptor
# This indicates whether the xmodule is a problem-type.
# It should respond to max_score() and grade(). It can be graded or ungraded
# (like a practice problem).
has_score = False
# Class level variable
# True if this descriptor always requires recalculation of grades, for
# example if the score can change via an extrnal service, not just when the
# student interacts with the module on the page. A specific example is
# FoldIt, which posts grade-changing updates through a separate API.
always_recalculate_grades = False
# The default implementation of get_icon_class returns the icon_class
# attribute of the class
#
# This attribute can be overridden by subclasses, and
# the function can also be overridden if the icon class depends on the data
# in the module
icon_class = 'other'
display_name = String(
display_name="Display Name",
help="This name appears in the horizontal navigation at the top of the page.",
......@@ -335,13 +366,6 @@ class XModule(XModuleMixin, HTMLSnippet, XBlock): # pylint: disable=abstract-me
See the HTML module for a simple example.
"""
# The default implementation of get_icon_class returns the icon_class
# attribute of the class
#
# This attribute can be overridden by subclasses, and
# the function can also be overridden if the icon class depends on the data
# in the module
icon_class = 'other'
has_score = descriptor_attr('has_score')
_field_data_cache = descriptor_attr('_field_data_cache')
......@@ -516,20 +540,6 @@ class XModuleDescriptor(XModuleMixin, HTMLSnippet, ResourceTemplates, XBlock):
entry_point = "xmodule.v1"
module_class = XModule
# Attributes for inspection of the descriptor
# This indicates whether the xmodule is a problem-type.
# It should respond to max_score() and grade(). It can be graded or ungraded
# (like a practice problem).
has_score = False
# Class level variable
# True if this descriptor always requires recalculation of grades, for
# example if the score can change via an extrnal service, not just when the
# student interacts with the module on the page. A specific example is
# FoldIt, which posts grade-changing updates through a separate API.
always_recalculate_grades = False
# VS[compat]. Backwards compatibility code that can go away after
# importing 2012 courses.
......@@ -862,9 +872,13 @@ class DescriptorSystem(ConfigurableFragmentWrapper, Runtime): # pylint: disable
return result
def render(self, block, view_name, context=None):
if isinstance(block, (XModule, XModuleDescriptor)) and view_name == 'student_view':
if view_name == 'student_view':
assert block.xmodule_runtime is not None
return block.xmodule_runtime.render(block._xmodule, view_name, context)
if isinstance(block, (XModule, XModuleDescriptor)):
to_render = block._xmodule
else:
to_render = block
return block.xmodule_runtime.render(to_render, view_name, context)
else:
return super(DescriptorSystem, self).render(block, view_name, context)
......
describe "$.immediateDescendents", ->
beforeEach ->
setFixtures """
<div>
<div class='xblock' id='child'>
<div class='xblock' id='nested'/>
</div>
<div>
<div class='xblock' id='grandchild'/>
</div>
</div>
"""
@descendents = $('#jasmine-fixtures').immediateDescendents(".xblock").get()
it "finds non-immediate children", ->
expect(@descendents).toContain($('#grandchild').get(0))
it "finds immediate children", ->
expect(@descendents).toContain($('#child').get(0))
it "skips nested descendents", ->
expect(@descendents).not.toContain($('#nested').get(0))
it "finds 2 children", ->
expect(@descendents.length).toBe(2)
\ No newline at end of file
describe "XBlock", ->
beforeEach ->
setFixtures """
<div>
<div class='xblock' id='vA' data-runtime-version="A" data-init="initFnA" data-name="a-name"/>
<div>
<div class='xblock' id='vZ' data-runtime-version="Z" data-init="initFnZ"/>
</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'/>
</div>
"""
describe "initializeBlock", ->
beforeEach ->
XBlock.runtime.vA = jasmine.createSpy().andReturn('runtimeA')
XBlock.runtime.vZ = jasmine.createSpy().andReturn('runtimeZ')
window.initFnA = jasmine.createSpy()
window.initFnZ = jasmine.createSpy()
@fakeChildren = ['list', 'of', 'children']
spyOn(XBlock, 'initializeBlocks').andReturn(@fakeChildren)
@vABlock = XBlock.initializeBlock($('#vA')[0])
@vZBlock = XBlock.initializeBlock($('#vZ')[0])
@missingVersionBlock = XBlock.initializeBlock($('#missing-version')[0])
@missingInitBlock = XBlock.initializeBlock($('#missing-init')[0])
it "loads the right runtime version", ->
expect(XBlock.runtime.vA).toHaveBeenCalledWith($('#vA')[0], @fakeChildren)
expect(XBlock.runtime.vZ).toHaveBeenCalledWith($('#vZ')[0], @fakeChildren)
it "loads the right init function", ->
expect(window.initFnA).toHaveBeenCalledWith('runtimeA', $('#vA')[0])
expect(window.initFnZ).toHaveBeenCalledWith('runtimeZ', $('#vZ')[0])
it "loads when missing versions", ->
expect(@missingVersionBlock.element).toBe($('#missing-version'))
expect(@missingVersionBlock.name).toBe('no-version')
it "loads when missing init fn", ->
expect(@missingInitBlock.element).toBe($('#missing-init'))
expect(@missingInitBlock.name).toBe('no-init')
it "adds names to blocks", ->
expect(@vABlock.name).toBe('a-name')
it "leaves leaves missing names undefined", ->
expect(@vZBlock.name).toBeUndefined()
it "attaches the element to the block", ->
expect(@vABlock.element).toBe($('#vA')[0])
expect(@vZBlock.element).toBe($('#vZ')[0])
expect(@missingVersionBlock.element).toBe($('#missing-version')[0])
expect(@missingInitBlock.element).toBe($('#missing-init')[0])
describe "initializeBlocks", ->
it "initializes children", ->
spyOn(XBlock, 'initializeBlock')
XBlock.initializeBlocks($('#jasmine-fixtures'))
expect(XBlock.initializeBlock).toHaveBeenCalledWith($('#vA')[0])
expect(XBlock.initializeBlock).toHaveBeenCalledWith($('#vZ')[0])
describe "XBlock.runtime.v1", ->
beforeEach ->
setFixtures """
<div class='xblock' data-usage-id='fake-usage-id'/>
"""
@children = [
{name: 'childA'},
{name: 'childB'}
]
@element = $('.xblock')[0]
@runtime = XBlock.runtime.v1(@element, @children)
it "provides a handler url", ->
expect(@runtime.handlerUrl('foo')).toBe('/xblock/handler/fake-usage-id/foo')
it "provides a list of children", ->
expect(@runtime.children).toBe(@children)
it "maps children by name", ->
expect(@runtime.childMap.childA).toBe(@children[0])
expect(@runtime.childMap.childB).toBe(@children[1])
# Find all the children of an element that match the selector, but only
# the first instance found down any path. For example, we'll find all
# the ".xblock" elements below us, but not the ones that are themselves
# contained somewhere inside ".xblock" elements.
jQuery.fn.immediateDescendents = (selector) ->
@children().map ->
elem = jQuery(this)
if elem.is(selector)
this
else
elem.immediateDescendents(selector).get()
@XBlock =
runtime: {}
initializeBlock: (element) ->
$element = $(element)
children = @initializeBlocks($element)
version = $element.data("runtime-version")
initFnName = $element.data("init")
if version? and initFnName?
runtime = @runtime["v#{version}"](element, children)
initFn = window[initFnName]
block = initFn(runtime, element) ? {}
else
elementTag = $('<div>').append($element.clone()).html();
console.log("Block #{elementTag} is missing data-runtime-version or data-init, and can't be initialized")
block = {}
block.element = element
block.name = $element.data("name")
block
initializeBlocks: (element) ->
$(element).immediateDescendents(".xblock").map((idx, elem) =>
@initializeBlock elem
).toArray()
@XBlock.runtime.v1 = (element, children) ->
childMap = {}
$.each children, (idx, child) ->
childMap[child.name] = child
return {
handlerUrl: (handlerName) ->
usageId = $(element).data("usage-id")
"/xblock/handler/#{usageId}/#{handlerName}"
children: children
childMap: childMap
}
......@@ -35,6 +35,7 @@ lib_paths:
- js/vendor/jquery.timeago.js
- coffee/src/ajax_prefix.js
- js/test/add_ajax_prefix.js
- coffee/src/jquery.immediateDescendents.js
# Paths to source JavaScript files
src_paths:
......
<section class="xmodule_display xmodule_${class_}" data-type="${module_name}">
<section class="${' '.join(classes)}" ${data_attributes}>
${content}
</section>
<section class="xmodule_edit xmodule_${class_}" data-type="${module_name}">
${content}
</section>
......@@ -25,7 +25,7 @@ from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.x_module import ModuleSystem
from xmodule_modifiers import replace_course_urls, replace_jump_to_id_urls, replace_static_urls, add_histogram, wrap_xmodule
from xmodule_modifiers import replace_course_urls, replace_jump_to_id_urls, replace_static_urls, add_histogram, wrap_xblock
import static_replace
from psychometrics.psychoanalyze import make_psychometrics_data_update_handler
......@@ -340,7 +340,7 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours
# Wrap the output display in a single div to allow for the XModule
# javascript to be bound correctly
if wrap_xmodule_display is True:
block_wrappers.append(partial(wrap_xmodule, 'xmodule_display.html'))
block_wrappers.append(wrap_xblock)
# TODO (cpennington): When modules are shared between courses, the static
# prefix is going to have to be specific to the module, not the directory
......
......@@ -299,7 +299,7 @@ class TestHtmlModifiers(ModuleStoreTestCase):
)
result_fragment = module.render('student_view')
self.assertIn('section class="xmodule_display xmodule_HtmlModule"', result_fragment.content)
self.assertIn('section class="xblock xblock-student_view xmodule_display xmodule_HtmlModule"', result_fragment.content)
def test_xmodule_display_wrapper_disabled(self):
module = render.get_module(
......@@ -312,7 +312,7 @@ class TestHtmlModifiers(ModuleStoreTestCase):
)
result_fragment = module.render('student_view')
self.assertNotIn('section class="xmodule_display xmodule_HtmlModule"', result_fragment.content)
self.assertNotIn('section class="xblock xblock-student_view xmodule_display xmodule_HtmlModule"', result_fragment.content)
def test_static_link_rewrite(self):
module = render.get_module(
......
......@@ -11,7 +11,7 @@ from django.utils.html import escape
from django.http import Http404
from django.conf import settings
from xmodule_modifiers import wrap_xmodule
from xmodule_modifiers import wrap_xblock
from xmodule.html_module import HtmlDescriptor
from xmodule.modulestore import MONGO_MODULESTORE_TYPE
from xmodule.modulestore.django import modulestore
......@@ -164,7 +164,7 @@ def _section_send_email(course_id, access, course):
""" Provide data for the corresponding bulk email section """
html_module = HtmlDescriptor(course.system, DictFieldData({'data': ''}), ScopeIds(None, None, None, None))
fragment = course.system.render(html_module, 'studio_view')
fragment = wrap_xmodule('xmodule_edit.html', html_module, 'studio_view', fragment, None)
fragment = wrap_xblock(html_module, 'studio_view', fragment, None)
email_editor = fragment.content
section_data = {
'section_key': 'send_email',
......
......@@ -23,7 +23,7 @@ from django.core.urlresolvers import reverse
from django.core.mail import send_mail
from django.utils import timezone
from xmodule_modifiers import wrap_xmodule
from xmodule_modifiers import wrap_xblock
import xmodule.graders as xmgraders
from xmodule.modulestore import MONGO_MODULESTORE_TYPE
from xmodule.modulestore.django import modulestore
......@@ -812,8 +812,8 @@ def instructor_dashboard(request, course_id):
# HTML editor for email
if idash_mode == 'Email' and is_studio_course:
html_module = HtmlDescriptor(course.system, DictFieldData({'data': html_message}), ScopeIds(None, None, None, None))
fragment = course.system.render(html_module, 'studio_view')
fragment = wrap_xmodule('xmodule_edit.html', html_module, 'studio_view', fragment, None)
fragment = html_module.render('studio_view')
fragment = wrap_xblock(html_module, 'studio_view', fragment, None)
email_editor = fragment.content
# Enable instructor email only if the following conditions are met:
......
......@@ -17,7 +17,7 @@ describe 'Courseware', ->
spyOn(window, 'Histogram')
spyOn(window, 'Problem')
spyOn(window, 'Video')
spyOn(XModule, 'loadModules')
spyOn(XBlock, 'initializeBlocks')
setFixtures """
<div class="course-content">
<div id="video_1" class="video" data-streams="1.0:abc1234"></div>
......@@ -30,7 +30,7 @@ describe 'Courseware', ->
@courseware.render()
it 'ensure that the XModules have been loaded', ->
expect(XModule.loadModules).toHaveBeenCalled()
expect(XBlock.initializeBlocks).toHaveBeenCalled()
it 'detect the histrogram element and convert it', ->
expect(window.Histogram).toHaveBeenCalledWith('3', [[0, 1]])
......@@ -11,7 +11,7 @@ class @Courseware
new Courseware
render: ->
XModule.loadModules()
XBlock.initializeBlocks($('.course-content'))
$('.course-content .histogram').each ->
id = $(this).attr('id').replace(/histogram_/, '')
try
......
......@@ -12,7 +12,7 @@ std_ajax_err = -> window.InstructorDashboard.util.std_ajax_err.apply this, argum
class SendEmail
constructor: (@$container) ->
# gather elements
@$emailEditor = XModule.loadModule($('.xmodule_edit'));
@$emailEditor = XBlock.initializeBlock($('.xblock-studio_view'));
@$send_to = @$container.find("select[name='send_to']'")
@$subject = @$container.find("input[name='subject']'")
@$btn_send = @$container.find("input[name='send']'")
......@@ -51,16 +51,16 @@ class SendEmail
dataType: 'json'
url: @$btn_send.data 'endpoint'
data: send_data
success: (data) =>
success: (data) =>
@display_response success_message
error: std_ajax_err =>
error: std_ajax_err =>
@fail_with_error gettext('Error sending email.')
else
@$task_response.empty()
@$request_response_error.empty()
fail_with_error: (msg) ->
console.warn msg
@$task_response.empty()
......
......@@ -38,6 +38,8 @@ lib_paths:
- xmodule_js/common_static/js/vendor/jquery.cookie.js
- xmodule_js/common_static/js/vendor/flot/jquery.flot.js
- xmodule_js/common_static/js/vendor/CodeMirror/codemirror.js
- xmodule_js/common_static/coffee/src/jquery.immediateDescendents.js
- xmodule_js/common_static/coffee/src/xblock
- xmodule_js/src/capa/
- xmodule_js/src/video/
- xmodule_js/src/xmodule.js
......
......@@ -544,7 +544,7 @@ function goto( mode)
<input type="submit" name="action" value="Send email">
</div>
<script type="text/javascript">
var emailEditor = XModule.loadModule($('.xmodule_edit'));
var emailEditor = XBlock.initializeBlock($('.xblock-studio_view'));
document.idashform.onsubmit = function() {
this.message.value = emailEditor.save()['data'];
return true;
......
......@@ -15,7 +15,7 @@
-e git+https://github.com/eventbrite/zendesk.git@d53fe0e81b623f084e91776bcf6369f8b7b63879#egg=zendesk
# Our libraries:
-e git+https://github.com/edx/XBlock.git@cee38a15f#egg=XBlock
-e git+https://github.com/edx/XBlock.git@74c1a2e9#egg=XBlock
-e git+https://github.com/edx/codejail.git@0a1b468#egg=codejail
-e git+https://github.com/edx/diff-cover.git@v0.2.6#egg=diff_cover
-e git+https://github.com/edx/js-test-tool.git@v0.1.1#egg=js_test_tool
......
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