Commit 260d00f3 by Filippo Valsorda

Support incremental editing in the Studio view

parent 9b1c33f0
...@@ -7,6 +7,7 @@ import logging ...@@ -7,6 +7,7 @@ import logging
import json import json
import webob import webob
import copy import copy
import urllib
from xblock.core import XBlock from xblock.core import XBlock
from xblock.fields import Scope, String, Dict, Float from xblock.fields import Scope, String, Dict, Float
...@@ -111,6 +112,8 @@ class DragAndDropBlock(XBlock): ...@@ -111,6 +112,8 @@ class DragAndDropBlock(XBlock):
js_templates = load_resource('/templates/html/js_templates.html') js_templates = load_resource('/templates/html/js_templates.html')
context = { context = {
'js_templates': js_templates, 'js_templates': js_templates,
'self': self,
'data': urllib.quote(json.dumps(self.data)),
} }
fragment = Fragment() fragment = Fragment()
......
...@@ -174,8 +174,6 @@ ...@@ -174,8 +174,6 @@
} }
.xblock--drag-and-drop .drag-builder .zone { .xblock--drag-and-drop .drag-builder .zone {
width: 200px;
height: 100px;
border: 1px dotted #666; border: 1px dotted #666;
} }
......
...@@ -39,7 +39,6 @@ function DragAndDropEditBlock(runtime, element) { ...@@ -39,7 +39,6 @@ function DragAndDropEditBlock(runtime, element) {
_fn.tpl.init(); _fn.tpl.init();
_fn.build.clickHandlers(); _fn.build.clickHandlers();
_fn.build.form.zone.add();
}, },
clickHandlers: function() { clickHandlers: function() {
var $fbkTab = _fn.build.$el.feedback.tab, var $fbkTab = _fn.build.$el.feedback.tab,
...@@ -47,8 +46,20 @@ function DragAndDropEditBlock(runtime, element) { ...@@ -47,8 +46,20 @@ function DragAndDropEditBlock(runtime, element) {
$itemTab = _fn.build.$el.items.tab; $itemTab = _fn.build.$el.items.tab;
$(element).one('click', '.continue-button', function(e) { $(element).one('click', '.continue-button', function(e) {
// $fbkTab -> $zoneTab
e.preventDefault(); e.preventDefault();
_fn.build.form.feedback(_fn.build.$el.feedback.form); _fn.build.form.feedback(_fn.build.$el.feedback.form);
for (var i = 0; i < _fn.data.zones.length; i++) {
_fn.build.form.zone.add(_fn.data.zones[i]);
}
if (_fn.data.zones.length === 0) {
_fn.build.form.zone.add();
}
if (_fn.data.targetImg) {
_fn.$target.css('background', 'url(' + _fn.data.targetImg + ') no-repeat');
}
$fbkTab.addClass('hidden'); $fbkTab.addClass('hidden');
$zoneTab.removeClass('hidden'); $zoneTab.removeClass('hidden');
...@@ -57,9 +68,16 @@ function DragAndDropEditBlock(runtime, element) { ...@@ -57,9 +68,16 @@ function DragAndDropEditBlock(runtime, element) {
$.placeholder.shim(); $.placeholder.shim();
$(this).one('click', function(e) { $(this).one('click', function(e) {
// $zoneTab -> $itemTab
e.preventDefault(); e.preventDefault();
_fn.build.form.zone.setAll(); _fn.build.form.zone.setAll();
_fn.build.form.item.add(); for (var i = 0; i < _fn.data.items.length; i++) {
_fn.build.form.item.add(_fn.data.items[i]);
}
if (_fn.data.items.length === 0) {
_fn.build.form.item.add();
}
$zoneTab.addClass('hidden'); $zoneTab.addClass('hidden');
$itemTab.removeClass('hidden'); $itemTab.removeClass('hidden');
...@@ -71,6 +89,8 @@ function DragAndDropEditBlock(runtime, element) { ...@@ -71,6 +89,8 @@ function DragAndDropEditBlock(runtime, element) {
$('.save-button', element).parent() $('.save-button', element).parent()
.removeClass('hidden') .removeClass('hidden')
.one('click', function(e) { .one('click', function(e) {
// $itemTab -> submit
e.preventDefault(); e.preventDefault();
_fn.build.form.submit(); _fn.build.form.submit();
}); });
...@@ -78,7 +98,9 @@ function DragAndDropEditBlock(runtime, element) { ...@@ -78,7 +98,9 @@ function DragAndDropEditBlock(runtime, element) {
}); });
$zoneTab $zoneTab
.on('click', '.add-zone', _fn.build.form.zone.add) .on('click', '.add-zone', function(e) {
_fn.build.form.zone.add();
})
.on('click', '.remove-zone', _fn.build.form.zone.remove) .on('click', '.remove-zone', _fn.build.form.zone.remove)
.on('click', '.target-image-form button', function(e) { .on('click', '.target-image-form button', function(e) {
e.preventDefault(); e.preventDefault();
...@@ -91,14 +113,15 @@ function DragAndDropEditBlock(runtime, element) { ...@@ -91,14 +113,15 @@ function DragAndDropEditBlock(runtime, element) {
}); });
$itemTab $itemTab
.on('click', '.add-item', _fn.build.form.item.add) .on('click', '.add-item', function(e) {
_fn.build.form.item.add();
})
.on('click', '.remove-item', _fn.build.form.item.remove); .on('click', '.remove-item', _fn.build.form.item.remove);
}, },
form: { form: {
zone: { zone: {
count: 0, count: 0,
formCount: 0, formCount: 0,
dropdown: '',
list: [], list: [],
obj: [], obj: [],
getObjByIndex: function(num) { getObjByIndex: function(num) {
...@@ -107,7 +130,7 @@ function DragAndDropEditBlock(runtime, element) { ...@@ -107,7 +130,7 @@ function DragAndDropEditBlock(runtime, element) {
return _fn.build.form.zone.obj[i]; return _fn.build.form.zone.obj[i];
} }
}, },
add: function(e) { add: function(oldZone) {
var inputTemplate = _fn.tpl.zoneInput, var inputTemplate = _fn.tpl.zoneInput,
zoneTemplate = _fn.tpl.zoneElement, zoneTemplate = _fn.tpl.zoneElement,
name = 'zone-', name = 'zone-',
...@@ -115,9 +138,7 @@ function DragAndDropEditBlock(runtime, element) { ...@@ -115,9 +138,7 @@ function DragAndDropEditBlock(runtime, element) {
num, num,
obj; obj;
if (e) { if (!oldZone) oldZone = {};
e.preventDefault();
}
_fn.build.form.zone.count++; _fn.build.form.zone.count++;
_fn.build.form.zone.formCount++; _fn.build.form.zone.formCount++;
...@@ -126,23 +147,19 @@ function DragAndDropEditBlock(runtime, element) { ...@@ -126,23 +147,19 @@ function DragAndDropEditBlock(runtime, element) {
// Update zone obj // Update zone obj
zoneObj = { zoneObj = {
title: 'Zone ' + num, title: oldZone.title || 'Zone ' + num,
id: name, id: name,
active: false,
index: num, index: num,
width: 200, width: oldZone.width || 200,
height: 100, height: oldZone.height || 100,
x: 0, x: oldZone.x || 0,
y: 0 y: oldZone.y || 0
}; };
_fn.build.form.zone.obj.push(zoneObj); _fn.build.form.zone.obj.push(zoneObj);
// Add fields to zone position form // Add fields to zone position form
$elements.zones.form.append(inputTemplate({ $elements.zones.form.append(inputTemplate(zoneObj));
title: 'Zone ' + num,
name: name
}));
_fn.build.form.zone.enableDelete(); _fn.build.form.zone.enableDelete();
// Add zone div to target // Add zone div to target
...@@ -192,7 +209,6 @@ function DragAndDropEditBlock(runtime, element) { ...@@ -192,7 +209,6 @@ function DragAndDropEditBlock(runtime, element) {
}); });
_fn.build.form.zone.list = zones; _fn.build.form.zone.list = zones;
_fn.build.form.createDropdown(zones);
}, },
clickHandler: function(num) { clickHandler: function(num) {
var $div = $('#zone-' + num, element), var $div = $('#zone-' + num, element),
...@@ -205,10 +221,6 @@ function DragAndDropEditBlock(runtime, element) { ...@@ -205,10 +221,6 @@ function DragAndDropEditBlock(runtime, element) {
$div.find('p').html(text); $div.find('p').html(text);
record.title = text; record.title = text;
if (!record.active) {
record.active = true;
}
}).on('keyup', '.width', function(e) { }).on('keyup', '.width', function(e) {
var width = $(e.currentTarget).val(), var width = $(e.currentTarget).val(),
record = _fn.build.form.zone.getObjByIndex(num); record = _fn.build.form.zone.getObjByIndex(num);
...@@ -241,31 +253,31 @@ function DragAndDropEditBlock(runtime, element) { ...@@ -241,31 +253,31 @@ function DragAndDropEditBlock(runtime, element) {
len = arr.length; len = arr.length;
for (i=0; i<len; i++) { for (i=0; i<len; i++) {
if (arr[i].active) { clean.push(arr[i]);
clean.push(arr[i]);
}
} }
return clean; return clean;
} }
}, },
createDropdown: function(arr) { createDropdown: function(selected) {
var tpl = _fn.tpl.zoneDropdown, var tpl = _fn.tpl.zoneDropdown,
i, i,
len = arr.length, is_sel,
arr = _fn.build.form.zone.list,
dropdown = [], dropdown = [],
html; html;
for (i=0; i<len; i++) { for (i=0; i<arr.length; i++) {
dropdown.push(tpl({ value: arr[i] })); if (arr[i] == selected) is_sel = 'selected';
else is_sel = '';
dropdown.push(tpl({ value: arr[i], selected: is_sel }));
} }
// Add option to include dummy answers // Add option to include dummy answers
dropdown.push(tpl({ value: 'none' })); dropdown.push(tpl({ value: 'none' }));
html = dropdown.join(''); html = dropdown.join('');
_fn.build.form.zone.dropdown = new Handlebars.SafeString(html); return new Handlebars.SafeString(html);
_fn.build.$el.items.form.find('.zone-select').html(html);
}, },
feedback: function($form) { feedback: function($form) {
_fn.data.feedback = { _fn.data.feedback = {
...@@ -275,16 +287,27 @@ function DragAndDropEditBlock(runtime, element) { ...@@ -275,16 +287,27 @@ function DragAndDropEditBlock(runtime, element) {
}, },
item: { item: {
count: 0, count: 0,
add: function(e) { add: function(oldItem) {
var $form = _fn.build.$el.items.form, var $form = _fn.build.$el.items.form,
tpl = _fn.tpl.itemInput; tpl = _fn.tpl.itemInput,
ctx = {};
if (e) { if (oldItem) ctx = oldItem;
e.preventDefault();
} ctx.dropdown = _fn.build.form.createDropdown(ctx.zone);
if (!oldItem) ctx.width = '190';
else ctx.width = oldItem.size.width.substr(0,
oldItem.size.width.length - 2);
if (ctx.width == 'au') ctx.width = '0';
if (!oldItem) ctx.height = '0';
else ctx.height = oldItem.size.height.substr(0,
oldItem.size.height.length - 2);
if (ctx.height == 'au') ctx.height = '0';
_fn.build.form.item.count++; _fn.build.form.item.count++;
$form.append(tpl({ dropdown: _fn.build.form.zone.dropdown })); $form.append(tpl(ctx));
_fn.build.form.item.enableDelete(); _fn.build.form.item.enableDelete();
// Placeholder shim for IE9 // Placeholder shim for IE9
...@@ -386,7 +409,5 @@ function DragAndDropEditBlock(runtime, element) { ...@@ -386,7 +409,5 @@ function DragAndDropEditBlock(runtime, element) {
runtime.notify('cancel', {}); runtime.notify('cancel', {});
}); });
$.ajax(runtime.handlerUrl(element, 'get_data')).done(function(data){ dragAndDrop.builder(window.DragAndDropV2BlockPreviousData);
dragAndDrop.builder(data);
});
} }
...@@ -3,25 +3,32 @@ ...@@ -3,25 +3,32 @@
<div class="xblock--drag-and-drop editor-with-buttons"> <div class="xblock--drag-and-drop editor-with-buttons">
{{ js_templates|safe }} {{ js_templates|safe }}
<section class="drag-builder"> <script type="text/javascript">
var DragAndDropV2BlockPreviousData = JSON.parse(decodeURIComponent('{{ data|safe }}'));
</script>
<section class="drag-builder">
<div class="tab feedback-tab"> <div class="tab feedback-tab">
<p class="tab-content">
Note: don't edit the question if students already answered it! Delete it and create a new one.
</p>
<section class="tab-content"> <section class="tab-content">
<form class="feedback-form"> <form class="feedback-form">
<h3>Question title</h3> <h3>Question title</h3>
<input class="display-name" /> <input class="display-name" value="{{ self.display_name }}" />
<h3>Maximum score</h3> <h3>Maximum score</h3>
<input class="weight" value="1"/> <input class="weight" value="1" value="{{ self.weight }}"/>
<h3>Question text</h3> <h3>Question text</h3>
<textarea class="question-text"></textarea> <textarea class="question-text">{{ self.question_text }}</textarea>
<h3>Introduction Feedback</h3> <h3>Introduction Feedback</h3>
<textarea class="intro-feedback"></textarea> <textarea class="intro-feedback">{{ self.data.feedback.start }}</textarea>
<h3>Final Feedback</h3> <h3>Final Feedback</h3>
<textarea class="final-feedback"></textarea> <textarea class="final-feedback">{{ self.data.feedback.finish }}</textarea>
</form> </form>
</section> </section>
</div> </div>
......
...@@ -23,35 +23,35 @@ ...@@ -23,35 +23,35 @@
</script> </script>
<script id="zone-input-tpl" type="text/html"> <script id="zone-input-tpl" type="text/html">
<div class="zone-row {{ name }}"> <div class="zone-row {{ id }}">
<label>Text</label> <label>Text</label>
<input type="text" class="title" placeholder="{{ title }}" /> <input type="text" class="title" value="{{ title }}" />
<a href="#" class="remove-zone hidden"> <a href="#" class="remove-zone hidden">
<div class="icon remove"></div> <div class="icon remove"></div>
</a> </a>
<div class="layout"> <div class="layout">
<label>width</label> <label>width</label>
<input type="text" class="size width" value="200" /> <input type="text" class="size width" value="{{ width }}" />
<label>height</label> <label>height</label>
<input type="text" class="size height" value="100" /> <input type="text" class="size height" value="{{ height }}" />
<br /> <br />
<label>x</label> <label>x</label>
<input type="text" class="coord x" value="0" /> <input type="text" class="coord x" value="{{ x }}" />
<label>y</label> <label>y</label>
<input type="text" class="coord y" value="0" /> <input type="text" class="coord y" value="{{ y }}" />
</div> </div>
</div> </div>
</script> </script>
<script id="zone-dropdown-tpl" type="text/html"> <script id="zone-dropdown-tpl" type="text/html">
<option value="{{ value }}">{{ value }}</option> <option value="{{ value }}" {{ selected }}>{{ value }}</option>
</script> </script>
<script id="item-input-tpl" type="text/html"> <script id="item-input-tpl" type="text/html">
<div class="item"> <div class="item">
<div class="row"> <div class="row">
<label>Text</label> <label>Text</label>
<input type="text" class="item-text"></input> <input type="text" class="item-text" value="{{ displayName }}"/>
<label>Zone</label> <label>Zone</label>
<select class="zone-select">{{ dropdown }}</select> <select class="zone-select">{{ dropdown }}</select>
<a href="#" class="remove-item hidden"> <a href="#" class="remove-item hidden">
...@@ -60,21 +60,21 @@ ...@@ -60,21 +60,21 @@
</div> </div>
<div class="row"> <div class="row">
<label>Background image URL (alternative to the text)</label> <label>Background image URL (alternative to the text)</label>
<textarea class="background-image"></textarea> <textarea class="background-image">{{ backgroundImage }}</textarea>
</div> </div>
<div class="row"> <div class="row">
<label>Success Feedback</label> <label>Success Feedback</label>
<textarea class="success-feedback"></textarea> <textarea class="success-feedback">{{ feedback.correct }}</textarea>
</div> </div>
<div class="row"> <div class="row">
<label>Error Feedback</label> <label>Error Feedback</label>
<textarea class="error-feedback"></textarea> <textarea class="error-feedback">{{ feedback.incorrect }}</textarea>
</div> </div>
<div class="row"> <div class="row">
<label>Width (px - 0 for auto)</label> <label>Width (px - 0 for auto)</label>
<input type="text" class="item-width" value="190"></input> <input type="text" class="item-width" value="{{ width }}"></input>
<label>Height (px - 0 for auto)</label> <label>Height (px - 0 for auto)</label>
<input type="text" class="item-height" value="0"></input> <input type="text" class="item-height" value="{{ height }}"></input>
</div> </div>
</div> </div>
</script> </script>
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
"width": 200, "width": 200,
"title": "Zone A", "title": "Zone A",
"height": 100, "height": 100,
"active": true,
"y": "200", "y": "200",
"x": "100", "x": "100",
"id": "zone-1" "id": "zone-1"
...@@ -15,7 +14,6 @@ ...@@ -15,7 +14,6 @@
"width": 200, "width": 200,
"title": "Zone B", "title": "Zone B",
"height": 100, "height": 100,
"active": true,
"y": 0, "y": 0,
"x": 0, "x": 0,
"id": "zone-2" "id": "zone-2"
......
...@@ -45,8 +45,7 @@ def test_templates_contents(): ...@@ -45,8 +45,7 @@ def test_templates_contents():
student_fragment = block.render('student_view', Mock()) student_fragment = block.render('student_view', Mock())
assert_in('<section class="xblock--drag-and-drop">', assert_in('<section class="xblock--drag-and-drop">',
student_fragment.content) student_fragment.content)
assert_in('<option value="{{ value }}">{{ value }}</option>', assert_in('{{ value }}', student_fragment.content)
student_fragment.content)
assert_in("Test Drag &amp; Drop", student_fragment.content) assert_in("Test Drag &amp; Drop", student_fragment.content)
assert_in("Question Drag &amp; Drop", student_fragment.content) assert_in("Question Drag &amp; Drop", student_fragment.content)
assert_in("(5 Points Possible)", student_fragment.content) assert_in("(5 Points Possible)", student_fragment.content)
...@@ -54,8 +53,7 @@ def test_templates_contents(): ...@@ -54,8 +53,7 @@ def test_templates_contents():
studio_fragment = block.render('studio_view', Mock()) studio_fragment = block.render('studio_view', Mock())
assert_in('<div class="xblock--drag-and-drop editor-with-buttons">', assert_in('<div class="xblock--drag-and-drop editor-with-buttons">',
studio_fragment.content) studio_fragment.content)
assert_in('<option value="{{ value }}">{{ value }}</option>', assert_in('{{ value }}', studio_fragment.content)
studio_fragment.content)
def test_studio_submit(): def test_studio_submit():
block = make_block() block = make_block()
......
...@@ -6,7 +6,6 @@ ...@@ -6,7 +6,6 @@
"id": "zone-1", "id": "zone-1",
"height": 100, "height": 100,
"y": "200", "y": "200",
"active": true,
"x": "100", "x": "100",
"width": 200 "width": 200
}, },
...@@ -16,7 +15,6 @@ ...@@ -16,7 +15,6 @@
"id": "zone-2", "id": "zone-2",
"height": 100, "height": 100,
"y": 0, "y": 0,
"active": true,
"x": 0, "x": 0,
"width": 200 "width": 200
} }
......
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