Commit 7fd8c2ed by Matjaz Gregoric Committed by GitHub

Merge pull request #80 from open-craft/mtyaka/remove-numeric-input

Remove support for numerical input
parents bab49713 1870d6f0
...@@ -13,7 +13,6 @@ The editor is fully guided. Features include: ...@@ -13,7 +13,6 @@ The editor is fully guided. Features include:
* custom text and background colors for items * custom text and background colors for items
* optional auto-alignment for items (left, right, center) * optional auto-alignment for items (left, right, center)
* image items * image items
* items prompting for additional (numerical) input after being dropped
* decoy items that don't have a zone * decoy items that don't have a zone
* feedback popups for both correct and incorrect attempts * feedback popups for both correct and incorrect attempts
* introductory and final feedback * introductory and final feedback
...@@ -135,15 +134,6 @@ after the learner drops the item on a zone - the success feedback is ...@@ -135,15 +134,6 @@ after the learner drops the item on a zone - the success feedback is
shown if the item is dropped on the correct zone, while the error shown if the item is dropped on the correct zone, while the error
feedback is shown when dropping the item on an incorrect drop zone. feedback is shown when dropping the item on an incorrect drop zone.
Additionally, items can have a numerical value (and an optional error
margin) associated with them. When a learner drops an item that has a
numerical value on the correct zone, an input field for entering a
value is shown next to the item. The value that the learner submits is
checked against the expected value for the item. If you also specify a
margin, the value entered by the learner will be considered correct if
it does not differ from the expected value by more than that margin
(and incorrect otherwise).
![Zone dropdown](/doc/img/edit-view-zone-dropdown.png) ![Zone dropdown](/doc/img/edit-view-zone-dropdown.png)
The zone that an item belongs to is selected from a dropdown that The zone that an item belongs to is selected from a dropdown that
...@@ -153,6 +143,13 @@ any zone. ...@@ -153,6 +143,13 @@ any zone.
You can define an arbitrary number of drag items. You can define an arbitrary number of drag items.
Demo Course
-----------
Export of a demo course that showcases various Drag and Drop v2
features is available at
[github.com/open-craft/demo-courses/archive/drag-and-drop-v2.tar.gz](https://github.com/open-craft/demo-courses/archive/drag-and-drop-v2.tar.gz).
Analytics Events Analytics Events
---------------- ----------------
...@@ -261,9 +258,7 @@ Example ("common" fields that are not interesting in this context have been left ...@@ -261,9 +258,7 @@ Example ("common" fields that are not interesting in this context have been left
{ {
... ...
"event": { "event": {
"input": null, "is_correct": true, -- Whether the draggable item has been placed in the correct location.
"is_correct": true, -- False if there is an input in the draggable item, and the learner provided the wrong answer. Otherwise true.
"is_correct_location": true, -- Whether the draggable item has been placed in the correct location.
"item_id": 0, -- ID of the draggable item. "item_id": 0, -- ID of the draggable item.
"location": "The Top Zone", -- Name of the location the item was dragged to. "location": "The Top Zone", -- Name of the location the item was dragged to.
}, },
...@@ -284,11 +279,9 @@ Real event example (taken from a devstack): ...@@ -284,11 +279,9 @@ Real event example (taken from a devstack):
"referer": "http://example.com/courses/course-v1:DnD+DnD+DnD/courseware/ec546c58d2f447b7a9223c57b5de7344/756071f8de7f47c3b0ae726586ebbe16/1?activate_block_id=block-v1%3ADnD%2BDnD%2BDnD%2Btype%40vertical%2Bblock%40d2fc47476ca14c55816c4a1264a27280", "referer": "http://example.com/courses/course-v1:DnD+DnD+DnD/courseware/ec546c58d2f447b7a9223c57b5de7344/756071f8de7f47c3b0ae726586ebbe16/1?activate_block_id=block-v1%3ADnD%2BDnD%2BDnD%2Btype%40vertical%2Bblock%40d2fc47476ca14c55816c4a1264a27280",
"accept_language": "en;q=1.0, en;q=0.5", "accept_language": "en;q=1.0, en;q=0.5",
"event": { "event": {
"is_correct_location": true,
"is_correct": true, "is_correct": true,
"location": "The Top Zone", "location": "The Top Zone",
"item_id": 0, "item_id": 0,
"input": null
}, },
"event_source": "server", "event_source": "server",
"context": { "context": {
......
...@@ -167,7 +167,6 @@ class DragAndDropBlock(XBlock, XBlockWithSettingsMixin, ThemableXBlockMixin): ...@@ -167,7 +167,6 @@ class DragAndDropBlock(XBlock, XBlockWithSettingsMixin, ThemableXBlockMixin):
for item in items: for item in items:
del item['feedback'] del item['feedback']
del item['zone'] del item['zone']
item['inputOptions'] = 'inputOptions' in item
# Fall back on "backgroundImage" to be backward-compatible. # Fall back on "backgroundImage" to be backward-compatible.
image_url = item.get('imageURL') or item.get('backgroundImage') image_url = item.get('imageURL') or item.get('backgroundImage')
if image_url: if image_url:
...@@ -267,31 +266,13 @@ class DragAndDropBlock(XBlock, XBlockWithSettingsMixin, ThemableXBlockMixin): ...@@ -267,31 +266,13 @@ class DragAndDropBlock(XBlock, XBlockWithSettingsMixin, ThemableXBlockMixin):
feedback = item['feedback']['incorrect'] feedback = item['feedback']['incorrect']
overall_feedback = None overall_feedback = None
is_correct = False is_correct = False
is_correct_location = False
if 'input' in attempt: # Student submitted numerical value for item if item['zone'] == attempt['zone']: # Student placed item in correct zone
state = self._get_item_state().get(str(item['id']))
if state:
state['input'] = attempt['input']
is_correct_location = True
if self._is_correct_input(item, attempt['input']):
is_correct = True
feedback = item['feedback']['correct']
else:
is_correct = False
elif item['zone'] == attempt['zone']: # Student placed item in correct zone
is_correct_location = True
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
feedback = None
else:
# 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 = { state = {
'zone': attempt['zone'], 'zone': attempt['zone'],
'correct': True,
'x_percent': attempt['x_percent'], 'x_percent': attempt['x_percent'],
'y_percent': attempt['y_percent'], 'y_percent': attempt['y_percent'],
} }
...@@ -325,14 +306,11 @@ class DragAndDropBlock(XBlock, XBlockWithSettingsMixin, ThemableXBlockMixin): ...@@ -325,14 +306,11 @@ class DragAndDropBlock(XBlock, XBlockWithSettingsMixin, ThemableXBlockMixin):
'item_id': item['id'], 'item_id': item['id'],
'location': zone.get("title"), 'location': zone.get("title"),
'location_id': zone.get("uid"), 'location_id': zone.get("uid"),
'input': attempt.get('input'),
'is_correct_location': is_correct_location,
'is_correct': is_correct, 'is_correct': is_correct,
}) })
return { return {
'correct': is_correct, 'correct': is_correct,
'correct_location': is_correct_location,
'finished': self._is_finished(), 'finished': self._is_finished(),
'overall_feedback': overall_feedback, 'overall_feedback': overall_feedback,
'feedback': feedback 'feedback': feedback
...@@ -396,7 +374,6 @@ class DragAndDropBlock(XBlock, XBlockWithSettingsMixin, ThemableXBlockMixin): ...@@ -396,7 +374,6 @@ class DragAndDropBlock(XBlock, XBlockWithSettingsMixin, ThemableXBlockMixin):
item_state = self._get_item_state() item_state = self._get_item_state()
for item_id, item in item_state.iteritems(): for item_id, item in item_state.iteritems():
definition = self._get_item_definition(int(item_id)) definition = self._get_item_definition(int(item_id))
item['correct_input'] = self._is_correct_input(definition, item.get('input'))
# If information about zone is missing # If information about zone is missing
# (because problem was completed before a11y enhancements were implemented), # (because problem was completed before a11y enhancements were implemented),
# deduce zone in which item is placed from definition: # deduce zone in which item is placed from definition:
...@@ -468,7 +445,6 @@ class DragAndDropBlock(XBlock, XBlockWithSettingsMixin, ThemableXBlockMixin): ...@@ -468,7 +445,6 @@ class DragAndDropBlock(XBlock, XBlockWithSettingsMixin, ThemableXBlockMixin):
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 self._is_correct_input(item, item_state[item_id].get('input')):
correct_count += 1 correct_count += 1
return correct_count / float(total_count) * self.weight return correct_count / float(total_count) * self.weight
...@@ -486,10 +462,6 @@ class DragAndDropBlock(XBlock, XBlockWithSettingsMixin, ThemableXBlockMixin): ...@@ -486,10 +462,6 @@ class DragAndDropBlock(XBlock, XBlockWithSettingsMixin, ThemableXBlockMixin):
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 'inputOptions' in item:
if 'input' in item_state[item_id]:
completed_count += 1
else:
completed_count += 1 completed_count += 1
return completed_count == total_count return completed_count == total_count
...@@ -513,25 +485,6 @@ class DragAndDropBlock(XBlock, XBlockWithSettingsMixin, ThemableXBlockMixin): ...@@ -513,25 +485,6 @@ class DragAndDropBlock(XBlock, XBlockWithSettingsMixin, ThemableXBlockMixin):
return usage_id return usage_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.
......
...@@ -193,49 +193,6 @@ ...@@ -193,49 +193,6 @@
max-width: 100%; max-width: 100%;
} }
.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 .option .numerical-input .spinner-wrapper {
position: absolute;
right: 0;
top: 0;
margin-right: 5px;
padding-top: 6px;
color: #333;
}
.xblock--drag-and-drop .drag-container .option .numerical-input .input {
display: inline-block;
width: 144px;
}
.xblock--drag-and-drop .drag-container .option .numerical-input .submit-input {
position: absolute;
left: 150px;
top: 4px;
white-space: nowrap; /* Fix cross-browser issue: Without this declaration, button text wraps in Chrome/Chromium */
}
.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 .option .numerical-input.correct .input {
background-color: #ceffce;
color: #087108;
}
.xblock--drag-and-drop .drag-container .option .numerical-input.incorrect .input {
background-color: #ffcece;
color: #ad0d0d;
}
.xblock--drag-and-drop .drag-container .option.fade { .xblock--drag-and-drop .drag-container .option.fade {
opacity: 0.80; opacity: 0.80;
} }
......
...@@ -196,12 +196,6 @@ ...@@ -196,12 +196,6 @@
width: 50px; width: 50px;
} }
.xblock--drag-and-drop--editor .items-form .item-numerical-value,
.xblock--drag-and-drop--editor .items-form .item-numerical-margin {
margin: 0 1%;
width: 50%;
}
.xblock--drag-and-drop--editor .items-form textarea { .xblock--drag-and-drop--editor .items-form textarea {
width: 97%; width: 97%;
margin: 0 1%; margin: 0 1%;
......
...@@ -4,20 +4,6 @@ function DragNDropTemplates(url_name) { ...@@ -4,20 +4,6 @@ function DragNDropTemplates(url_name) {
// Set up a mock for gettext if it isn't available in the client runtime: // Set up a mock for gettext if it isn't available in the client runtime:
if (!window.gettext) { window.gettext = function gettext_stub(string) { return string; }; } if (!window.gettext) { window.gettext = function gettext_stub(string) { return string; }; }
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 itemSpinnerTemplate = function(xhr_active) { var itemSpinnerTemplate = function(xhr_active) {
if (!xhr_active) { if (!xhr_active) {
return null; return null;
...@@ -36,22 +22,6 @@ function DragNDropTemplates(url_name) { ...@@ -36,22 +22,6 @@ function DragNDropTemplates(url_name) {
}); });
}; };
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}),
itemSpinnerTemplate(input.xhr_active),
h('button.submit-input', {disabled: input.has_value}, gettext('ok'))
])
);
};
var getZone = function(zoneUID, ctx) { var getZone = function(zoneUID, ctx) {
for (var i = 0; i < ctx.zones.length; i++) { for (var i = 0; i < ctx.zones.length; i++) {
if (ctx.zones[i].uid === zoneUID) { if (ctx.zones[i].uid === zoneUID) {
...@@ -98,10 +68,6 @@ function DragNDropTemplates(url_name) { ...@@ -98,10 +68,6 @@ function DragNDropTemplates(url_name) {
} }
} else { } else {
// This is an "aligned" zone, so the item position within the zone is calculated by the browser. // This is an "aligned" zone, so the item position within the zone is calculated by the browser.
// Allow for the input + button width for aligned items
if (item.input) {
style.marginRight = '190px';
}
// Make up for the fact we're in a wrapper container by calculating percentage differences. // Make up for the fact we're in a wrapper container by calculating percentage differences.
var maxWidth = (item.widthPercent || 30) / 100; var maxWidth = (item.widthPercent || 30) / 100;
var widthPercent = zone.width_percent / 100; var widthPercent = zone.width_percent / 100;
...@@ -132,8 +98,7 @@ function DragNDropTemplates(url_name) { ...@@ -132,8 +98,7 @@ function DragNDropTemplates(url_name) {
} }
// Define children // Define children
var children = [ var children = [
itemSpinnerTemplate(item.xhr_active), itemSpinnerTemplate(item.xhr_active)
itemInputTemplate(item.input)
]; ];
var item_content_html = item.displayName; var item_content_html = item.displayName;
if (item.imageURL) { if (item.imageURL) {
...@@ -386,7 +351,6 @@ function DragAndDropBlock(runtime, element, configuration) { ...@@ -386,7 +351,6 @@ function DragAndDropBlock(runtime, element, configuration) {
$element.on('keydown', '.reset-button', function(evt) { $element.on('keydown', '.reset-button', function(evt) {
runOnKey(evt, RET, resetProblem); runOnKey(evt, RET, resetProblem);
}); });
$element.on('click', '.submit-input', submitInput);
// For the next one, we need to use addEventListener with useCapture 'true' in order // For the next one, we need to use addEventListener with useCapture 'true' in order
// to watch for load events on any child element, since load events do not bubble. // to watch for load events on any child element, since load events do not bubble.
...@@ -783,9 +747,9 @@ function DragAndDropBlock(runtime, element, configuration) { ...@@ -783,9 +747,9 @@ function DragAndDropBlock(runtime, element, configuration) {
$.post(url, JSON.stringify(data), 'json') $.post(url, JSON.stringify(data), 'json')
.done(function(data){ .done(function(data){
state.last_action_correct = data.correct_location; state.last_action_correct = data.correct;
if (data.correct_location) { if (data.correct) {
state.items[item_id].correct_input = Boolean(data.correct); state.items[item_id].correct = true;
state.items[item_id].submitting_location = false; state.items[item_id].submitting_location = false;
} else { } else {
delete state.items[item_id]; delete state.items[item_id];
...@@ -803,42 +767,6 @@ function DragAndDropBlock(runtime, element, configuration) { ...@@ -803,42 +767,6 @@ function DragAndDropBlock(runtime, element, configuration) {
}); });
}; };
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;
}
state.items[item_id].input = input_value;
state.items[item_id].submitting_input = true;
applyState();
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.last_action_correct = data.correct;
state.items[item_id].submitting_input = false;
state.items[item_id].correct_input = data.correct;
state.feedback = data.feedback;
if (data.finished) {
state.finished = true;
state.overall_feedback = data.overall_feedback;
}
applyState();
})
.fail(function(data) {
state.items[item_id].submitting_input = false;
applyState();
});
};
var closePopup = function(evt) { var closePopup = function(evt) {
if (!state.feedback) { if (!state.feedback) {
return; return;
...@@ -847,9 +775,8 @@ function DragAndDropBlock(runtime, element, configuration) { ...@@ -847,9 +775,8 @@ function DragAndDropBlock(runtime, element, configuration) {
var target = $(evt.target); var target = $(evt.target);
var popup_box = '.xblock--drag-and-drop .popup'; var popup_box = '.xblock--drag-and-drop .popup';
var close_button = '.xblock--drag-and-drop .popup .close'; var close_button = '.xblock--drag-and-drop .popup .close';
var submit_input_button = '.xblock--drag-and-drop .submit-input';
if (target.is(popup_box) || target.is(submit_input_button)) { if (target.is(popup_box)) {
return; return;
} }
if (target.parents(popup_box).length && !target.is(close_button)) { if (target.parents(popup_box).length && !target.is(close_button)) {
...@@ -884,31 +811,17 @@ function DragAndDropBlock(runtime, element, configuration) { ...@@ -884,31 +811,17 @@ function DragAndDropBlock(runtime, element, configuration) {
var render = function() { var render = function() {
var items = configuration.items.map(function(item) { var items = configuration.items.map(function(item) {
var input = null;
var item_user_state = state.items[item.id]; var item_user_state = state.items[item.id];
if (item.inputOptions) {
input = {
is_visible: item_user_state && !item_user_state.submitting_location,
has_value: Boolean(item_user_state && 'input' in item_user_state),
value : (item_user_state && item_user_state.input) || '',
class_name: undefined,
xhr_active: (item_user_state && item_user_state.submitting_input)
};
if (input.has_value && !item_user_state.submitting_input) {
input.class_name = item_user_state.correct_input ? 'correct' : 'incorrect';
}
}
var grabbed = false; var grabbed = false;
if (item.grabbed !== undefined) { if (item.grabbed !== undefined) {
grabbed = item.grabbed; grabbed = item.grabbed;
} }
var placed = item_user_state && ('input' in item_user_state || item_user_state.correct_input); var placed = item_user_state && item_user_state.correct;
var itemProperties = { var itemProperties = {
value: item.id, value: item.id,
drag_disabled: Boolean(item_user_state || state.finished), drag_disabled: Boolean(item_user_state || state.finished),
class_name: placed || state.finished ? 'fade' : undefined, class_name: placed || state.finished ? 'fade' : undefined,
xhr_active: (item_user_state && item_user_state.submitting_location), xhr_active: (item_user_state && item_user_state.submitting_location),
input: input,
displayName: item.displayName, displayName: item.displayName,
imageURL: item.expandedImageURL, imageURL: item.expandedImageURL,
imageDescription: item.imageDescription, imageDescription: item.imageDescription,
......
...@@ -414,10 +414,6 @@ function DragAndDropEditBlock(runtime, element, params) { ...@@ -414,10 +414,6 @@ function DragAndDropEditBlock(runtime, element, params) {
// block, but preserve the data in case we need it again: // block, but preserve the data in case we need it again:
ctx.pixelHeight = itemData.size.height.substr(0, itemData.size.height.length - 2); // Remove 'px' ctx.pixelHeight = itemData.size.height.substr(0, itemData.size.height.length - 2); // Remove 'px'
} }
if (itemData.inputOptions) {
ctx.numericalValue = itemData.inputOptions.value;
ctx.numericalMargin = itemData.inputOptions.margin;
}
} }
ctx.dropdown = _fn.build.form.createDropdown(ctx.zone); ctx.dropdown = _fn.build.form.createDropdown(ctx.zone);
...@@ -486,15 +482,6 @@ function DragAndDropEditBlock(runtime, element, params) { ...@@ -486,15 +482,6 @@ function DragAndDropEditBlock(runtime, element, params) {
var widthPercent = $el.find('.item-width').val(); var widthPercent = $el.find('.item-width').val();
if (widthPercent && +widthPercent > 0) { data.widthPercent = widthPercent; } if (widthPercent && +widthPercent > 0) { data.widthPercent = widthPercent; }
var numValue = parseFloat($el.find('.item-numerical-value').val());
var numMargin = parseFloat($el.find('.item-numerical-margin').val());
if (isFinite(numValue)) {
data.inputOptions = {
value: numValue,
margin: isFinite(numMargin) ? numMargin : 0
};
}
items.push(data); items.push(data);
} }
}); });
......
...@@ -32,16 +32,6 @@ ...@@ -32,16 +32,6 @@
opacity: 1; opacity: 1;
} }
.themed-xblock.xblock--drag-and-drop .drag-container .option .numerical-input.correct .input {
background-color: #ceffce;
color: #087108;
}
.themed-xblock.xblock--drag-and-drop .drag-container .option .numerical-input.incorrect .input {
background-color: #ffcece;
color: #ad0d0d;
}
.themed-xblock.xblock--drag-and-drop .drag-container .option.fade { .themed-xblock.xblock--drag-and-drop .drag-container .option.fade {
opacity: 0.5; opacity: 0.5;
} }
......
...@@ -131,23 +131,5 @@ ...@@ -131,23 +131,5 @@
<label for="item-{{id}}-width-percent">{{i18n "Preferred width as a percentage of the background image width (or blank for automatic width):"}}</label> <label for="item-{{id}}-width-percent">{{i18n "Preferred width as a percentage of the background image width (or blank for automatic width):"}}</label>
<input type="number" id="item-{{id}}-width-percent" class="item-width" value="{{ singleDecimalFloat widthPercent }}" step="0.1" min="1" max="99" />% <input type="number" id="item-{{id}}-width-percent" class="item-width" value="{{ singleDecimalFloat widthPercent }}" step="0.1" min="1" max="99" />%
</div> </div>
<div class="row advanced">
<label for="item-{{id}}-numerical-value">
{{i18n "Optional numerical value (if you set this, learners will be prompted for this value after dropping this item)"}}
</label>
<input type="number"
step="0.1"
id="item-{{id}}-numerical-value"
class="item-numerical-value" value="{{ numericalValue }}" />
</div>
<div class="row advanced">
<label for="item-{{id}}-numerical-margin">
{{i18n "Margin ± (when a numerical value is required, values entered by learners must not differ from the expected value by more than this margin; default is zero)"}}
</label>
<input type="number"
step="0.1"
id="item-{{id}}-numerical-margin"
class="item-numerical-margin" value="{{ numericalMargin }}" />
</div>
</div> </div>
</script> </script>
...@@ -248,19 +248,6 @@ msgid "" ...@@ -248,19 +248,6 @@ msgid ""
"automatic width):" "automatic width):"
msgstr "" msgstr ""
#: templates/html/js_templates.html
msgid ""
"Optional numerical value (if you set this, learners will be prompted for "
"this value after dropping this item)"
msgstr ""
#: templates/html/js_templates.html
msgid ""
"Margin ± (when a numerical value is required, values entered by learners "
"must not differ from the expected value by more than this margin; default "
"is zero)"
msgstr ""
#: templates/html/drag_and_drop.html #: templates/html/drag_and_drop.html
msgid "Loading drag and drop problem." msgid "Loading drag and drop problem."
msgstr "" msgstr ""
......
...@@ -304,29 +304,6 @@ msgstr "" ...@@ -304,29 +304,6 @@ msgstr ""
"Préférréd wïdth äs ä pérçéntägé öf thé ßäçkgröünd ïmägé wïdth (ör ßlänk för " "Préférréd wïdth äs ä pérçéntägé öf thé ßäçkgröünd ïmägé wïdth (ör ßlänk för "
"äütömätïç wïdth): Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σ#" "äütömätïç wïdth): Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σ#"
#: templates/html/js_templates.html
msgid ""
"Optional numerical value (if you set this, learners will be prompted for "
"this value after dropping this item)"
msgstr ""
"Öptïönäl nümérïçäl välüé (ïf ýöü sét thïs, léärnérs wïll ßé prömptéd för "
"thïs välüé äftér dröppïng thïs ïtém) Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#"
#: templates/html/js_templates.html
msgid ""
"Margin ± (when a numerical value is required, values entered by learners "
"must not differ from the expected value by more than this margin; default is"
" zero)"
msgstr ""
"Märgïn ± (whén ä nümérïçäl välüé ïs réqüïréd, välüés éntéréd ßý léärnérs "
"müst nöt dïffér fröm thé éxpéçtéd välüé ßý möré thän thïs märgïn; défäült ïs"
" zérö) Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α∂ιριѕι¢ιηg єłιт, ѕє∂ ∂σ "
"єιυѕмσ∂ тємρσя ιη¢ι∂ι∂υηт υт łαвσяє єт ∂σłσяє мαgηα αłιqυα. υт єηιм α∂ мιηιм"
" νєηιαм, qυιѕ ησѕтяυ∂ єχєя¢ιтαтιση υłłαм¢σ łαвσяιѕ ηιѕι υт αłιqυιρ єχ єα "
"¢σммσ∂σ ¢σηѕєqυαт. ∂υιѕ αυтє ιяυяє ∂σłσя ιη яєρяєнєη∂єяιт ιη νσłυρтαтє νєłιт"
" єѕѕє ¢ιłłυм ∂σłσяє єυ ƒυgιαт ηυłłα ραяιαтυя. єχ¢єρтєυя ѕιηт σ¢¢αє¢αт "
"¢υρι∂αтαт ηση ρяσι∂єηт, ѕυηт ιη ¢υłρα qυι σƒƒι¢ια ∂єѕєяυηт мσ#"
#: templates/html/drag_and_drop.html #: templates/html/drag_and_drop.html
msgid "Loading drag and drop problem." msgid "Loading drag and drop problem."
msgstr "Löädïng dräg änd dröp prößlém. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢т#" msgstr "Löädïng dräg änd dröp prößlém. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢т#"
......
...@@ -47,10 +47,6 @@ ...@@ -47,10 +47,6 @@
"size": { "size": {
"width": "190px", "width": "190px",
"height": "100px" "height": "100px"
},
"inputOptions": {
"value": 100,
"margin": 5
} }
}, },
{ {
......
...@@ -36,11 +36,7 @@ ...@@ -36,11 +36,7 @@
}, },
"zone": "zone-2", "zone": "zone-2",
"imageURL": "", "imageURL": "",
"id": 1, "id": 1
"inputOptions": {
"value": 100,
"margin": 5
}
}, },
{ {
"displayName": "X", "displayName": "X",
......
...@@ -36,11 +36,7 @@ ...@@ -36,11 +36,7 @@
}, },
"zone": "zone-52", "zone": "zone-52",
"imageURL": "", "imageURL": "",
"id": 20, "id": 20
"inputOptions": {
"value": 100,
"margin": 5
}
}, },
{ {
"displayName": "X", "displayName": "X",
......
...@@ -36,11 +36,7 @@ ...@@ -36,11 +36,7 @@
}, },
"zone": "zone-2", "zone": "zone-2",
"imageURL": "", "imageURL": "",
"id": 1, "id": 1
"inputOptions": {
"value": 100,
"margin": 5
}
}, },
{ {
"displayName": "<span style='color:red'>X</span>", "displayName": "<span style='color:red'>X</span>",
......
...@@ -22,7 +22,6 @@ class TestCustomDataDragAndDropRendering(BaseIntegrationTest): ...@@ -22,7 +22,6 @@ class TestCustomDataDragAndDropRendering(BaseIntegrationTest):
self.assertEqual(len(items), 3) self.assertEqual(len(items), 3)
self.assertIn('<b>1</b>', self.get_element_html(items[0])) self.assertIn('<b>1</b>', self.get_element_html(items[0]))
self.assertIn('<i>2</i>', self.get_element_html(items[1])) self.assertIn('<i>2</i>', self.get_element_html(items[1]))
self.assertIn('<input class="input" type="text">', self.get_element_html(items[1]))
self.assertIn('<span style="color:red">X</span>', self.get_element_html(items[2])) self.assertIn('<span style="color:red">X</span>', self.get_element_html(items[2]))
def test_background_image(self): def test_background_image(self):
......
...@@ -36,29 +36,25 @@ ...@@ -36,29 +36,25 @@
"displayName": "<b>1</b>", "displayName": "<b>1</b>",
"imageURL": "", "imageURL": "",
"expandedImageURL": "", "expandedImageURL": "",
"id": 0, "id": 0
"inputOptions": false
}, },
{ {
"displayName": "<i>2</i>", "displayName": "<i>2</i>",
"imageURL": "", "imageURL": "",
"expandedImageURL": "", "expandedImageURL": "",
"id": 1, "id": 1
"inputOptions": true
}, },
{ {
"displayName": "X", "displayName": "X",
"imageURL": "", "imageURL": "",
"expandedImageURL": "", "expandedImageURL": "",
"id": 2, "id": 2
"inputOptions": false
}, },
{ {
"displayName": "", "displayName": "",
"imageURL": "http://placehold.it/100x300", "imageURL": "http://placehold.it/100x300",
"expandedImageURL": "http://placehold.it/100x300", "expandedImageURL": "http://placehold.it/100x300",
"id": 3, "id": 3
"inputOptions": false
} }
] ]
} }
...@@ -39,11 +39,7 @@ ...@@ -39,11 +39,7 @@
}, },
"zone": "Zone <b>2</b>", "zone": "Zone <b>2</b>",
"imageURL": "", "imageURL": "",
"id": 1, "id": 1
"inputOptions": {
"value": 100,
"margin": 5
}
}, },
{ {
"displayName": "X", "displayName": "X",
......
...@@ -37,7 +37,6 @@ ...@@ -37,7 +37,6 @@
"imageURL": "", "imageURL": "",
"expandedImageURL": "", "expandedImageURL": "",
"id": 0, "id": 0,
"inputOptions": false,
"size": {"height": "auto", "width": "190px"} "size": {"height": "auto", "width": "190px"}
}, },
{ {
...@@ -45,7 +44,6 @@ ...@@ -45,7 +44,6 @@
"imageURL": "", "imageURL": "",
"expandedImageURL": "", "expandedImageURL": "",
"id": 1, "id": 1,
"inputOptions": true,
"size": {"height": "auto", "width": "190px"} "size": {"height": "auto", "width": "190px"}
}, },
{ {
...@@ -53,7 +51,6 @@ ...@@ -53,7 +51,6 @@
"imageURL": "", "imageURL": "",
"expandedImageURL": "", "expandedImageURL": "",
"id": 2, "id": 2,
"inputOptions": false,
"size": {"height": "100px", "width": "100px"} "size": {"height": "100px", "width": "100px"}
}, },
{ {
...@@ -61,7 +58,6 @@ ...@@ -61,7 +58,6 @@
"imageURL": "http://i1.kym-cdn.com/entries/icons/square/000/006/151/tumblr_lltzgnHi5F1qzib3wo1_400.jpg", "imageURL": "http://i1.kym-cdn.com/entries/icons/square/000/006/151/tumblr_lltzgnHi5F1qzib3wo1_400.jpg",
"expandedImageURL": "http://i1.kym-cdn.com/entries/icons/square/000/006/151/tumblr_lltzgnHi5F1qzib3wo1_400.jpg", "expandedImageURL": "http://i1.kym-cdn.com/entries/icons/square/000/006/151/tumblr_lltzgnHi5F1qzib3wo1_400.jpg",
"id": 3, "id": 3,
"inputOptions": false,
"size": {"height": "auto", "width": "190px"} "size": {"height": "auto", "width": "190px"}
} }
] ]
......
...@@ -46,10 +46,6 @@ ...@@ -46,10 +46,6 @@
"size": { "size": {
"width": "190px", "width": "190px",
"height": "auto" "height": "auto"
},
"inputOptions": {
"value": 100,
"margin": 5
} }
}, },
{ {
......
...@@ -36,29 +36,25 @@ ...@@ -36,29 +36,25 @@
"displayName": "1", "displayName": "1",
"imageURL": "", "imageURL": "",
"expandedImageURL": "", "expandedImageURL": "",
"id": 0, "id": 0
"inputOptions": false
}, },
{ {
"displayName": "2", "displayName": "2",
"imageURL": "", "imageURL": "",
"expandedImageURL": "", "expandedImageURL": "",
"id": 1, "id": 1
"inputOptions": true
}, },
{ {
"displayName": "X", "displayName": "X",
"imageURL": "/static/test_url_expansion", "imageURL": "/static/test_url_expansion",
"expandedImageURL": "/course/test-course/assets/test_url_expansion", "expandedImageURL": "/course/test-course/assets/test_url_expansion",
"id": 2, "id": 2
"inputOptions": false
}, },
{ {
"displayName": "", "displayName": "",
"imageURL": "http://placehold.it/200x100", "imageURL": "http://placehold.it/200x100",
"expandedImageURL": "http://placehold.it/200x100", "expandedImageURL": "http://placehold.it/200x100",
"id": 3, "id": 3
"inputOptions": false
} }
] ]
} }
...@@ -38,11 +38,7 @@ ...@@ -38,11 +38,7 @@
}, },
"zone": "zone-2", "zone": "zone-2",
"imageURL": "", "imageURL": "",
"id": 1, "id": 1
"inputOptions": {
"value": 100,
"margin": 5
}
}, },
{ {
"displayName": "X", "displayName": "X",
......
...@@ -65,7 +65,6 @@ class BaseDragAndDropAjaxFixture(TestCaseMixin): ...@@ -65,7 +65,6 @@ class BaseDragAndDropAjaxFixture(TestCaseMixin):
"overall_feedback": None, "overall_feedback": None,
"finished": False, "finished": False,
"correct": False, "correct": False,
"correct_location": False,
"feedback": self.FEEDBACK[item_id]["incorrect"] "feedback": self.FEEDBACK[item_id]["incorrect"]
}) })
...@@ -77,7 +76,6 @@ class BaseDragAndDropAjaxFixture(TestCaseMixin): ...@@ -77,7 +76,6 @@ class BaseDragAndDropAjaxFixture(TestCaseMixin):
"overall_feedback": None, "overall_feedback": None,
"finished": False, "finished": False,
"correct": False, "correct": False,
"correct_location": False,
"feedback": self.FEEDBACK[item_id]["incorrect"] "feedback": self.FEEDBACK[item_id]["incorrect"]
}) })
...@@ -89,79 +87,9 @@ class BaseDragAndDropAjaxFixture(TestCaseMixin): ...@@ -89,79 +87,9 @@ class BaseDragAndDropAjaxFixture(TestCaseMixin):
"overall_feedback": None, "overall_feedback": None,
"finished": False, "finished": False,
"correct": True, "correct": True,
"correct_location": True,
"feedback": self.FEEDBACK[item_id]["correct"] "feedback": self.FEEDBACK[item_id]["correct"]
}) })
def test_do_attempt_with_input(self):
# Drop item that requires numerical input
data = {"val": 1, "zone": self.ZONE_2, "x_percent": "0%", "y_percent": "85%"}
res = self.call_handler('do_attempt', data)
self.assertEqual(res, {
"finished": False,
"correct": False,
"correct_location": True,
"feedback": None,
"overall_feedback": None,
})
expected_state = {
'items': {
"1": {
"x_percent": "0%", "y_percent": "85%", "correct_input": False, "zone": self.ZONE_2,
},
},
'finished': False,
'overall_feedback': self.initial_feedback(),
}
self.assertEqual(expected_state, self.call_handler('get_user_state', method="GET"))
# Submit incorrect value
data = {"val": 1, "input": "250"}
res = self.call_handler('do_attempt', data)
self.assertEqual(res, {
"finished": False,
"correct": False,
"correct_location": True,
"feedback": self.FEEDBACK[1]['incorrect'],
"overall_feedback": None
})
expected_state = {
'items': {
"1": {
"x_percent": "0%", "y_percent": "85%", "correct_input": False, "zone": self.ZONE_2,
"input": "250",
},
},
'finished': False,
'overall_feedback': self.initial_feedback(),
}
self.assertEqual(expected_state, self.call_handler('get_user_state', method="GET"))
# Submit correct value
data = {"val": 1, "input": "103"}
res = self.call_handler('do_attempt', data)
self.assertEqual(res, {
"finished": False,
"correct": True,
"correct_location": True,
"feedback": self.FEEDBACK[1]['correct'],
"overall_feedback": None,
})
expected_state = {
'items': {
"1": {
"x_percent": "0%", "y_percent": "85%", "correct_input": True, "zone": self.ZONE_2,
"input": "103",
},
},
'finished': False,
'overall_feedback': self.initial_feedback(),
}
self.assertEqual(expected_state, self.call_handler('get_user_state', method="GET"))
def test_grading(self): def test_grading(self):
published_grades = [] published_grades = []
...@@ -182,11 +110,6 @@ class BaseDragAndDropAjaxFixture(TestCaseMixin): ...@@ -182,11 +110,6 @@ class BaseDragAndDropAjaxFixture(TestCaseMixin):
}) })
self.assertEqual(2, len(published_grades)) self.assertEqual(2, len(published_grades))
self.assertEqual({'value': 0.5, 'max_value': 1}, published_grades[-1])
self.call_handler('do_attempt', {"val": 1, "input": "99"})
self.assertEqual(3, len(published_grades))
self.assertEqual({'value': 1, 'max_value': 1}, published_grades[-1]) self.assertEqual({'value': 1, 'max_value': 1}, published_grades[-1])
def test_do_attempt_final(self): def test_do_attempt_final(self):
...@@ -195,7 +118,7 @@ class BaseDragAndDropAjaxFixture(TestCaseMixin): ...@@ -195,7 +118,7 @@ class BaseDragAndDropAjaxFixture(TestCaseMixin):
expected_state = { expected_state = {
"items": { "items": {
"0": {"x_percent": "33%", "y_percent": "11%", "correct_input": True, "zone": self.ZONE_1} "0": {"x_percent": "33%", "y_percent": "11%", "correct": True, "zone": self.ZONE_1}
}, },
"finished": False, "finished": False,
'overall_feedback': self.initial_feedback(), 'overall_feedback': self.initial_feedback(),
...@@ -204,24 +127,20 @@ class BaseDragAndDropAjaxFixture(TestCaseMixin): ...@@ -204,24 +127,20 @@ class BaseDragAndDropAjaxFixture(TestCaseMixin):
data = {"val": 1, "zone": self.ZONE_2, "x_percent": "22%", "y_percent": "22%"} data = {"val": 1, "zone": self.ZONE_2, "x_percent": "22%", "y_percent": "22%"}
res = self.call_handler('do_attempt', data) res = self.call_handler('do_attempt', data)
data = {"val": 1, "input": "99"}
res = self.call_handler('do_attempt', data)
self.assertEqual(res, { self.assertEqual(res, {
"overall_feedback": self.FINAL_FEEDBACK, "overall_feedback": self.FINAL_FEEDBACK,
"finished": True, "finished": True,
"correct": True, "correct": True,
"correct_location": True,
"feedback": self.FEEDBACK[1]["correct"] "feedback": self.FEEDBACK[1]["correct"]
}) })
expected_state = { expected_state = {
"items": { "items": {
"0": { "0": {
"x_percent": "33%", "y_percent": "11%", "correct_input": True, "zone": self.ZONE_1, "x_percent": "33%", "y_percent": "11%", "correct": True, "zone": self.ZONE_1,
}, },
"1": { "1": {
"x_percent": "22%", "y_percent": "22%", "correct_input": True, "zone": self.ZONE_2, "x_percent": "22%", "y_percent": "22%", "correct": True, "zone": self.ZONE_2,
"input": "99",
} }
}, },
"finished": True, "finished": True,
......
...@@ -46,7 +46,7 @@ class BasicTests(TestCaseMixin, unittest.TestCase): ...@@ -46,7 +46,7 @@ class BasicTests(TestCaseMixin, unittest.TestCase):
self.assertEqual(zones, DEFAULT_DATA["zones"]) self.assertEqual(zones, DEFAULT_DATA["zones"])
# Items should contain no answer data: # Items should contain no answer data:
self.assertEqual(items, [ self.assertEqual(items, [
{"id": i, "displayName": display_name, "imageURL": "", "expandedImageURL": "", "inputOptions": False} {"id": i, "displayName": display_name, "imageURL": "", "expandedImageURL": ""}
for i, display_name in enumerate( for i, display_name in enumerate(
["Goes to the top", "Goes to the middle", "Goes to the bottom", "I don't belong anywhere"] ["Goes to the top", "Goes to the middle", "Goes to the bottom", "I don't belong anywhere"]
) )
...@@ -76,15 +76,15 @@ class BasicTests(TestCaseMixin, unittest.TestCase): ...@@ -76,15 +76,15 @@ class BasicTests(TestCaseMixin, unittest.TestCase):
# Check the result: # Check the result:
self.assertTrue(self.block.completed) self.assertTrue(self.block.completed)
self.assertEqual(self.block.item_state, { self.assertEqual(self.block.item_state, {
'0': {'x_percent': '33%', 'y_percent': '11%', 'zone': TOP_ZONE_ID}, '0': {'x_percent': '33%', 'y_percent': '11%', 'correct': True, 'zone': TOP_ZONE_ID},
'1': {'x_percent': '67%', 'y_percent': '80%', 'zone': MIDDLE_ZONE_ID}, '1': {'x_percent': '67%', 'y_percent': '80%', 'correct': True, 'zone': MIDDLE_ZONE_ID},
'2': {'x_percent': '99%', 'y_percent': '95%', 'zone': BOTTOM_ZONE_ID}, '2': {'x_percent': '99%', 'y_percent': '95%', 'correct': True, 'zone': BOTTOM_ZONE_ID},
}) })
self.assertEqual(self.call_handler('get_user_state'), { self.assertEqual(self.call_handler('get_user_state'), {
'items': { 'items': {
'0': {'x_percent': '33%', 'y_percent': '11%', 'correct_input': True, 'zone': TOP_ZONE_ID}, '0': {'x_percent': '33%', 'y_percent': '11%', 'correct': True, 'zone': TOP_ZONE_ID},
'1': {'x_percent': '67%', 'y_percent': '80%', 'correct_input': True, 'zone': MIDDLE_ZONE_ID}, '1': {'x_percent': '67%', 'y_percent': '80%', 'correct': True, 'zone': MIDDLE_ZONE_ID},
'2': {'x_percent': '99%', 'y_percent': '95%', 'correct_input': True, 'zone': BOTTOM_ZONE_ID}, '2': {'x_percent': '99%', 'y_percent': '95%', 'correct': True, 'zone': BOTTOM_ZONE_ID},
}, },
'finished': True, 'finished': True,
'overall_feedback': FINISH_FEEDBACK, 'overall_feedback': FINISH_FEEDBACK,
......
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