Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
X
xblock-drag-and-drop-v2
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
OpenEdx
xblock-drag-and-drop-v2
Commits
fb6b6e73
Commit
fb6b6e73
authored
Dec 23, 2015
by
Tim Krones
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Improve default settings for DnDv2 blocks and explain "Optional
numerical value" and "Margin ±" settings for items.
parent
672aa8c0
Show whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
133 additions
and
57 deletions
+133
-57
README.md
+31
-20
drag_and_drop_v2/default_data.py
+36
-14
drag_and_drop_v2/drag_and_drop_v2.py
+10
-1
drag_and_drop_v2/public/css/drag_and_drop_edit.css
+9
-1
drag_and_drop_v2/templates/html/drag_and_drop_edit.html
+6
-3
drag_and_drop_v2/templates/html/js_templates.html
+12
-4
tests/integration/test_title_and_question.py
+2
-1
tests/unit/data/html/config_out.json
+1
-1
tests/unit/data/old/config_out.json
+1
-1
tests/unit/data/plain/config_out.json
+1
-1
tests/unit/test_basics.py
+24
-10
No files found.
README.md
View file @
fb6b6e73
...
...
@@ -8,29 +8,30 @@ The editor is fully guided. Features include:
*
custom target image
*
free target zone positioning and sizing
*
custom size items
*
custom zone labels
*
custom text and background colors for items
*
image items
*
decoy items that don't have a zone
*
feedback popups for both correct and incorrect attempts
*
introductory and final feedback
It
supports progressive grading and keeps progress across
The XBlock
supports progressive grading and keeps progress across
refreshes. All checking and record keeping is done on the server side.
The
screenshot shows the Drag and Drop XBlock rendered inside the edX
LMS before starting
before the user starts solving the problem:
The
following screenshot shows the Drag and Drop XBlock rendered
inside the edX LMS
before the user starts solving the problem:

This screenshot shows the XBlock after the student successfully
completed the
drag and d
rop problem:
completed the
Drag and D
rop problem:

Installation
------------
Install the requirements into the
p
ython virtual environment of your
Install the requirements into the
P
ython virtual environment of your
`edx-platform`
installation by running the following command from the
root folder:
...
...
@@ -41,12 +42,12 @@ $ pip install -e .
Enabling in Studio
------------------
You can enable the Drag and Drop XBlock in
studio through the a
dvanced
s
ettings.
You can enable the Drag and Drop XBlock in
Studio through the A
dvanced
S
ettings.
1.
From the main page of a specific course, navigate to
`Settings ->
Advanced Settings`
from the top menu.
2.
Check for the
`
advanced_modules
`
policy key, and add
2.
Check for the
`
Advanced Module List
`
policy key, and add
`"drag-and-drop-v2"`
to the policy value list.
3.
Click the "Save changes" button.
...
...
@@ -54,13 +55,13 @@ Usage
-----
The Drag and Drop XBlock features an interactive editor. Add the Drag
and Drop component to a lesson, then click the
'Edit'
button.
and Drop component to a lesson, then click the
`EDIT`
button.

In the first step, you can set some basic properties of the component,
such as the title,
question text that rendered
above the background
image, the introduct
ion
feedback (shown initially) and the final
such as the title,
the question text to render
above the background
image, the introduct
ory
feedback (shown initially) and the final
feedback (shown after the student successfully completes the drag and
drop problem).
...
...
@@ -69,21 +70,31 @@ drop problem).
In the next step, you set the background image URL and define the
properties of the drop zones. The properties include the title/text
rendered in the drop zone, the zone's dimensions and position
coordinates. You can define an arbitrary number of drop zones as long
as their titles are unique.
coordinates. In this step you can also specify whether you would like
zone labels to be shown to students or not. It is possible to define
an arbitrary number of drop zones as long as their titles are unique.

In the final step, you define the drag items. A drag item can contain
either text or an image. You can define the success and error feedback
texts. The feedback text is displayed in a popup after the student
drops the item into a zone - the success feedback is shown if the item
is dropped into the correct zone, while the error feedback is shown
when dropping the item into a wrong drop zone.
either text or an image. You can define custom success and error feedback
for each item. The feedback text is displayed in a popup after the student
drops the item on a zone - the success feedback is 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.
Additionally, items can have a numerical value (and an optional error
margin) associated with them. When a student 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 student submits is
checked against the expected value for the item. If you also specify a
margin, the value entered by the student will be considered correct if
it does not differ from the expected value by more than that margin
(and incorrect otherwise).

The zone that
the item belongs
is selected from a dropdown that
The zone that
an item belongs to
is selected from a dropdown that
includes all drop zones defined in the previous step and a
`none`
option that can be used for "decoy" items - items that don't belong to
any zone.
...
...
drag_and_drop_v2/default_data.py
View file @
fb6b6e73
from
.utils
import
_
INCORRECT_FEEDBACK
=
_
(
"No, this item does not belong here. Try again."
)
CORRECT_FEEDBACK
=
_
(
"Correct! This one belongs to {zone}."
)
DEFAULT_DATA
=
{
"zones"
:
[
{
"index"
:
1
,
"id"
:
"zone-1"
,
"title"
:
_
(
"
Zone 1
"
),
"title"
:
_
(
"
The Top Zone
"
),
"x"
:
160
,
"y"
:
30
,
"width"
:
196
,
...
...
@@ -14,43 +17,62 @@ DEFAULT_DATA = {
{
"index"
:
2
,
"id"
:
"zone-2"
,
"title"
:
_
(
"
Zone 2
"
),
"title"
:
_
(
"
The Middle Zone
"
),
"x"
:
86
,
"y"
:
210
,
"width"
:
340
,
"height"
:
140
,
"height"
:
138
,
},
{
"index"
:
3
,
"id"
:
"zone-3"
,
"title"
:
_
(
"The Bottom Zone"
),
"x"
:
15
,
"y"
:
350
,
"width"
:
485
,
"height"
:
135
,
}
],
"items"
:
[
{
"displayName"
:
"
1
"
,
"displayName"
:
"
Goes to the top
"
,
"feedback"
:
{
"incorrect"
:
_
(
"No, 1 does not belong here"
)
,
"correct"
:
_
(
"Yes, it's a 1
"
)
"incorrect"
:
INCORRECT_FEEDBACK
,
"correct"
:
CORRECT_FEEDBACK
.
format
(
zone
=
"the top
"
)
},
"zone"
:
"
Zone 1
"
,
"zone"
:
"
The Top Zone
"
,
"imageURL"
:
""
,
"id"
:
0
,
},
{
"displayName"
:
"
2
"
,
"displayName"
:
"
Goes to the middle
"
,
"feedback"
:
{
"incorrect"
:
_
(
"No, 2 does not belong here"
)
,
"correct"
:
_
(
"Yes, it's a 2
"
)
"incorrect"
:
INCORRECT_FEEDBACK
,
"correct"
:
CORRECT_FEEDBACK
.
format
(
zone
=
"the middle
"
)
},
"zone"
:
"
Zone 2
"
,
"zone"
:
"
The Middle Zone
"
,
"imageURL"
:
""
,
"id"
:
1
,
},
{
"displayName"
:
"X"
,
"displayName"
:
"Goes to the bottom"
,
"feedback"
:
{
"incorrect"
:
INCORRECT_FEEDBACK
,
"correct"
:
CORRECT_FEEDBACK
.
format
(
zone
=
"the bottom"
)
},
"zone"
:
"The Bottom Zone"
,
"imageURL"
:
""
,
"id"
:
2
,
},
{
"displayName"
:
"I don't belong anywhere"
,
"feedback"
:
{
"incorrect"
:
_
(
"You silly, there are no zones for
X
"
),
"incorrect"
:
_
(
"You silly, there are no zones for
this one.
"
),
"correct"
:
""
},
"zone"
:
"none"
,
"imageURL"
:
""
,
"id"
:
2
,
"id"
:
3
,
},
],
"feedback"
:
{
...
...
drag_and_drop_v2/drag_and_drop_v2.py
View file @
fb6b6e73
...
...
@@ -144,11 +144,20 @@ class DragAndDropBlock(XBlock):
item
[
'inputOptions'
]
=
'inputOptions'
in
item
return
items
def
title_with_points
():
"""
Build title using `display_name` and `weight` of this Drag and Drop exercise.
"""
if
self
.
weight
==
1
:
return
"{title} (1 point possible)"
.
format
(
title
=
self
.
display_name
)
else
:
return
"{title} ({max_grade} points possible)"
.
format
(
title
=
self
.
display_name
,
max_grade
=
self
.
weight
)
return
{
"zones"
:
self
.
data
.
get
(
'zones'
,
[]),
"display_zone_labels"
:
self
.
data
.
get
(
'displayLabels'
,
False
),
"items"
:
items_without_answers
(),
"title"
:
self
.
display_name
,
"title"
:
title_with_points
()
,
"show_title"
:
self
.
show_title
,
"question_text"
:
self
.
question_text
,
"show_question_header"
:
self
.
show_question_header
,
...
...
drag_and_drop_v2/public/css/drag_and_drop_edit.css
View file @
fb6b6e73
...
...
@@ -203,7 +203,15 @@
.xblock--drag-and-drop--editor
.items-form
.item-numerical-value
,
.xblock--drag-and-drop--editor
.items-form
.item-numerical-margin
{
width
:
60px
;
margin-right
:
1%
;
}
.xblock--drag-and-drop--editor
.items-form
.item-numerical-value
{
width
:
620px
;
}
.xblock--drag-and-drop--editor
.items-form
.item-numerical-margin
{
width
:
578px
;
}
.xblock--drag-and-drop--editor
.items-form
textarea
{
...
...
drag_and_drop_v2/templates/html/drag_and_drop_edit.html
View file @
fb6b6e73
...
...
@@ -20,7 +20,7 @@
</label>
<h3>
{% trans "Maximum score" %}
</h3>
<input
class=
"weight"
value=
"1"
value=
"{{ self.weight }}"
/>
<input
class=
"weight"
value=
"1"
value=
"{{ self.weight }}"
/>
<h3>
{% trans "Question text" %}
</h3>
<textarea
class=
"question-text"
>
{{ self.question_text }}
</textarea>
...
...
@@ -30,7 +30,7 @@
{% trans "Show \"Question\" heading" %}
</label>
<h3>
{% trans "Introduct
ion
Feedback" %}
</h3>
<h3>
{% trans "Introduct
ory
Feedback" %}
</h3>
<textarea
class=
"intro-feedback"
>
{{ self.data.feedback.start }}
</textarea>
<h3>
{% trans "Final Feedback" %}
</h3>
...
...
@@ -48,7 +48,10 @@
<h3
id=
"background-url-label"
>
{% trans "Background URL" %}
</h3>
<input
type=
"text"
class=
"url-input"
aria-labelledby=
"background-url-label"
>
<input
type=
"text"
class=
"url-input"
aria-labelledby=
"background-url-label"
placeholder=
"e.g. http://example.com/background.png or /static/background.png"
>
<h3
id=
"background-description-label"
>
{% trans "Background description" %}
</h3>
...
...
drag_and_drop_v2/templates/html/js_templates.html
View file @
fb6b6e73
...
...
@@ -113,12 +113,20 @@
value
=
"{{ height }}"
/>
<
/div
>
<
div
class
=
"row"
>
<
label
for
=
"item-{{id}}-numerical-value"
>
{{
i18n
"Optional numerical value"
}}
<
/label
>
<
input
type
=
"text"
<
label
for
=
"item-{{id}}-numerical-value"
>
{{
i18n
"Optional numerical value (if you set this, students 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 }}"
/>
<
label
for
=
"item-{{id}}-numerical-margin"
>
{{
i18n
"Margin ±"
}}
<
/label
>
<
input
type
=
"text"
<
/div
>
<
div
class
=
"row"
>
<
label
for
=
"item-{{id}}-numerical-margin"
>
{{
i18n
"Margin ± (to be considered correct, value entered by user must not differ from expected value by more than this)"
}}
<
/label
>
<
input
type
=
"number"
step
=
"0.1"
id
=
"item-{{id}}-numerical-margin"
class
=
"item-numerical-margin"
value
=
"{{ numericalMargin }}"
/>
<
/div
>
...
...
tests/integration/test_title_and_question.py
View file @
fb6b6e73
...
...
@@ -53,7 +53,8 @@ class TestDragAndDropTitleAndQuestion(BaseIntegrationTest):
page
=
self
.
go_to_page
(
const_page_name
)
if
show_title
:
problem_header
=
page
.
find_element_by_css_selector
(
'h2.problem-header'
)
self
.
assertEqual
(
self
.
get_element_html
(
problem_header
),
display_name
)
expected_header
=
display_name
+
' (1 point possible)'
self
.
assertEqual
(
self
.
get_element_html
(
problem_header
),
expected_header
)
else
:
with
self
.
assertRaises
(
NoSuchElementException
):
page
.
find_element_by_css_selector
(
'h2.problem-header'
)
tests/unit/data/html/config_out.json
View file @
fb6b6e73
{
"title"
:
"DnDv2 XBlock with HTML instructions"
,
"title"
:
"DnDv2 XBlock with HTML instructions
(1 point possible)
"
,
"show_title"
:
false
,
"question_text"
:
"Solve this <strong>drag-and-drop</strong> problem."
,
"show_question_header"
:
false
,
...
...
tests/unit/data/old/config_out.json
View file @
fb6b6e73
{
"title"
:
"Drag and Drop"
,
"title"
:
"Drag and Drop
(1 point possible)
"
,
"show_title"
:
true
,
"question_text"
:
""
,
"show_question_header"
:
true
,
...
...
tests/unit/data/plain/config_out.json
View file @
fb6b6e73
{
"title"
:
"DnDv2 XBlock with plain text instructions"
,
"title"
:
"DnDv2 XBlock with plain text instructions
(1 point possible)
"
,
"show_title"
:
true
,
"question_text"
:
"Can you solve this drag-and-drop problem?"
,
"show_question_header"
:
true
,
...
...
tests/unit/test_basics.py
View file @
fb6b6e73
...
...
@@ -31,7 +31,7 @@ class BasicTests(TestCaseMixin, unittest.TestCase):
items
=
config
.
pop
(
"items"
)
self
.
assertEqual
(
config
,
{
"display_zone_labels"
:
False
,
"title"
:
"Drag and Drop"
,
"title"
:
"Drag and Drop
(1 point possible)
"
,
"show_title"
:
True
,
"question_text"
:
""
,
"show_question_header"
:
True
,
...
...
@@ -44,8 +44,8 @@ class BasicTests(TestCaseMixin, unittest.TestCase):
self
.
assertEqual
(
zones
,
[
{
"index"
:
1
,
"title"
:
"Zone 1"
,
"id"
:
"zone-1"
,
"title"
:
"The Top Zone"
,
"x"
:
160
,
"y"
:
30
,
"width"
:
196
,
...
...
@@ -53,19 +53,29 @@ class BasicTests(TestCaseMixin, unittest.TestCase):
},
{
"index"
:
2
,
"title"
:
"Zone 2"
,
"id"
:
"zone-2"
,
"title"
:
"The Middle Zone"
,
"x"
:
86
,
"y"
:
210
,
"width"
:
340
,
"height"
:
140
,
"height"
:
138
,
},
{
"index"
:
3
,
"id"
:
"zone-3"
,
"title"
:
"The Bottom Zone"
,
"x"
:
15
,
"y"
:
350
,
"width"
:
485
,
"height"
:
135
,
}
])
# Items should contain no answer data:
self
.
assertEqual
(
items
,
[
{
"id"
:
0
,
"displayName"
:
"1"
,
"imageURL"
:
""
,
"inputOptions"
:
False
},
{
"id"
:
1
,
"displayName"
:
"2"
,
"imageURL"
:
""
,
"inputOptions"
:
False
},
{
"id"
:
2
,
"displayName"
:
"X"
,
"imageURL"
:
""
,
"inputOptions"
:
False
},
{
"id"
:
0
,
"displayName"
:
"Goes to the top"
,
"imageURL"
:
""
,
"inputOptions"
:
False
},
{
"id"
:
1
,
"displayName"
:
"Goes to the middle"
,
"imageURL"
:
""
,
"inputOptions"
:
False
},
{
"id"
:
2
,
"displayName"
:
"Goes to the bottom"
,
"imageURL"
:
""
,
"inputOptions"
:
False
},
{
"id"
:
3
,
"displayName"
:
"I don't belong anywhere"
,
"imageURL"
:
""
,
"inputOptions"
:
False
},
])
def
test_ajax_solve_and_reset
(
self
):
...
...
@@ -81,10 +91,12 @@ class BasicTests(TestCaseMixin, unittest.TestCase):
})
assert_user_state_empty
()
# Drag both items into the correct spot:
data
=
{
"val"
:
0
,
"zone"
:
"Zone 1"
,
"x_percent"
:
"33
%
"
,
"y_percent"
:
"11
%
"
}
# Drag three items into the correct spot:
data
=
{
"val"
:
0
,
"zone"
:
"The Top Zone"
,
"x_percent"
:
"33
%
"
,
"y_percent"
:
"11
%
"
}
self
.
call_handler
(
'do_attempt'
,
data
)
data
=
{
"val"
:
1
,
"zone"
:
"The Middle Zone"
,
"x_percent"
:
"67
%
"
,
"y_percent"
:
"80
%
"
}
self
.
call_handler
(
'do_attempt'
,
data
)
data
=
{
"val"
:
1
,
"zone"
:
"Zone 2"
,
"x_percent"
:
"67
%
"
,
"y_percent"
:
"80
%
"
}
data
=
{
"val"
:
2
,
"zone"
:
"The Bottom Zone"
,
"x_percent"
:
"99
%
"
,
"y_percent"
:
"95
%
"
}
self
.
call_handler
(
'do_attempt'
,
data
)
# Check the result:
...
...
@@ -92,11 +104,13 @@ class BasicTests(TestCaseMixin, unittest.TestCase):
self
.
assertEqual
(
self
.
block
.
item_state
,
{
'0'
:
{
'x_percent'
:
'33
%
'
,
'y_percent'
:
'11
%
'
},
'1'
:
{
'x_percent'
:
'67
%
'
,
'y_percent'
:
'80
%
'
},
'2'
:
{
'x_percent'
:
'99
%
'
,
'y_percent'
:
'95
%
'
},
})
self
.
assertEqual
(
self
.
call_handler
(
'get_user_state'
),
{
'items'
:
{
'0'
:
{
'x_percent'
:
'33
%
'
,
'y_percent'
:
'11
%
'
,
'correct_input'
:
True
},
'1'
:
{
'x_percent'
:
'67
%
'
,
'y_percent'
:
'80
%
'
,
'correct_input'
:
True
},
'2'
:
{
'x_percent'
:
'99
%
'
,
'y_percent'
:
'95
%
'
,
'correct_input'
:
True
},
},
'finished'
:
True
,
'overall_feedback'
:
DEFAULT_FINISH_FEEDBACK
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment