Commit c7fd6641 by Matjaz Gregoric

Use virtual-dom for rendering the drag and drop interface.

This patch also includes a change where the items are positioned
absolutely (rather than relatively) after dropping into the correct
zone, which means that after dropped into the zone, the element
no longer occupies vertical space in the item list on the left,
which is desired behaviour.
parent d2ac471c
default_data = {
DEFAULT_DATA = {
"zones": [
{
"index": 1,
......
......@@ -3,7 +3,6 @@
# Imports ###########################################################
import logging
import json
import webob
import copy
......@@ -14,12 +13,7 @@ from xblock.fields import Scope, String, Dict, Float, Boolean
from xblock.fragment import Fragment
from .utils import render_template, load_resource
from .default_data import default_data
# Globals ###########################################################
log = logging.getLogger(__name__)
from .default_data import DEFAULT_DATA
# Classes ###########################################################
......@@ -53,7 +47,7 @@ class DragAndDropBlock(XBlock):
display_name="Drag and Drop",
help="JSON spec as generated by the builder",
scope=Scope.content,
default=default_data
default=DEFAULT_DATA
)
item_state = Dict(
......@@ -75,30 +69,22 @@ class DragAndDropBlock(XBlock):
Player view, displayed to the student
"""
js_templates = load_resource('/templates/html/js_templates.html')
context = {
'js_templates': js_templates,
'title': self.display_name,
'question_text': self.question_text,
}
fragment = Fragment()
fragment.add_content(render_template('/templates/html/drag_and_drop.html', context))
CSS_URLS = (
fragment.add_content(render_template('/templates/html/drag_and_drop.html'))
css_urls = (
'public/css/vendor/jquery-ui-1.10.4.custom.min.css',
'public/css/drag_and_drop.css'
)
JS_URLS = (
js_urls = (
'public/js/vendor/jquery-ui-1.10.4.custom.min.js',
'public/js/vendor/jquery-ui-touch-punch-0.2.3.min.js', # Makes it work on touch devices
'public/js/vendor/jquery.html5-placeholder-shim.js',
'public/js/vendor/handlebars-v1.1.2.js',
'public/js/vendor/virtual-dom-1.3.0.min.js',
'public/js/drag_and_drop.js',
'public/js/view.js',
)
for css_url in CSS_URLS:
for css_url in css_urls:
fragment.add_css_url(self.runtime.local_resource_url(self, css_url))
for js_url in JS_URLS:
for js_url in js_urls:
fragment.add_javascript_url(self.runtime.local_resource_url(self, js_url))
fragment.initialize_js('DragAndDropBlock')
......@@ -119,18 +105,21 @@ class DragAndDropBlock(XBlock):
fragment = Fragment()
fragment.add_content(render_template('/templates/html/drag_and_drop_edit.html', context))
fragment.add_css_url(self.runtime.local_resource_url(self,
'public/css/vendor/jquery-ui-1.10.4.custom.min.css'))
fragment.add_css_url(self.runtime.local_resource_url(self,
'public/css/drag_and_drop_edit.css'))
fragment.add_javascript_url(self.runtime.local_resource_url(self,
'public/js/vendor/jquery-ui-1.10.4.custom.min.js'))
fragment.add_javascript_url(self.runtime.local_resource_url(self,
'public/js/vendor/jquery.html5-placeholder-shim.js'))
fragment.add_javascript_url(self.runtime.local_resource_url(self,
'public/js/vendor/handlebars-v1.1.2.js'))
fragment.add_javascript_url(self.runtime.local_resource_url(self,
'public/js/drag_and_drop_edit.js'))
css_urls = (
'public/css/vendor/jquery-ui-1.10.4.custom.min.css',
'public/css/drag_and_drop_edit.css'
)
js_urls = (
'public/js/vendor/jquery-ui-1.10.4.custom.min.js',
'public/js/vendor/jquery.html5-placeholder-shim.js',
'public/js/vendor/handlebars-v1.1.2.js',
'public/js/drag_and_drop_edit.js',
)
for css_url in css_urls:
fragment.add_css_url(self.runtime.local_resource_url(self, css_url))
for js_url in js_urls:
fragment.add_javascript_url(self.runtime.local_resource_url(self, js_url))
fragment.initialize_js('DragAndDropEditBlock')
......@@ -149,33 +138,12 @@ class DragAndDropBlock(XBlock):
@XBlock.handler
def get_data(self, request, suffix=''):
data = copy.deepcopy(self.data)
for item in data['items']:
# Strip answers
del item['feedback']
del item['zone']
item['inputOptions'] = item.has_key('inputOptions')
if not self._is_finished():
del data['feedback']['finish']
item_state = self._get_item_state()
for item_id, item in item_state.iteritems():
definition = next(i for i in self.data['items'] if str(i['id']) == item_id)
item['correct_input'] = self._is_correct_input(definition, item.get('input'))
data['state'] = {
'items': item_state,
'finished': self._is_finished()
}
data = self._get_data()
return webob.response.Response(body=json.dumps(data))
@XBlock.json_handler
def do_attempt(self, attempt, suffix=''):
item = next(i for i in self.data['items'] if i['id'] == attempt['val'])
tot_items = sum(1 for i in self.data['items'] if i['zone'] != 'none')
state = None
feedback = item['feedback']['incorrect']
......@@ -195,7 +163,7 @@ class DragAndDropBlock(XBlock):
is_correct = False
elif item['zone'] == attempt['zone']:
is_correct_location = True
if item.has_key('inputOptions'):
if 'inputOptions' in item:
# Input value will have to be provided for the item.
# It is not (yet) correct and no feedback should be shown yet.
is_correct = False
......@@ -204,7 +172,11 @@ class DragAndDropBlock(XBlock):
# If this item has no input value set, we are done with it.
is_correct = True
feedback = item['feedback']['correct']
state = {'top': attempt['top'], 'left': attempt['left']}
state = {
'top': attempt['top'],
'left': attempt['left'],
'absolute': True # flag for backwards compatibility (values used to be relative)
}
if state:
self.item_state[str(item['id'])] = state
......@@ -247,7 +219,34 @@ class DragAndDropBlock(XBlock):
@XBlock.json_handler
def reset(self, data, suffix=''):
self.item_state = {}
return {'result':'success'}
return self._get_data()
def _get_data(self):
data = copy.deepcopy(self.data)
for item in data['items']:
# Strip answers
del item['feedback']
del item['zone']
item['inputOptions'] = 'inputOptions' in item
if not self._is_finished():
del data['feedback']['finish']
item_state = self._get_item_state()
for item_id, item in item_state.iteritems():
definition = next(i for i in self.data['items'] if str(i['id']) == item_id)
item['correct_input'] = self._is_correct_input(definition, item.get('input'))
data['state'] = {
'items': item_state,
'finished': self._is_finished()
}
data['title'] = self.display_name
data['question_text'] = self.question_text
return data
def _get_item_state(self):
"""
......@@ -264,24 +263,6 @@ class DragAndDropBlock(XBlock):
return state
def _is_correct_input(self, item, val):
"""
Is submitted numerical value within the tolerated margin for this item.
"""
input_options = item.get('inputOptions')
if input_options:
try:
submitted_value = float(val)
except:
return False
else:
expected_value = input_options['value']
margin = input_options['margin']
return abs(submitted_value - expected_value) <= margin
else:
return True
def _get_grade(self):
"""
Returns the student's grade for this block.
......@@ -313,8 +294,8 @@ class DragAndDropBlock(XBlock):
total_count += 1
item_id = str(item['id'])
if item_id in item_state:
if item.has_key('inputOptions'):
if item_state[item_id].has_key('input'):
if 'inputOptions' in item:
if 'input' in item_state[item_id]:
completed_count += 1
else:
completed_count += 1
......@@ -325,24 +306,43 @@ class DragAndDropBlock(XBlock):
def publish_event(self, data, suffix=''):
try:
event_type = data.pop('event_type')
except KeyError as e:
except KeyError:
return {'result': 'error', 'message': 'Missing event_type in JSON data'}
data['user_id'] = self.scope_ids.user_id
data['component_id'] = self._get_unique_id()
self.runtime.publish(self, event_type, data)
return {'result':'success'}
return {'result': 'success'}
def _get_unique_id(self):
try:
unique_id = self.location.name
unique_id = self.location.name # pylint: disable=no-member
except AttributeError:
# workaround for xblock workbench
unique_id = self.parent and self.parent.replace('.', '-')
return unique_id
@staticmethod
def _is_correct_input(item, val):
"""
Is submitted numerical value within the tolerated margin for this item.
"""
input_options = item.get('inputOptions')
if input_options:
try:
submitted_value = float(val)
except (ValueError, TypeError):
return False
else:
expected_value = input_options['value']
margin = input_options['margin']
return abs(submitted_value - expected_value) <= margin
else:
return True
@staticmethod
def workbench_scenarios():
"""
A canned scenario for display in the workbench.
......
......@@ -47,15 +47,15 @@
width: 210px;
margin: 10px;
padding: 0 !important; /* LMS tries to override this */
font-size: 14px;
position: relative;
display: inline;
float: left;
list-style-type: none;
}
.xblock--drag-and-drop .drag-container .items .option {
.xblock--drag-and-drop .drag-container .option {
width: 190px;
font-size: 14px;
background: #2e83cd;
color: #fff;
position: relative;
......@@ -68,28 +68,23 @@
opacity: 1;
}
.xblock--drag-and-drop .drag-container .items .option img {
.xblock--drag-and-drop .drag-container .option img {
max-width: 100%;
}
.xblock--drag-and-drop .drag-container .items .option .numerical-input {
display: none;
.xblock--drag-and-drop .drag-container .option .numerical-input {
height: 32px;
position: absolute;
left: calc(100% + 5px);
top: calc(50% - 16px);
}
.xblock--drag-and-drop .drag-container .items .option.within-dropzone .numerical-input {
display: block;
}
.xblock--drag-and-drop .drag-container .items .option.within-dropzone .numerical-input .input {
.xblock--drag-and-drop .drag-container .option .numerical-input .input {
display: inline-block;
width: 144px;
}
.xblock--drag-and-drop .drag-container .items .option.within-dropzone .numerical-input .submit-input {
.xblock--drag-and-drop .drag-container .option .numerical-input .submit-input {
box-sizing: border-box;
position: absolute;
left: 150px;
......@@ -97,22 +92,22 @@
height: 24px;
}
.xblock--drag-and-drop .drag-container .items .option.within-dropzone .numerical-input.correct .input-submit,
.xblock--drag-and-drop .drag-container .items .option.within-dropzone .numerical-input.incorrect .input-submit {
.xblock--drag-and-drop .drag-container .option .numerical-input.correct .input-submit,
.xblock--drag-and-drop .drag-container .option .numerical-input.incorrect .input-submit {
display: none;
}
.xblock--drag-and-drop .drag-container .items .option.within-dropzone .numerical-input.correct .input {
.xblock--drag-and-drop .drag-container .option .numerical-input.correct .input {
background: #ceffce;
color: #0dad0d;
}
.xblock--drag-and-drop .drag-container .items .option.within-dropzone .numerical-input.incorrect .input {
.xblock--drag-and-drop .drag-container .option .numerical-input.incorrect .input {
background: #ffcece;
color: #ad0d0d;
}
.xblock--drag-and-drop .drag-container .items .option.fade {
.xblock--drag-and-drop .drag-container .option.fade {
opacity: 0.5;
}
......@@ -129,7 +124,6 @@
}
.xblock--drag-and-drop .target-img {
display: none;
background: url('../img/triangle.png') no-repeat;
width: 100%;
height: 100%;
......@@ -229,5 +223,4 @@
float: right;
color: #3384CA;
margin-top: 3px;
display: none;
}
function DragAndDropBlock(runtime, element) {
function publish_event(data) {
$.ajax({
type: "POST",
url: runtime.handlerUrl(element, 'publish_event'),
data: JSON.stringify(data)
});
}
var dragAndDrop = (function($) {
var _fn = {
pupup_ts: Date.now(),
// DOM Elements
$ul: $('.xblock--drag-and-drop .items', element),
$target: $('.xblock--drag-and-drop .target-img', element),
$feedback: $('.xblock--drag-and-drop .feedback .message', element),
$popup: $('.xblock--drag-and-drop .popup', element),
$reset_button: $('.xblock--drag-and-drop .reset-button', element),
// Cannot set until items added to DOM
$items: {}, // $('.xblock--drag-and-drop .items .option'),
$zones: {}, // $('.xblock--drag-and-drop .target .zone'),
// jQuery UI Draggable options
options: {
drag: {
var root = $(element).find('.xblock--drag-and-drop')[0];
var __state;
var __vdom = virtualDom.h(); // blank virtual DOM
var init = function() {
$.ajax(runtime.handlerUrl(element, 'get_data'), {
dataType: 'json'
}).done(function(data){
setState(data);
initDroppable();
});
$(document).on('mousedown touchstart', closePopup);
$(element).on('click', '.reset-button', resetExercise);
$(element).on('click', '.submit-input', submitInput);
publishEvent({event_type: 'xblock.drag-and-drop-v2.loaded'});
};
var getState = function() {
return __state;
};
var setState = function(new_state) {
if (new_state.state.feedback) {
if (new_state.state.feedback !== __state.state.feedback) {
publishEvent({
event_type: 'xblock.drag-and-drop-v2.feedback.closed',
content: __state.state.feedback,
manually: false
});
}
publishEvent({
event_type: 'xblock.drag-and-drop-v2.feedback.opened',
content: new_state.state.feedback
});
}
__state = new_state;
updateDOM(new_state);
destroyDraggable();
if (!new_state.state.finished) {
initDraggable();
}
};
var updateDOM = function(state) {
var new_vdom = render(state);
var patches = virtualDom.diff(__vdom, new_vdom);
root = virtualDom.patch(root, patches);
__vdom = new_vdom;
};
var publishEvent = function(data) {
$.ajax({
type: 'POST',
url: runtime.handlerUrl(element, 'publish_event'),
data: JSON.stringify(data)
});
};
var initDroppable = function() {
$(root).find('.zone').droppable({
accept: '.xblock--drag-and-drop .items .option',
tolerance: 'pointer',
drop: function(evt, ui) {
var item_id = ui.draggable.data('value');
var zone = $(this).data('zone');
var position = ui.draggable.position();
var top = position.top + 'px';
var left = position.left + 'px';
var state = getState();
state.state.items[item_id] = {
top: top,
left: left,
absolute: true,
submitting_location: true
};
// Wrap in setTimeout to let the droppable event finish.
setTimeout(function() {
setState(state);
submitLocation(item_id, zone, top, left);
}, 0);
}
});
};
var initDraggable = function() {
$(root).find('.items .option').not('[data-drag-disabled=true]').each(function() {
try {
$(this).draggable({
containment: '.xblock--drag-and-drop .drag-container',
cursor: 'move',
stack: '.xblock--drag-and-drop .items .option'
},
drop: {
accept: '.xblock--drag-and-drop .items .option',
tolerance: 'pointer'
}
},
tpl: {
init: function() {
_fn.tpl = {
item: Handlebars.compile($("#item-tpl", element).html()),
imageItem: Handlebars.compile($("#image-item-tpl", element).html()),
zoneElement: Handlebars.compile($("#zone-element-tpl", element).html())
};
}
},
init: function(data) {
_fn.data = data;
// Compile templates
_fn.tpl.init();
// Add the items to the page
_fn.items.draw();
_fn.zones.draw();
// Init drag and drop plugin
_fn.$items.draggable(_fn.options.drag);
_fn.$zones.droppable(_fn.options.drop);
// Init click handlers
_fn.eventHandlers.init(_fn.$items, _fn.$zones);
// Position the already correct items
_fn.items.init();
// Load welcome or final feedback
if (_fn.data.state.finished)
_fn.finish(_fn.data.feedback.finish);
else
_fn.feedback.set(_fn.data.feedback.start);
// Set the target image
if (_fn.data.targetImg)
_fn.$target.css('background', 'url(' + _fn.data.targetImg + ') no-repeat');
// Display target image
_fn.$target.show();
// Display the zone names if required
if (_fn.data.displayLabels) {
$('p', _fn.$zones).css('visibility', 'visible');
}
},
finish: function(final_feedback) {
// Disable any decoy items
_fn.$items.draggable('disable');
_fn.$reset_button.show();
// Show final feedback
if (final_feedback) _fn.feedback.set(final_feedback);
},
reset: function() {
_fn.$items.draggable('enable');
_fn.$items.find('.numerical-input').removeClass('correct incorrect');
_fn.$items.find('.numerical-input .input').prop('disabled', false).val('');
_fn.$items.find('.numerical-input .submit-input').prop('disabled', false);
_fn.$items.each(function(index, element) {
_fn.eventHandlers.drag.reset($(element));
});
_fn.$popup.hide();
_fn.$reset_button.hide();
_fn.feedback.set(_fn.data.feedback.start);
},
eventHandlers: {
init: function($drag, $dropzone) {
var handlers = _fn.eventHandlers;
$drag.on('dragstart', handlers.drag.start);
$drag.on('dragstop', handlers.drag.stop);
$dropzone.on('drop', handlers.drop.success);
$dropzone.on('dropover', handlers.drop.hover);
$(element).on('click', '.submit-input', handlers.drag.submitInput);
$(document).on('click', function(evt) {
// Click should only close the popup if the popup has been
// visible for at least one second.
var popup_timeout = 1000;
if (Date.now() - _fn.popup_ts > popup_timeout) {
handlers.popup.close(evt);
}
});
_fn.$reset_button.on('click', handlers.problem.reset);
},
problem: {
reset: function(event, ui) {
$.ajax({
type: "POST",
url: runtime.handlerUrl(element, "reset"),
data: "{}",
success: _fn.reset
});
}
},
popup: {
close: function(event, ui) {
target = $(event.target);
popup_box = ".xblock--drag-and-drop .popup";
close_button = ".xblock--drag-and-drop .popup .close";
if (target.is(popup_box)) {
return;
};
if (target.parents(popup_box).length>0 && !target.is(close_button)) {
return;
};
_fn.$popup.hide();
publish_event({
event_type: 'xblock.drag-and-drop-v2.feedback.closed',
content: _fn.$popup.find(".popup-content").text(),
manually: true
});
}
},
drag: {
start: function(event, ui) {
_fn.eventHandlers.popup.close(event, ui);
target = $(event.currentTarget);
target.removeClass('within-dropzone fade');
var item_id = target.data("value");
publish_event({event_type:'xblock.drag-and-drop-v2.item.picked-up', item_id:item_id});
},
stop: function(event, ui) {
var $el = $(event.currentTarget);
if (!$el.hasClass('within-dropzone')) {
// Return to original position
_fn.eventHandlers.drag.reset($el);
} else {
_fn.eventHandlers.drag.submitLocation($el);
}
},
submitLocation: function($el) {
var val = $el.data('value'),
zone = $el.data('zone') || null;
$.post(runtime.handlerUrl(element, 'do_attempt'),
JSON.stringify({
val: val,
zone: zone,
top: $el.css('top'),
left: $el.css('left')
}), 'json').done(function(data){
if (data.correct_location) {
$el.draggable('disable');
if (data.finished) {
_fn.finish(data.final_feedback);
}
} else {
// Return to original position
_fn.eventHandlers.drag.reset($el);
}
if (data.feedback) {
_fn.feedback.popup(data.feedback, data.correct);
}
stack: '.xblock--drag-and-drop .items .option',
revert: 'invalid',
revertDuration: 150,
start: function(evt, ui) {
var item_id = $(this).data('value');
publishEvent({
event_type: 'xblock.drag-and-drop-v2.item.picked-up',
item_id: item_id
});
},
submitInput: function(evt) {
var $el = $(this).closest('li.option');
var $input_div = $el.find('.numerical-input');
var $input = $input_div.find('.input');
var val = $el.data('value');
if (!$input.val()) {
// Don't submit if the user didn't enter anything yet.
return;
}
$input.prop('disabled', true);
$input_div.find('.submit-input').prop('disabled', true);
$.post(runtime.handlerUrl(element, 'do_attempt'),
JSON.stringify({
val: val,
input: $input.val()
}), 'json').done(function(data){
if (data.correct) {
$input_div.removeClass('incorrect').addClass('correct');
} else {
$input_div.removeClass('correct').addClass('incorrect');
}
if (data.finished) {
_fn.finish(data.final_feedback);
}
if (data.feedback) {
_fn.feedback.popup(data.feedback, data.correct);
}
});
},
set: function($el, top, left) {
$el.addClass('within-dropzone')
.css({
top: top,
left: left
})
.draggable('disable');
},
reset: function($el) {
$el.removeClass('within-dropzone fade')
.css({
top: '',
left: ''
});
}
},
drop: {
hover: function(event, ui) {
var zone = $(event.currentTarget).data('zone');
ui.draggable.data('zone', zone);
},
success: function(event, ui) {
ui.draggable.addClass('within-dropzone');
var item = _fn.data.items[ui.draggable.data('value')];
if (item.inputOptions) {
ui.draggable.find('.input').focus();
}
}
}
},
items: {
init: function() {
_fn.$items.each(function (){
var $el = $(this),
saved_entry = _fn.data.state.items[$el.data('value')];
if (saved_entry) {
var $input_div = $el.find('.numerical-input')
var $input = $input_div.find('.input');
$input.val(saved_entry.input);
console.log('wuwuwu', saved_entry)
if ('input' in saved_entry) {
$input_div.addClass(saved_entry.correct_input ? 'correct' : 'incorrect');
$input.prop('disabled', true);
$input_div.find('.submit-input').prop('disabled', true);
}
if ('input' in saved_entry || saved_entry.correct_input) {
$el.addClass('fade');
}
_fn.eventHandlers.drag.set($el, saved_entry.top, saved_entry.left);
}
});
},
draw: function() {
var list = [],
items = _fn.data.items,
tpl = _fn.tpl.item,
img_tpl = _fn.tpl.imageItem;
items.forEach(function(item) {
if (item.backgroundImage.length > 0) {
list.push(img_tpl(item));
} else {
list.push(tpl(item));
}
});
// Update DOM
_fn.$ul.html(list.join(''));
// Set variable
_fn.$items = $('.xblock--drag-and-drop .items .option', element);
}
},
zones: {
draw: function() {
var html = [],
zones = _fn.data.zones,
tpl = _fn.tpl.zoneElement,
i,
len = zones.length;
for (i=0; i<len; i++) {
html.push(tpl(zones[i]));
}
// Update DOM
_fn.$target.html(html.join(''));
// Set variable
_fn.$zones = _fn.$target.find('.zone');
}
},
feedback: {
// Update DOM with feedback
set: function(str) {
if ($.trim(str) === '') _fn.$feedback.parent().hide();
else _fn.$feedback.parent().show();
return _fn.$feedback.html(str);
},
// Show a feedback popup
popup: function(str, boo) {
if (str === undefined || str === '') return;
if (_fn.$popup.is(":visible")) {
publish_event({
event_type: "xblock.drag-and-drop-v2.feedback.closed",
content: _fn.$popup.find(".popup-content").text(),
manually: false
});
}
publish_event({
event_type: "xblock.drag-and-drop-v2.feedback.opened",
content: str
});
_fn.$popup.find(".popup-content").html(str);
_fn.$popup.show();
_fn.popup_ts = Date.now();
});
} catch (e) {
// Initializing the draggable will fail if draggable was already
// initialized. That's expected, ignore the exception.
}
});
};
var destroyDraggable = function() {
$(root).find('.items .option[data-drag-disabled=true]').each(function() {
try {
$(this).draggable('destroy');
} catch (e) {
// Destroying the draggable will fail if draggable was
// not initialized in the first place. Ignore the exception.
}
});
};
var submitLocation = function(item_id, zone, top, left) {
if (!zone) {
return;
}
var url = runtime.handlerUrl(element, 'do_attempt');
var data = {val: item_id, zone: zone, top: top, left: left};
$.post(url, JSON.stringify(data), 'json').done(function(data){
var state = getState();
if (data.correct_location) {
state.state.items[item_id].correct_input = Boolean(data.correct);
state.state.items[item_id].submitting_location = false;
} else {
delete state.state.items[item_id];
}
state.state.feedback = data.feedback;
if (data.finished) {
state.state.finished = true;
state.feedback.finish = data.final_feedback;
}
setState(state);
});
};
var submitInput = function(evt) {
var item = $(evt.target).closest('.option');
var input_div = item.find('.numerical-input');
var input = input_div.find('.input');
var input_value = input.val();
var item_id = item.data('value');
if (!input_value) {
// Don't submit if the user didn't enter anything yet.
return;
}
var state = getState();
state.state.items[item_id].input = input_value;
state.state.items[item_id].submitting_input = true;
setState(state);
var url = runtime.handlerUrl(element, 'do_attempt');
var data = {val: item_id, input: input_value};
$.post(url, JSON.stringify(data), 'json').done(function(data) {
state.state.items[item_id].submitting_input = false;
state.state.items[item_id].correct_input = data.correct;
state.state.feedback = data.feedback;
if (data.finished) {
state.state.finished = true;
state.feedback.finish = data.final_feedback;
}
setState(state);
});
};
var closePopup = function(evt) {
var target = $(evt.target);
var popup_box = '.xblock--drag-and-drop .popup';
var close_button = '.xblock--drag-and-drop .popup .close';
var submit_input_button = '.xblock--drag-and-drop .submit-input';
var state = getState();
if (!state.state.feedback) {
return;
}
if (target.is(popup_box) || target.is(submit_input_button)) {
return;
}
if (target.parents(popup_box).length && !target.is(close_button)) {
return;
}
publishEvent({
event_type: 'xblock.drag-and-drop-v2.feedback.closed',
content: state.state.feedback,
manually: true
});
delete state.state.feedback;
setState(state);
};
var resetExercise = function() {
$.ajax({
type: 'POST',
url: runtime.handlerUrl(element, 'reset'),
data: '{}',
success: setState
});
};
var render = function(state) {
var items = state.items.map(function(item) {
var item_state = state.state.items[item.id];
var position = item_state || {};
var input = null;
if (item.inputOptions) {
input = {
is_visible: item_state && !item_state.submitting_location,
has_value: Boolean(item_state && 'input' in item_state),
value : (item_state && item_state.input) || '',
class_name: undefined,
};
if (input.has_value && !item_state.submitting_input) {
input.class_name = item_state.correct_input ? 'correct' : 'incorrect';
}
},
data: null
};
return {
init: _fn.init,
}
return {
value: item.id,
drag_disabled: Boolean(item_state || state.state.finished),
width: item.size.width,
height: item.size.height,
top: position.top,
left: position.left,
position: position.absolute ? 'absolute' : 'relative',
class_name: item_state && ('input' in item_state || item_state.correct_input) ? 'fade': undefined,
input: input,
content_html: item.backgroundImage ? '<img src="' + item.backgroundImage + '"/>' : item.displayName
};
});
var context = {
header_html: state.title,
question_html: state.question_text,
popup_html: state.state.feedback || '',
feedback_html: $.trim(state.state.finished ? state.feedback.finish : state.feedback.start),
target_img_src: state.targetImg,
display_zone_labels: state.displayLabels,
display_reset_button: state.state.finished,
zones: state.zones,
items: items
};
})(jQuery);
$.ajax(runtime.handlerUrl(element, 'get_data'), {
dataType: 'json'
}).done(function(data){
dragAndDrop.init(data);
});
return DragAndDropBlock.renderView(context);
};
publish_event({event_type:"xblock.drag-and-drop-v2.loaded"});
init();
}
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define("virtual-dom-1.3.0",[],e);else{var n;"undefined"!=typeof window?n=window:"undefined"!=typeof global?n=global:"undefined"!=typeof self&&(n=self),n.virtualDom=e()}}(function(){return function e(n,t,r){function o(s,u){if(!t[s]){if(!n[s]){var a="function"==typeof require&&require;if(!u&&a)return a(s,!0);if(i)return i(s,!0);var f=new Error("Cannot find module '"+s+"'");throw f.code="MODULE_NOT_FOUND",f}var v=t[s]={exports:{}};n[s][0].call(v.exports,function(e){var t=n[s][1][e];return o(t?t:e)},v,v.exports,e,n,t,r)}return t[s].exports}for(var i="function"==typeof require&&require,s=0;s<r.length;s++)o(r[s]);return o}({1:[function(e,n){var t=e("./vdom/create-element.js");n.exports=t},{"./vdom/create-element.js":15}],2:[function(e,n){var t=e("./vtree/diff.js");n.exports=t},{"./vtree/diff.js":35}],3:[function(e,n){var t=e("./virtual-hyperscript/index.js");n.exports=t},{"./virtual-hyperscript/index.js":22}],4:[function(e,n){var t=e("./diff.js"),r=e("./patch.js"),o=e("./h.js"),i=e("./create-element.js");n.exports={diff:t,patch:r,h:o,create:i}},{"./create-element.js":1,"./diff.js":2,"./h.js":3,"./patch.js":13}],5:[function(e,n){n.exports=function(e){var n,t=String.prototype.split,r=/()??/.exec("")[1]===e;return n=function(n,o,i){if("[object RegExp]"!==Object.prototype.toString.call(o))return t.call(n,o,i);var s,u,a,f,v=[],d=(o.ignoreCase?"i":"")+(o.multiline?"m":"")+(o.extended?"x":"")+(o.sticky?"y":""),c=0,o=new RegExp(o.source,d+"g");for(n+="",r||(s=new RegExp("^"+o.source+"$(?!\\s)",d)),i=i===e?-1>>>0:i>>>0;(u=o.exec(n))&&(a=u.index+u[0].length,!(a>c&&(v.push(n.slice(c,u.index)),!r&&u.length>1&&u[0].replace(s,function(){for(var n=1;n<arguments.length-2;n++)arguments[n]===e&&(u[n]=e)}),u.length>1&&u.index<n.length&&Array.prototype.push.apply(v,u.slice(1)),f=u[0].length,c=a,v.length>=i)));)o.lastIndex===u.index&&o.lastIndex++;return c===n.length?(f||!o.test(""))&&v.push(""):v.push(n.slice(c)),v.length>i?v.slice(0,i):v}}()},{}],6:[function(){},{}],7:[function(e,n){"use strict";function t(e){var n=e[i];return n||(n=e[i]={}),n}var r=e("individual/one-version"),o="7";r("ev-store",o);var i="__EV_STORE_KEY@"+o;n.exports=t},{"individual/one-version":9}],8:[function(e,n){(function(e){"use strict";function t(e,n){return e in r?r[e]:(r[e]=n,n)}var r="undefined"!=typeof window?window:"undefined"!=typeof e?e:{};n.exports=t}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],9:[function(e,n){"use strict";function t(e,n,t){var o="__INDIVIDUAL_ONE_VERSION_"+e,i=o+"_ENFORCE_SINGLETON",s=r(i,n);if(s!==n)throw new Error("Can only have one copy of "+e+".\nYou already have version "+s+" installed.\nThis means you cannot install version "+n);return r(o,t)}var r=e("./index.js");n.exports=t},{"./index.js":8}],10:[function(e,n){(function(t){var r="undefined"!=typeof t?t:"undefined"!=typeof window?window:{},o=e("min-document");if("undefined"!=typeof document)n.exports=document;else{var i=r["__GLOBAL_DOCUMENT_CACHE@4"];i||(i=r["__GLOBAL_DOCUMENT_CACHE@4"]=o),n.exports=i}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"min-document":6}],11:[function(e,n){"use strict";n.exports=function(e){return"object"==typeof e&&null!==e}},{}],12:[function(e,n){function t(e){return"[object Array]"===o.call(e)}var r=Array.isArray,o=Object.prototype.toString;n.exports=r||t},{}],13:[function(e,n){var t=e("./vdom/patch.js");n.exports=t},{"./vdom/patch.js":18}],14:[function(e,n){function t(e,n,t){for(var i in n){var a=n[i];void 0===a?r(e,i,a,t):u(a)?(r(e,i,a,t),a.hook&&a.hook(e,i,t?t[i]:void 0)):s(a)?o(e,n,t,i,a):e[i]=a}}function r(e,n,t,r){if(r){var o=r[n];if(u(o))o.unhook&&o.unhook(e,n,t);else if("attributes"===n)for(var i in o)e.removeAttribute(i);else if("style"===n)for(var s in o)e.style[s]="";else e[n]="string"==typeof o?"":null}}function o(e,n,t,r,o){var u=t?t[r]:void 0;if("attributes"!==r){if(u&&s(u)&&i(u)!==i(o))return void(e[r]=o);s(e[r])||(e[r]={});var a="style"===r?"":void 0;for(var f in o){var v=o[f];e[r][f]=void 0===v?a:v}}else for(var d in o){var c=o[d];void 0===c?e.removeAttribute(d):e.setAttribute(d,c)}}function i(e){return Object.getPrototypeOf?Object.getPrototypeOf(e):e.__proto__?e.__proto__:e.constructor?e.constructor.prototype:void 0}var s=e("is-object"),u=e("../vnode/is-vhook.js");n.exports=t},{"../vnode/is-vhook.js":26,"is-object":11}],15:[function(e,n){function t(e,n){var f=n?n.document||r:r,v=n?n.warn:null;if(e=a(e).a,u(e))return e.init();if(s(e))return f.createTextNode(e.text);if(!i(e))return v&&v("Item is not a valid virtual dom node",e),null;var d=null===e.namespace?f.createElement(e.tagName):f.createElementNS(e.namespace,e.tagName),c=e.properties;o(d,c);for(var p=e.children,l=0;l<p.length;l++){var h=t(p[l],n);h&&d.appendChild(h)}return d}var r=e("global/document"),o=e("./apply-properties"),i=e("../vnode/is-vnode.js"),s=e("../vnode/is-vtext.js"),u=e("../vnode/is-widget.js"),a=e("../vnode/handle-thunk.js");n.exports=t},{"../vnode/handle-thunk.js":24,"../vnode/is-vnode.js":27,"../vnode/is-vtext.js":28,"../vnode/is-widget.js":29,"./apply-properties":14,"global/document":10}],16:[function(e,n){function t(e,n,t,o){return t&&0!==t.length?(t.sort(i),r(e,n,t,o,0)):{}}function r(e,n,t,i,u){if(i=i||{},e){o(t,u,u)&&(i[u]=e);var a=n.children;if(a)for(var f=e.childNodes,v=0;v<n.children.length;v++){u+=1;var d=a[v]||s,c=u+(d.count||0);o(t,u,c)&&r(f[v],d,t,i,u),u=c}}return i}function o(e,n,t){if(0===e.length)return!1;for(var r,o,i=0,s=e.length-1;s>=i;){if(r=(s+i)/2>>0,o=e[r],i===s)return o>=n&&t>=o;if(n>o)i=r+1;else{if(!(o>t))return!0;s=r-1}}return!1}function i(e,n){return e>n?1:-1}var s={};n.exports=t},{}],17:[function(e,n){function t(e,n,t){var a=e.type,c=e.vNode,l=e.patch;switch(a){case p.REMOVE:return r(n,c);case p.INSERT:return o(n,l,t);case p.VTEXT:return i(n,c,l,t);case p.WIDGET:return s(n,c,l,t);case p.VNODE:return u(n,c,l,t);case p.ORDER:return f(n,l),n;case p.PROPS:return d(n,l,c.properties),n;case p.THUNK:return v(n,t.patch(n,l,t));default:return n}}function r(e,n){var t=e.parentNode;return t&&t.removeChild(e),a(e,n),null}function o(e,n,t){var r=l(n,t);return e&&e.appendChild(r),e}function i(e,n,t,r){var o;if(3===e.nodeType)e.replaceData(0,e.length,t.text),o=e;else{var i=e.parentNode;o=l(t,r),i&&i.replaceChild(o,e)}return o}function s(e,n,t,r){var o,i=h(n,t);o=i?t.update(n,e)||e:l(t,r);var s=e.parentNode;return s&&o!==e&&s.replaceChild(o,e),i||a(e,n),o}function u(e,n,t,r){var o=e.parentNode,i=l(t,r);return o&&o.replaceChild(i,e),i}function a(e,n){"function"==typeof n.destroy&&c(n)&&n.destroy(e)}function f(e,n){var t,r=[],o=e.childNodes,i=o.length,s=n.reverse;for(t=0;i>t;t++)r.push(e.childNodes[t]);var u,a,f,v,d,c=0;for(t=0;i>t;){if(u=n[t],v=1,void 0!==u&&u!==t){for(;n[t+v]===u+v;)v++;for(s[t]>t+v&&c++,a=r[u],f=o[t+c]||null,d=0;a!==f&&d++<v;)e.insertBefore(a,f),a=r[u+d];t>u+v&&c--}t in n.removes&&c++,t+=v}}function v(e,n){return e&&n&&e!==n&&e.parentNode&&(console.log(e),e.parentNode.replaceChild(n,e)),n}var d=e("./apply-properties"),c=e("../vnode/is-widget.js"),p=e("../vnode/vpatch.js"),l=e("./create-element"),h=e("./update-widget");n.exports=t},{"../vnode/is-widget.js":29,"../vnode/vpatch.js":32,"./apply-properties":14,"./create-element":15,"./update-widget":19}],18:[function(e,n){function t(e,n){return r(e,n)}function r(e,n,t){var u=i(n);if(0===u.length)return e;var f=a(e,n.a,u),v=e.ownerDocument;t||(t={patch:r},v!==s&&(t.document=v));for(var d=0;d<u.length;d++){var c=u[d];e=o(e,f[c],n[c],t)}return e}function o(e,n,t,r){if(!n)return e;var o;if(u(t))for(var i=0;i<t.length;i++)o=f(t[i],n,r),n===e&&(e=o);else o=f(t,n,r),n===e&&(e=o);return e}function i(e){var n=[];for(var t in e)"a"!==t&&n.push(Number(t));return n}var s=e("global/document"),u=e("x-is-array"),a=e("./dom-index"),f=e("./patch-op");n.exports=t},{"./dom-index":16,"./patch-op":17,"global/document":10,"x-is-array":12}],19:[function(e,n){function t(e,n){return r(e)&&r(n)?"name"in e&&"name"in n?e.id===n.id:e.init===n.init:!1}var r=e("../vnode/is-widget.js");n.exports=t},{"../vnode/is-widget.js":29}],20:[function(e,n){"use strict";function t(e){return this instanceof t?void(this.value=e):new t(e)}var r=e("ev-store");n.exports=t,t.prototype.hook=function(e,n){var t=r(e),o=n.substr(3);t[o]=this.value},t.prototype.unhook=function(e,n){var t=r(e),o=n.substr(3);t[o]=void 0}},{"ev-store":7}],21:[function(e,n){"use strict";function t(e){return this instanceof t?void(this.value=e):new t(e)}n.exports=t,t.prototype.hook=function(e,n){e[n]!==this.value&&(e[n]=this.value)}},{}],22:[function(e,n){"use strict";function t(e,n,t){var i,u,a,f,d=[];return!t&&s(n)&&(t=n,u={}),u=u||n||{},i=g(e,u),u.hasOwnProperty("key")&&(a=u.key,u.key=void 0),u.hasOwnProperty("namespace")&&(f=u.namespace,u.namespace=void 0),"INPUT"!==i||f||!u.hasOwnProperty("value")||void 0===u.value||h(u.value)||(u.value=x(u.value)),o(u),void 0!==t&&null!==t&&r(t,d,i,u),new v(i,u,d,a,f)}function r(e,n,t,o){if("string"==typeof e)n.push(new d(e));else if(i(e))n.push(e);else{if(!f(e)){if(null===e||void 0===e)return;throw u({foreignObject:e,parentVnode:{tagName:t,properties:o}})}for(var s=0;s<e.length;s++)r(e[s],n,t,o)}}function o(e){for(var n in e)if(e.hasOwnProperty(n)){var t=e[n];if(h(t))continue;"ev-"===n.substr(0,3)&&(e[n]=w(t))}}function i(e){return c(e)||p(e)||l(e)||y(e)}function s(e){return"string"==typeof e||f(e)||i(e)}function u(e){var n=new Error;return n.type="virtual-hyperscript.unexpected.virtual-element",n.message="Unexpected virtual child passed to h().\nExpected a VNode / Vthunk / VWidget / string but:\ngot:\n"+a(e.foreignObject)+".\nThe parent vnode is:\n"+a(e.parentVnode),n.foreignObject=e.foreignObject,n.parentVnode=e.parentVnode,n}function a(e){try{return JSON.stringify(e,null," ")}catch(n){return String(e)}}var f=e("x-is-array"),v=e("../vnode/vnode.js"),d=e("../vnode/vtext.js"),c=e("../vnode/is-vnode"),p=e("../vnode/is-vtext"),l=e("../vnode/is-widget"),h=e("../vnode/is-vhook"),y=e("../vnode/is-thunk"),g=e("./parse-tag.js"),x=e("./hooks/soft-set-hook.js"),w=e("./hooks/ev-hook.js");n.exports=t},{"../vnode/is-thunk":25,"../vnode/is-vhook":26,"../vnode/is-vnode":27,"../vnode/is-vtext":28,"../vnode/is-widget":29,"../vnode/vnode.js":31,"../vnode/vtext.js":33,"./hooks/ev-hook.js":20,"./hooks/soft-set-hook.js":21,"./parse-tag.js":23,"x-is-array":12}],23:[function(e,n){"use strict";function t(e,n){if(!e)return"DIV";var t=!n.hasOwnProperty("id"),s=r(e,o),u=null;i.test(s[1])&&(u="DIV");var a,f,v,d;for(d=0;d<s.length;d++)f=s[d],f&&(v=f.charAt(0),u?"."===v?(a=a||[],a.push(f.substring(1,f.length))):"#"===v&&t&&(n.id=f.substring(1,f.length)):u=f);return a&&(n.className&&a.push(n.className),n.className=a.join(" ")),n.namespace?u:u.toUpperCase()}var r=e("browser-split"),o=/([\.#]?[a-zA-Z0-9_:-]+)/,i=/^\.|#/;n.exports=t},{"browser-split":5}],24:[function(e,n){function t(e,n){var t=e,o=n;return u(n)&&(o=r(n,e)),u(e)&&(t=r(e,null)),{a:t,b:o}}function r(e,n){var t=e.vnode;if(t||(t=e.vnode=e.render(n)),!(o(t)||i(t)||s(t)))throw new Error("thunk did not return a valid node");return t}var o=e("./is-vnode"),i=e("./is-vtext"),s=e("./is-widget"),u=e("./is-thunk");n.exports=t},{"./is-thunk":25,"./is-vnode":27,"./is-vtext":28,"./is-widget":29}],25:[function(e,n){function t(e){return e&&"Thunk"===e.type}n.exports=t},{}],26:[function(e,n){function t(e){return e&&("function"==typeof e.hook&&!e.hasOwnProperty("hook")||"function"==typeof e.unhook&&!e.hasOwnProperty("unhook"))}n.exports=t},{}],27:[function(e,n){function t(e){return e&&"VirtualNode"===e.type&&e.version===r}var r=e("./version");n.exports=t},{"./version":30}],28:[function(e,n){function t(e){return e&&"VirtualText"===e.type&&e.version===r}var r=e("./version");n.exports=t},{"./version":30}],29:[function(e,n){function t(e){return e&&"Widget"===e.type}n.exports=t},{}],30:[function(e,n){n.exports="1"},{}],31:[function(e,n){function t(e,n,t,r,v){this.tagName=e,this.properties=n||a,this.children=t||f,this.key=null!=r?String(r):void 0,this.namespace="string"==typeof v?v:null;var d,c=t&&t.length||0,p=0,l=!1,h=!1,y=!1;for(var g in n)if(n.hasOwnProperty(g)){var x=n[g];u(x)&&x.unhook&&(d||(d={}),d[g]=x)}for(var w=0;c>w;w++){var m=t[w];o(m)?(p+=m.count||0,!l&&m.hasWidgets&&(l=!0),!h&&m.hasThunks&&(h=!0),y||!m.hooks&&!m.descendantHooks||(y=!0)):!l&&i(m)?"function"==typeof m.destroy&&(l=!0):!h&&s(m)&&(h=!0)}this.count=c+p,this.hasWidgets=l,this.hasThunks=h,this.hooks=d,this.descendantHooks=y}var r=e("./version"),o=e("./is-vnode"),i=e("./is-widget"),s=e("./is-thunk"),u=e("./is-vhook");n.exports=t;var a={},f=[];t.prototype.version=r,t.prototype.type="VirtualNode"},{"./is-thunk":25,"./is-vhook":26,"./is-vnode":27,"./is-widget":29,"./version":30}],32:[function(e,n){function t(e,n,t){this.type=Number(e),this.vNode=n,this.patch=t}var r=e("./version");t.NONE=0,t.VTEXT=1,t.VNODE=2,t.WIDGET=3,t.PROPS=4,t.ORDER=5,t.INSERT=6,t.REMOVE=7,t.THUNK=8,n.exports=t,t.prototype.version=r,t.prototype.type="VirtualPatch"},{"./version":30}],33:[function(e,n){function t(e){this.text=String(e)}var r=e("./version");n.exports=t,t.prototype.version=r,t.prototype.type="VirtualText"},{"./version":30}],34:[function(e,n){function t(e,n){var s;for(var u in e){u in n||(s=s||{},s[u]=void 0);var a=e[u],f=n[u];if(a!==f)if(o(a)&&o(f))if(r(f)!==r(a))s=s||{},s[u]=f;else if(i(f))s=s||{},s[u]=f;else{var v=t(a,f);v&&(s=s||{},s[u]=v)}else s=s||{},s[u]=f}for(var d in n)d in e||(s=s||{},s[d]=n[d]);return s}function r(e){return Object.getPrototypeOf?Object.getPrototypeOf(e):e.__proto__?e.__proto__:e.constructor?e.constructor.prototype:void 0}var o=e("is-object"),i=e("../vnode/is-vhook");n.exports=t},{"../vnode/is-vhook":26,"is-object":11}],35:[function(e,n){function t(e,n){var t={a:e};return r(e,n,t,0),t}function r(e,n,t,r){if(e!==n){var s=t[r],a=!1;if(w(e)||w(n))u(e,n,t,r);else if(null==n)x(e)||(i(e,t,r),s=t[r]),s=p(s,new h(h.REMOVE,e,n));else if(y(n))if(y(e))if(e.tagName===n.tagName&&e.namespace===n.namespace&&e.key===n.key){var f=j(e.properties,n.properties);f&&(s=p(s,new h(h.PROPS,e,f))),s=o(e,n,t,s,r)}else s=p(s,new h(h.VNODE,e,n)),a=!0;else s=p(s,new h(h.VNODE,e,n)),a=!0;else g(n)?g(e)?e.text!==n.text&&(s=p(s,new h(h.VTEXT,e,n))):(s=p(s,new h(h.VTEXT,e,n)),a=!0):x(n)&&(x(e)||(a=!0),s=p(s,new h(h.WIDGET,e,n)));s&&(t[r]=s),a&&i(e,t,r)}}function o(e,n,t,o,i){for(var s=e.children,u=d(s,n.children),a=s.length,f=u.length,v=a>f?a:f,c=0;v>c;c++){var l=s[c],g=u[c];i+=1,l?r(l,g,t,i):g&&(o=p(o,new h(h.INSERT,null,g))),y(l)&&l.count&&(i+=l.count)}return u.moves&&(o=p(o,new h(h.ORDER,e,u.moves))),o}function i(e,n,t){f(e,n,t),s(e,n,t)}function s(e,n,t){if(x(e))"function"==typeof e.destroy&&(n[t]=p(n[t],new h(h.REMOVE,e,null)));else if(y(e)&&(e.hasWidgets||e.hasThunks))for(var r=e.children,o=r.length,i=0;o>i;i++){var a=r[i];t+=1,s(a,n,t),y(a)&&a.count&&(t+=a.count)}else w(e)&&u(e,null,n,t)}function u(e,n,r,o){var i=m(e,n),s=t(i.a,i.b);a(s)&&(r[o]=new h(h.THUNK,null,s))}function a(e){for(var n in e)if("a"!==n)return!0;return!1}function f(e,n,t){if(y(e)){if(e.hooks&&(n[t]=p(n[t],new h(h.PROPS,e,v(e.hooks)))),e.descendantHooks||e.hasThunks)for(var r=e.children,o=r.length,i=0;o>i;i++){var s=r[i];t+=1,f(s,n,t),y(s)&&s.count&&(t+=s.count)}}else w(e)&&u(e,null,n,t)}function v(e){var n={};for(var t in e)n[t]=void 0;return n}function d(e,n){var t=c(n);if(!t)return n;var r=c(e);if(!r)return n;var o={},i={};for(var s in t)o[t[s]]=r[s];for(var u in r)i[r[u]]=t[u];for(var a=e.length,f=n.length,v=a>f?a:f,d=[],p=0,l=0,h=0,y={},g=y.removes={},x=y.reverse={},w=!1;v>p;){var m=i[l];if(void 0!==m)d[l]=n[m],m!==h&&(y[m]=h,x[h]=m,w=!0),h++;else if(l in i)d[l]=void 0,g[l]=h++,w=!0;else{for(;void 0!==o[p];)p++;if(v>p){var j=n[p];j&&(d[l]=j,p!==h&&(w=!0,y[p]=h,x[h]=p),h++),p++}}l++}return w&&(d.moves=y),d}function c(e){var n,t;for(n=0;n<e.length;n++){var r=e[n];void 0!==r.key&&(t=t||{},t[r.key]=n)}return t}function p(e,n){return e?(l(e)?e.push(n):e=[e,n],e):n}var l=e("x-is-array"),h=e("../vnode/vpatch"),y=e("../vnode/is-vnode"),g=e("../vnode/is-vtext"),x=e("../vnode/is-widget"),w=e("../vnode/is-thunk"),m=e("../vnode/handle-thunk"),j=e("./diff-props");n.exports=t},{"../vnode/handle-thunk":24,"../vnode/is-thunk":25,"../vnode/is-vnode":27,"../vnode/is-vtext":28,"../vnode/is-widget":29,"../vnode/vpatch":32,"./diff-props":34,"x-is-array":12}]},{},[4])(4)});
if (window.require && !window.virtualDom) {
require(['virtual-dom-1.3.0'], function(virtualDom) { window.virtualDom = virtualDom; });
}
(function(h) {
var FocusHook = function() {
if (!(this instanceof FocusHook)) {
return new FocusHook();
}
};
FocusHook.prototype.hook = function(node, prop, prev) {
setTimeout(function() {
if (document.activeElement !== node) {
node.focus();
}
}, 0);
};
var px = function(n) {
return n + 'px';
};
var renderCollection = function(template, collection, ctx) {
return collection.map(function(item) {
return template(item, ctx);
});
};
var itemInputTemplate = function(input) {
if (!input) {
return null;
}
var focus_hook = input.has_value ? undefined : FocusHook();
return (
h('div.numerical-input', {className: input.class_name,
style: {display: input.is_visible ? 'block' : 'none'}}, [
h('input.input', {type: 'text', value: input.value, disabled: input.has_value,
focusHook: focus_hook}),
h('button.submit-input', {disabled: input.has_value}, 'ok')
])
);
};
var itemTemplate = function(item) {
return (
h('div.option', {className: item.class_name,
attributes: {'data-value': item.value, 'data-drag-disabled': item.drag_disabled},
style: {width: item.width, height: item.height,
top: item.top, left: item.left, position: item.position}}, [
h('div', {innerHTML: item.content_html}),
itemInputTemplate(item.input)
])
);
};
var zoneTemplate = function(zone, ctx) {
return (
h('div.zone', {id: zone.id, attributes: {'data-zone': zone.title},
style: {top: px(zone.y), left: px(zone.x),
width: px(zone.width), height: px(zone.height)}},
h('p', {style: {visibility: ctx.display_zone_labels ? 'visible': 'hidden'}}, zone.title))
);
};
var feedbackTemplate = function(ctx) {
var feedback_display = ctx.feedback_html ? 'block' : 'none';
var reset_button_display = ctx.display_reset_button ? 'block' : 'none';
return (
h('section.feedback', [
h('div.reset-button', {style: {display: reset_button_display}}, 'Reset exercise'),
h('div.title1', {style: {display: feedback_display}}, 'Feedback'),
h('p.message', {style: {display: feedback_display},
innerHTML: ctx.feedback_html})
])
);
};
var mainTemplate = function(ctx) {
return (
h('section.xblock--drag-and-drop', [
h('h2.problem-header', {innerHTML: ctx.header_html}),
h('section.problem', {role: 'application'}, [
h('div.title1', 'Question'),
h('p', {innerHTML: ctx.question_html})
]),
h('section.drag-container', [
h('div.items', renderCollection(itemTemplate, ctx.items, ctx)),
h('div.target', [
h('div.popup', {style: {display: ctx.popup_html ? 'block' : 'none'}}, [
h('div.close.icon-remove-sign.fa-times-circle'),
h('p.popup-content', {innerHTML: ctx.popup_html})
]),
h('div.target-img', {style: {backgroundImage: ctx.target_img_src}},
renderCollection(zoneTemplate, ctx.zones, ctx))
]),
h('div.clear')
]),
feedbackTemplate(ctx)
])
);
};
DragAndDropBlock.renderView = mainTemplate;
})(virtualDom.h);
<section class="xblock--drag-and-drop">
{{ js_templates|safe }}
<h2 class="problem-header">
{{ title|safe }}
</h2>
<section class="problem" role="application">
<div class="title1">Question</div>
<p>{{ question_text|safe }}</p>
</section>
<section class="drag-container">
<ul class="items"></ul>
<div class="target">
<div class="popup">
<div class="close icon-remove-sign fa-times-circle"></div>
<p class="popup-content"></p>
</div>
<div class="target-img"></div>
</div>
<div class="clear"></div>
</section>
<section class="feedback">
<div class="reset-button">Reset exercise</div>
<div class="title1">Feedback</div>
<p class="message"></p>
</section>
</section>
<section class="xblock--drag-and-drop"></section>
<script id="item-tpl" type="text/html">
<li class="option" data-value="{{ id }}"
style="width: {{ size.width }}; height: {{ size.height }}">
{{{ displayName }}}
{{#if inputOptions}}
<div class="numerical-input">
<input class="input" type="text" />
<button class="submit-input">ok</button>
</div>
{{/if}}
</li>
</script>
<script id="image-item-tpl" type="text/html">
<li class="option" data-value="{{ id }}"
style="width: {{ size.width }}; height: {{ size.height }}">
<img src="{{ backgroundImage }}" />
{{#if inputOptions}}
<input class="input" type="text" />
<button class="submit-input">ok</button>
{{/if}}
</li>
</script>
<script id="zone-element-tpl" type="text/html">
<div id="{{ id }}" class="zone" data-zone="{{ title }}" style="
top:{{ y }}px;
......
......@@ -3,17 +3,11 @@
# Imports ###########################################################
import logging
import pkg_resources
from django.template import Context, Template
# Globals ###########################################################
log = logging.getLogger(__name__)
# Functions #########################################################
def load_resource(resource_path):
......@@ -23,11 +17,13 @@ def load_resource(resource_path):
resource_content = pkg_resources.resource_string(__name__, resource_path)
return resource_content
def render_template(template_path, context={}):
def render_template(template_path, context=None):
"""
Evaluate a template by resource path, applying the provided context
"""
if context is None:
context = {}
template_str = load_resource(template_path)
template = Template(template_str)
return template.render(Context(context))
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