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,
......
...@@ -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(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