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": [ "zones": [
{ {
"index": 1, "index": 1,
......
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
# Imports ########################################################### # Imports ###########################################################
import logging
import json import json
import webob import webob
import copy import copy
...@@ -14,12 +13,7 @@ from xblock.fields import Scope, String, Dict, Float, Boolean ...@@ -14,12 +13,7 @@ from xblock.fields import Scope, String, Dict, Float, Boolean
from xblock.fragment import Fragment from xblock.fragment import Fragment
from .utils import render_template, load_resource from .utils import render_template, load_resource
from .default_data import default_data from .default_data import DEFAULT_DATA
# Globals ###########################################################
log = logging.getLogger(__name__)
# Classes ########################################################### # Classes ###########################################################
...@@ -53,7 +47,7 @@ class DragAndDropBlock(XBlock): ...@@ -53,7 +47,7 @@ class DragAndDropBlock(XBlock):
display_name="Drag and Drop", display_name="Drag and Drop",
help="JSON spec as generated by the builder", help="JSON spec as generated by the builder",
scope=Scope.content, scope=Scope.content,
default=default_data default=DEFAULT_DATA
) )
item_state = Dict( item_state = Dict(
...@@ -75,30 +69,22 @@ class DragAndDropBlock(XBlock): ...@@ -75,30 +69,22 @@ class DragAndDropBlock(XBlock):
Player view, displayed to the student 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 = Fragment()
fragment.add_content(render_template('/templates/html/drag_and_drop.html', context)) fragment.add_content(render_template('/templates/html/drag_and_drop.html'))
CSS_URLS = ( css_urls = (
'public/css/vendor/jquery-ui-1.10.4.custom.min.css', 'public/css/vendor/jquery-ui-1.10.4.custom.min.css',
'public/css/drag_and_drop.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-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-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/virtual-dom-1.3.0.min.js',
'public/js/vendor/handlebars-v1.1.2.js',
'public/js/drag_and_drop.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)) 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.add_javascript_url(self.runtime.local_resource_url(self, js_url))
fragment.initialize_js('DragAndDropBlock') fragment.initialize_js('DragAndDropBlock')
...@@ -119,18 +105,21 @@ class DragAndDropBlock(XBlock): ...@@ -119,18 +105,21 @@ class DragAndDropBlock(XBlock):
fragment = Fragment() fragment = Fragment()
fragment.add_content(render_template('/templates/html/drag_and_drop_edit.html', context)) 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')) css_urls = (
fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/vendor/jquery-ui-1.10.4.custom.min.css',
'public/css/drag_and_drop_edit.css')) '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')) js_urls = (
fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/vendor/jquery-ui-1.10.4.custom.min.js',
'public/js/vendor/jquery.html5-placeholder-shim.js')) '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',
'public/js/vendor/handlebars-v1.1.2.js')) 'public/js/drag_and_drop_edit.js',
fragment.add_javascript_url(self.runtime.local_resource_url(self, )
'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') fragment.initialize_js('DragAndDropEditBlock')
...@@ -149,33 +138,12 @@ class DragAndDropBlock(XBlock): ...@@ -149,33 +138,12 @@ class DragAndDropBlock(XBlock):
@XBlock.handler @XBlock.handler
def get_data(self, request, suffix=''): def get_data(self, request, suffix=''):
data = copy.deepcopy(self.data) data = self._get_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()
}
return webob.response.Response(body=json.dumps(data)) return webob.response.Response(body=json.dumps(data))
@XBlock.json_handler @XBlock.json_handler
def do_attempt(self, attempt, suffix=''): def do_attempt(self, attempt, suffix=''):
item = next(i for i in self.data['items'] if i['id'] == attempt['val']) 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 state = None
feedback = item['feedback']['incorrect'] feedback = item['feedback']['incorrect']
...@@ -195,7 +163,7 @@ class DragAndDropBlock(XBlock): ...@@ -195,7 +163,7 @@ class DragAndDropBlock(XBlock):
is_correct = False is_correct = False
elif item['zone'] == attempt['zone']: elif item['zone'] == attempt['zone']:
is_correct_location = True is_correct_location = True
if item.has_key('inputOptions'): if 'inputOptions' in item:
# Input value will have to be provided for the item. # Input value will have to be provided for the item.
# It is not (yet) correct and no feedback should be shown yet. # It is not (yet) correct and no feedback should be shown yet.
is_correct = False is_correct = False
...@@ -204,7 +172,11 @@ class DragAndDropBlock(XBlock): ...@@ -204,7 +172,11 @@ class DragAndDropBlock(XBlock):
# If this item has no input value set, we are done with it. # If this item has no input value set, we are done with it.
is_correct = True is_correct = True
feedback = item['feedback']['correct'] 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: if state:
self.item_state[str(item['id'])] = state self.item_state[str(item['id'])] = state
...@@ -247,7 +219,34 @@ class DragAndDropBlock(XBlock): ...@@ -247,7 +219,34 @@ class DragAndDropBlock(XBlock):
@XBlock.json_handler @XBlock.json_handler
def reset(self, data, suffix=''): def reset(self, data, suffix=''):
self.item_state = {} 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): def _get_item_state(self):
""" """
...@@ -264,24 +263,6 @@ class DragAndDropBlock(XBlock): ...@@ -264,24 +263,6 @@ class DragAndDropBlock(XBlock):
return state 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): def _get_grade(self):
""" """
Returns the student's grade for this block. Returns the student's grade for this block.
...@@ -313,8 +294,8 @@ class DragAndDropBlock(XBlock): ...@@ -313,8 +294,8 @@ class DragAndDropBlock(XBlock):
total_count += 1 total_count += 1
item_id = str(item['id']) item_id = str(item['id'])
if item_id in item_state: if item_id in item_state:
if item.has_key('inputOptions'): if 'inputOptions' in item:
if item_state[item_id].has_key('input'): if 'input' in item_state[item_id]:
completed_count += 1 completed_count += 1
else: else:
completed_count += 1 completed_count += 1
...@@ -325,24 +306,43 @@ class DragAndDropBlock(XBlock): ...@@ -325,24 +306,43 @@ class DragAndDropBlock(XBlock):
def publish_event(self, data, suffix=''): def publish_event(self, data, suffix=''):
try: try:
event_type = data.pop('event_type') event_type = data.pop('event_type')
except KeyError as e: except KeyError:
return {'result': 'error', 'message': 'Missing event_type in JSON data'} return {'result': 'error', 'message': 'Missing event_type in JSON data'}
data['user_id'] = self.scope_ids.user_id data['user_id'] = self.scope_ids.user_id
data['component_id'] = self._get_unique_id() data['component_id'] = self._get_unique_id()
self.runtime.publish(self, event_type, data) self.runtime.publish(self, event_type, data)
return {'result':'success'} return {'result': 'success'}
def _get_unique_id(self): def _get_unique_id(self):
try: try:
unique_id = self.location.name unique_id = self.location.name # pylint: disable=no-member
except AttributeError: except AttributeError:
# workaround for xblock workbench # workaround for xblock workbench
unique_id = self.parent and self.parent.replace('.', '-') unique_id = self.parent and self.parent.replace('.', '-')
return unique_id return unique_id
@staticmethod @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(): def workbench_scenarios():
""" """
A canned scenario for display in the workbench. A canned scenario for display in the workbench.
......
...@@ -47,15 +47,15 @@ ...@@ -47,15 +47,15 @@
width: 210px; width: 210px;
margin: 10px; margin: 10px;
padding: 0 !important; /* LMS tries to override this */ padding: 0 !important; /* LMS tries to override this */
font-size: 14px;
position: relative; position: relative;
display: inline; display: inline;
float: left; float: left;
list-style-type: none; list-style-type: none;
} }
.xblock--drag-and-drop .drag-container .items .option { .xblock--drag-and-drop .drag-container .option {
width: 190px; width: 190px;
font-size: 14px;
background: #2e83cd; background: #2e83cd;
color: #fff; color: #fff;
position: relative; position: relative;
...@@ -68,28 +68,23 @@ ...@@ -68,28 +68,23 @@
opacity: 1; opacity: 1;
} }
.xblock--drag-and-drop .drag-container .items .option img { .xblock--drag-and-drop .drag-container .option img {
max-width: 100%; max-width: 100%;
} }
.xblock--drag-and-drop .drag-container .items .option .numerical-input { .xblock--drag-and-drop .drag-container .option .numerical-input {
display: none;
height: 32px; height: 32px;
position: absolute; position: absolute;
left: calc(100% + 5px); left: calc(100% + 5px);
top: calc(50% - 16px); top: calc(50% - 16px);
} }
.xblock--drag-and-drop .drag-container .items .option.within-dropzone .numerical-input { .xblock--drag-and-drop .drag-container .option .numerical-input .input {
display: block;
}
.xblock--drag-and-drop .drag-container .items .option.within-dropzone .numerical-input .input {
display: inline-block; display: inline-block;
width: 144px; 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; box-sizing: border-box;
position: absolute; position: absolute;
left: 150px; left: 150px;
...@@ -97,22 +92,22 @@ ...@@ -97,22 +92,22 @@
height: 24px; height: 24px;
} }
.xblock--drag-and-drop .drag-container .items .option.within-dropzone .numerical-input.correct .input-submit, .xblock--drag-and-drop .drag-container .option .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.incorrect .input-submit {
display: none; 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; background: #ceffce;
color: #0dad0d; 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; background: #ffcece;
color: #ad0d0d; color: #ad0d0d;
} }
.xblock--drag-and-drop .drag-container .items .option.fade { .xblock--drag-and-drop .drag-container .option.fade {
opacity: 0.5; opacity: 0.5;
} }
...@@ -129,7 +124,6 @@ ...@@ -129,7 +124,6 @@
} }
.xblock--drag-and-drop .target-img { .xblock--drag-and-drop .target-img {
display: none;
background: url('../img/triangle.png') no-repeat; background: url('../img/triangle.png') no-repeat;
width: 100%; width: 100%;
height: 100%; height: 100%;
...@@ -229,5 +223,4 @@ ...@@ -229,5 +223,4 @@
float: right; float: right;
color: #3384CA; color: #3384CA;
margin-top: 3px; margin-top: 3px;
display: none;
} }
function DragAndDropBlock(runtime, element) { function DragAndDropBlock(runtime, element) {
function publish_event(data) { var root = $(element).find('.xblock--drag-and-drop')[0];
$.ajax({
type: "POST", var __state;
url: runtime.handlerUrl(element, 'publish_event'), var __vdom = virtualDom.h(); // blank virtual DOM
data: JSON.stringify(data)
}); var init = function() {
} $.ajax(runtime.handlerUrl(element, 'get_data'), {
dataType: 'json'
var dragAndDrop = (function($) { }).done(function(data){
var _fn = { setState(data);
pupup_ts: Date.now(), initDroppable();
});
// DOM Elements
$ul: $('.xblock--drag-and-drop .items', element), $(document).on('mousedown touchstart', closePopup);
$target: $('.xblock--drag-and-drop .target-img', element), $(element).on('click', '.reset-button', resetExercise);
$feedback: $('.xblock--drag-and-drop .feedback .message', element), $(element).on('click', '.submit-input', submitInput);
$popup: $('.xblock--drag-and-drop .popup', element),
$reset_button: $('.xblock--drag-and-drop .reset-button', element), publishEvent({event_type: 'xblock.drag-and-drop-v2.loaded'});
};
// Cannot set until items added to DOM
$items: {}, // $('.xblock--drag-and-drop .items .option'), var getState = function() {
$zones: {}, // $('.xblock--drag-and-drop .target .zone'), return __state;
};
// jQuery UI Draggable options
options: { var setState = function(new_state) {
drag: { 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', containment: '.xblock--drag-and-drop .drag-container',
cursor: 'move', cursor: 'move',
stack: '.xblock--drag-and-drop .items .option' stack: '.xblock--drag-and-drop .items .option',
}, revert: 'invalid',
drop: { revertDuration: 150,
accept: '.xblock--drag-and-drop .items .option', start: function(evt, ui) {
tolerance: 'pointer' var item_id = $(this).data('value');
} publishEvent({
}, event_type: 'xblock.drag-and-drop-v2.item.picked-up',
item_id: item_id
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);
}
}); });
},
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: { } catch (e) {
hover: function(event, ui) { // Initializing the draggable will fail if draggable was already
var zone = $(event.currentTarget).data('zone'); // initialized. That's expected, ignore the exception.
ui.draggable.data('zone', zone); }
}, });
success: function(event, ui) { };
ui.draggable.addClass('within-dropzone');
var item = _fn.data.items[ui.draggable.data('value')]; var destroyDraggable = function() {
if (item.inputOptions) { $(root).find('.items .option[data-drag-disabled=true]').each(function() {
ui.draggable.find('.input').focus(); try {
} $(this).draggable('destroy');
} } catch (e) {
} // Destroying the draggable will fail if draggable was
}, // not initialized in the first place. Ignore the exception.
}
items: { });
init: function() { };
_fn.$items.each(function (){
var $el = $(this), var submitLocation = function(item_id, zone, top, left) {
saved_entry = _fn.data.state.items[$el.data('value')]; if (!zone) {
if (saved_entry) { return;
var $input_div = $el.find('.numerical-input') }
var $input = $input_div.find('.input'); var url = runtime.handlerUrl(element, 'do_attempt');
$input.val(saved_entry.input); var data = {val: item_id, zone: zone, top: top, left: left};
console.log('wuwuwu', saved_entry) $.post(url, JSON.stringify(data), 'json').done(function(data){
if ('input' in saved_entry) { var state = getState();
$input_div.addClass(saved_entry.correct_input ? 'correct' : 'incorrect'); if (data.correct_location) {
$input.prop('disabled', true); state.state.items[item_id].correct_input = Boolean(data.correct);
$input_div.find('.submit-input').prop('disabled', true); state.state.items[item_id].submitting_location = false;
} } else {
if ('input' in saved_entry || saved_entry.correct_input) { delete state.state.items[item_id];
$el.addClass('fade'); }
} state.state.feedback = data.feedback;
_fn.eventHandlers.drag.set($el, saved_entry.top, saved_entry.left); if (data.finished) {
} state.state.finished = true;
}); state.feedback.finish = data.final_feedback;
}, }
draw: function() { setState(state);
var list = [], });
items = _fn.data.items, };
tpl = _fn.tpl.item,
img_tpl = _fn.tpl.imageItem; var submitInput = function(evt) {
var item = $(evt.target).closest('.option');
items.forEach(function(item) { var input_div = item.find('.numerical-input');
if (item.backgroundImage.length > 0) { var input = input_div.find('.input');
list.push(img_tpl(item)); var input_value = input.val();
} else { var item_id = item.data('value');
list.push(tpl(item));
} if (!input_value) {
}); // Don't submit if the user didn't enter anything yet.
return;
// Update DOM }
_fn.$ul.html(list.join(''));
var state = getState();
// Set variable state.state.items[item_id].input = input_value;
_fn.$items = $('.xblock--drag-and-drop .items .option', element); state.state.items[item_id].submitting_input = true;
} setState(state);
},
var url = runtime.handlerUrl(element, 'do_attempt');
zones: { var data = {val: item_id, input: input_value};
draw: function() { $.post(url, JSON.stringify(data), 'json').done(function(data) {
var html = [], state.state.items[item_id].submitting_input = false;
zones = _fn.data.zones, state.state.items[item_id].correct_input = data.correct;
tpl = _fn.tpl.zoneElement, state.state.feedback = data.feedback;
i, if (data.finished) {
len = zones.length; state.state.finished = true;
state.feedback.finish = data.final_feedback;
for (i=0; i<len; i++) { }
html.push(tpl(zones[i])); setState(state);
} });
};
// Update DOM
_fn.$target.html(html.join('')); var closePopup = function(evt) {
var target = $(evt.target);
// Set variable var popup_box = '.xblock--drag-and-drop .popup';
_fn.$zones = _fn.$target.find('.zone'); var close_button = '.xblock--drag-and-drop .popup .close';
} var submit_input_button = '.xblock--drag-and-drop .submit-input';
}, var state = getState();
feedback: { if (!state.state.feedback) {
// Update DOM with feedback return;
set: function(str) { }
if ($.trim(str) === '') _fn.$feedback.parent().hide(); if (target.is(popup_box) || target.is(submit_input_button)) {
else _fn.$feedback.parent().show(); return;
return _fn.$feedback.html(str); }
}, if (target.parents(popup_box).length && !target.is(close_button)) {
return;
// Show a feedback popup }
popup: function(str, boo) {
if (str === undefined || str === '') return; publishEvent({
event_type: 'xblock.drag-and-drop-v2.feedback.closed',
if (_fn.$popup.is(":visible")) { content: state.state.feedback,
publish_event({ manually: true
event_type: "xblock.drag-and-drop-v2.feedback.closed", });
content: _fn.$popup.find(".popup-content").text(),
manually: false delete state.state.feedback;
}); setState(state);
} };
publish_event({
event_type: "xblock.drag-and-drop-v2.feedback.opened", var resetExercise = function() {
content: str $.ajax({
}); type: 'POST',
url: runtime.handlerUrl(element, 'reset'),
_fn.$popup.find(".popup-content").html(str); data: '{}',
_fn.$popup.show(); success: setState
});
_fn.popup_ts = Date.now(); };
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';
} }
}, }
return {
data: null value: item.id,
}; drag_disabled: Boolean(item_state || state.state.finished),
width: item.size.width,
return { height: item.size.height,
init: _fn.init, 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'), { return DragAndDropBlock.renderView(context);
dataType: 'json' };
}).done(function(data){
dragAndDrop.init(data);
});
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"> <section class="xblock--drag-and-drop"></section>
{{ 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>
<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"> <script id="zone-element-tpl" type="text/html">
<div id="{{ id }}" class="zone" data-zone="{{ title }}" style=" <div id="{{ id }}" class="zone" data-zone="{{ title }}" style="
top:{{ y }}px; top:{{ y }}px;
......
...@@ -3,17 +3,11 @@ ...@@ -3,17 +3,11 @@
# Imports ########################################################### # Imports ###########################################################
import logging
import pkg_resources import pkg_resources
from django.template import Context, Template from django.template import Context, Template
# Globals ###########################################################
log = logging.getLogger(__name__)
# Functions ######################################################### # Functions #########################################################
def load_resource(resource_path): def load_resource(resource_path):
...@@ -23,11 +17,13 @@ def load_resource(resource_path): ...@@ -23,11 +17,13 @@ def load_resource(resource_path):
resource_content = pkg_resources.resource_string(__name__, resource_path) resource_content = pkg_resources.resource_string(__name__, resource_path)
return resource_content 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 Evaluate a template by resource path, applying the provided context
""" """
if context is None:
context = {}
template_str = load_resource(template_path) template_str = load_resource(template_path)
template = Template(template_str) template = Template(template_str)
return template.render(Context(context)) 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