Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
X
xblock-vectordraw
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
edx
xblock-vectordraw
Commits
d2fe0450
Commit
d2fe0450
authored
Nov 24, 2015
by
Tim Krones
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Make LMS and Studio functionality accessible.
parent
c3a098db
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
888 additions
and
67 deletions
+888
-67
tests/integration/test_vectordraw.py
+267
-22
tests/integration/xml/custom.xml
+1
-0
vectordraw/public/css/vectordraw.css
+36
-7
vectordraw/public/css/vectordraw_edit.css
+104
-0
vectordraw/public/js/vectordraw.js
+125
-25
vectordraw/public/js/vectordraw_edit.js
+158
-1
vectordraw/templates/html/vectordraw.html
+72
-12
vectordraw/templates/html/vectordraw_edit.html
+77
-0
vectordraw/vectordraw.py
+48
-0
No files found.
tests/integration/test_vectordraw.py
View file @
d2fe0450
...
...
@@ -29,6 +29,10 @@ class TestVectorDraw(StudioEditableBaseTest):
else
:
self
.
fail
(
errmsg
)
def
check_hidden_text
(
self
,
selector
,
expected_text
):
hidden_text
=
self
.
browser
.
execute_script
(
"return $('{}').text();"
.
format
(
selector
))
self
.
assertEquals
(
hidden_text
,
expected_text
)
def
check_title_and_description
(
self
,
expected_title
=
"Vector Drawing"
,
expected_description
=
None
):
title
=
self
.
exercise
.
find_element_by_css_selector
(
"h2"
)
self
.
assertEquals
(
title
.
text
,
expected_title
)
...
...
@@ -70,6 +74,8 @@ class TestVectorDraw(StudioEditableBaseTest):
self
.
assertTrue
(
background
.
is_displayed
())
src
=
background
.
get_attribute
(
"xlink:href"
)
self
.
assertEquals
(
src
,
"https://github.com/open-craft/jsinput-vectordraw/raw/master/Notes_and_Examples/2_boxIncline_multiVector/box_on_incline.png"
)
alt
=
background
.
get_attribute
(
"alt"
)
self
.
assertEquals
(
alt
,
"A very informative description"
)
else
:
self
.
assert_not_present
(
board
,
...
...
@@ -78,29 +84,42 @@ class TestVectorDraw(StudioEditableBaseTest):
)
def
check_buttons
(
self
,
controls
,
add_vector_label
=
"Add Selected Force"
):
# "Add vector" button
add_vector
=
controls
.
find_element_by_css_selector
(
".add-vector"
)
self
.
assertEquals
(
add_vector
.
text
,
add_vector_label
)
# "Reset" button
reset
=
controls
.
find_element_by_css_selector
(
".reset"
)
self
.
assertEquals
(
reset
.
text
,
"Reset"
)
undo
=
controls
.
find_element_by_css_selector
(
".undo"
)
undo
.
find_element_by_css_selector
(
".fa.fa-undo"
)
reset
.
find_element_by_css_selector
(
".sr"
)
self
.
check_hidden_text
(
".reset > .sr"
,
"Reset board to initial state"
)
# "Redo" button
redo
=
controls
.
find_element_by_css_selector
(
".redo"
)
redo
.
find_element_by_css_selector
(
".fa.fa-repeat"
)
redo
.
find_element_by_css_selector
(
".sr"
)
self
.
check_hidden_text
(
".redo > .sr"
,
"Redo last action"
)
# "Undo" button
undo
=
controls
.
find_element_by_css_selector
(
".undo"
)
undo
.
find_element_by_css_selector
(
".fa.fa-undo"
)
undo
.
find_element_by_css_selector
(
".sr"
)
self
.
check_hidden_text
(
".undo > .sr"
,
"Undo last action"
)
def
check_vector_properties
(
self
,
menu
,
is_present
=
False
,
expected_label
=
"Vector Properties"
,
expected_name
=
None
,
expected_length
=
None
,
expected_angle
=
None
expected_name
=
None
,
expected_
tail
=
None
,
expected_
length
=
None
,
expected_angle
=
None
):
if
is_present
:
vector_properties
=
menu
.
find_element_by_css_selector
(
".vector-properties"
)
vector_properties_label
=
vector_properties
.
find_element_by_css_selector
(
"h3"
)
self
.
assertEquals
(
vector_properties_label
.
text
,
expected_label
)
vector_name
=
vector_properties
.
find_element_by_css_selector
(
".vector-prop-name"
)
self
.
assertEquals
(
vector_name
.
text
,
"name: {}"
.
format
(
expected_name
or
"-"
))
vector_length
=
vector_properties
.
find_element_by_css_selector
(
".vector-prop-length"
)
self
.
assertEquals
(
vector_length
.
text
,
"length: {}"
.
format
(
expected_length
or
"-"
))
vector_angle
=
vector_properties
.
find_element_by_css_selector
(
".vector-prop-angle"
)
self
.
assertTrue
(
vector_angle
.
text
.
startswith
(
"angle: {}"
.
format
(
expected_angle
or
"-"
)))
# Name
self
.
check_vector_property
(
vector_properties
,
"name"
,
"select"
,
"name:"
,
expected_name
or
"-"
)
# Tail
self
.
check_vector_property
(
vector_properties
,
"tail"
,
"input"
,
"tail position:"
,
expected_tail
or
"-"
)
# Length
self
.
check_vector_property
(
vector_properties
,
"length"
,
"input"
,
"length:"
,
expected_length
or
"-"
)
# Angle
self
.
check_vector_property
(
vector_properties
,
"angle"
,
"input"
,
"angle:"
,
expected_angle
or
"-"
)
# Slope
vector_slope
=
vector_properties
.
find_element_by_css_selector
(
".vector-prop-slope"
)
self
.
assertFalse
(
vector_slope
.
is_displayed
())
else
:
...
...
@@ -110,13 +129,36 @@ class TestVectorDraw(StudioEditableBaseTest):
"If show_vector_properties is set to False, menu should not show vector properties."
)
def
check_vector_property
(
self
,
vector_properties
,
property_name
,
input_type
,
expected_label
,
expected_value
=
None
):
vector_property
=
vector_properties
.
find_element_by_css_selector
(
".vector-prop-{}"
.
format
(
property_name
)
)
vector_property_label
=
vector_property
.
find_element_by_css_selector
(
"#vector-prop-{}-label"
.
format
(
property_name
)
)
self
.
assertEquals
(
vector_property_label
.
text
,
expected_label
)
vector_property_input
=
vector_property
.
find_element_by_css_selector
(
input_type
)
self
.
assertEquals
(
vector_property_input
.
get_attribute
(
"aria-labelledby"
),
"vector-prop-{}-label"
.
format
(
property_name
)
)
if
input_type
==
"input"
:
self
.
assertEquals
(
vector_property_input
.
get_attribute
(
"value"
),
expected_value
)
else
:
selected_option
=
vector_property_input
.
find_element_by_css_selector
(
'option[selected="selected"]'
)
self
.
assertEquals
(
selected_option
.
text
,
expected_value
)
def
check_actions
(
self
):
actions
=
self
.
exercise
.
find_element_by_css_selector
(
".action"
)
self
.
assertTrue
(
actions
.
is_displayed
())
check
=
actions
.
find_element_by_css_selector
(
".check"
)
self
.
assertEquals
(
check
.
text
,
"CHECK"
)
check
.
find_element_by_css_selector
(
".sr"
)
self
.
check_hidden_text
(
".check > .sr"
,
"Check your answer"
)
def
check_dropdown
(
self
,
controls
,
vectors
=
[],
points
=
[]):
def
check_add_dropdown
(
self
,
controls
,
vectors
=
[],
points
=
[]):
# Check dropdown
dropdown
=
controls
.
find_element_by_css_selector
(
"select"
)
if
not
vectors
and
not
points
:
self
.
assert_not_present
(
...
...
@@ -125,11 +167,17 @@ class TestVectorDraw(StudioEditableBaseTest):
"Dropdown should not list any vectors or points by default."
)
else
:
self
.
check_options
(
dropdown
,
vectors
,
"vector"
)
self
.
check_
add_
options
(
dropdown
,
vectors
,
"vector"
)
non_fixed_points
=
[
point
for
point
in
points
if
not
point
[
"fixed"
]]
self
.
check_options
(
dropdown
,
non_fixed_points
,
"point"
)
def
check_options
(
self
,
dropdown
,
elements
,
element_type
):
self
.
check_add_options
(
dropdown
,
non_fixed_points
,
"point"
)
# Check label
label_id
=
"element-list-add-label"
label_selector
=
"#"
+
label_id
controls
.
find_element_by_css_selector
(
label_selector
)
self
.
check_hidden_text
(
label_selector
,
"Select element to add to board"
)
self
.
assertEquals
(
dropdown
.
get_attribute
(
"aria-labelledby"
),
label_id
)
def
check_add_options
(
self
,
dropdown
,
elements
,
element_type
):
element_options
=
dropdown
.
find_elements_by_css_selector
(
'option[value^="{}-"]'
.
format
(
element_type
))
self
.
assertEquals
(
len
(
element_options
),
len
(
elements
))
for
element
,
element_option
in
zip
(
elements
,
element_options
):
...
...
@@ -137,6 +185,30 @@ class TestVectorDraw(StudioEditableBaseTest):
option_disabled
=
element_option
.
get_attribute
(
"disabled"
)
self
.
assertEquals
(
bool
(
option_disabled
),
element
[
"render"
])
def
check_edit_dropdown
(
self
,
menu
,
vectors
=
[],
points
=
[]):
vector_properties
=
menu
.
find_element_by_css_selector
(
".vector-properties"
)
# Check dropdown
dropdown
=
vector_properties
.
find_element_by_css_selector
(
"select"
)
if
not
vectors
and
not
points
:
options
=
dropdown
.
find_elements_by_css_selector
(
"option"
)
self
.
assertEquals
(
len
(
options
),
1
)
default_option
=
options
[
0
]
self
.
assertEquals
(
default_option
.
get_attribute
(
"value"
),
"-"
)
else
:
if
vectors
:
self
.
check_edit_options
(
dropdown
,
vectors
,
"vector"
)
if
points
:
non_fixed_points
=
[
point
for
point
in
points
if
not
point
[
"fixed"
]]
self
.
check_edit_options
(
dropdown
,
non_fixed_points
,
"point"
)
def
check_edit_options
(
self
,
dropdown
,
elements
,
element_type
):
element_options
=
dropdown
.
find_elements_by_css_selector
(
'option[value^="{}-"]'
.
format
(
element_type
))
self
.
assertEquals
(
len
(
element_options
),
len
(
elements
))
for
element
,
element_option
in
zip
(
elements
,
element_options
):
self
.
assertEquals
(
element_option
.
text
,
element
[
"name"
])
option_disabled
=
element_option
.
get_attribute
(
"disabled"
)
self
.
assertNotEquals
(
bool
(
option_disabled
),
element
[
"render"
])
def
check_vectors
(
self
,
board
,
vectors
):
line_elements
=
board
.
find_elements_by_css_selector
(
"line"
)
point_elements
=
board
.
find_elements_by_css_selector
(
"ellipse"
)
...
...
@@ -168,7 +240,8 @@ class TestVectorDraw(StudioEditableBaseTest):
self
.
assertEquals
(
board_has_point
,
point
[
"render"
])
def
board_has_line
(
self
,
position
,
line_elements
):
return
bool
(
self
.
find_line
(
position
,
line_elements
))
line
=
self
.
find_line
(
position
,
line_elements
)
return
bool
(
line
)
and
self
.
line_has_title
(
line
)
and
self
.
line_has_desc
(
line
)
def
board_has_point
(
self
,
position
,
point_elements
):
return
bool
(
self
.
find_point
(
position
,
point_elements
))
...
...
@@ -181,6 +254,16 @@ class TestVectorDraw(StudioEditableBaseTest):
return
True
return
False
def
line_has_title
(
self
,
line
):
title
=
line
.
find_element_by_css_selector
(
"title"
)
title_id
=
title
.
get_attribute
(
"id"
)
aria_labelledby
=
line
.
get_attribute
(
"aria-labelledby"
)
return
title_id
==
aria_labelledby
def
line_has_desc
(
self
,
line
):
aria_describedby
=
line
.
get_attribute
(
"aria-describedby"
)
return
aria_describedby
==
"jxgboard1-vector-properties"
def
find_line
(
self
,
position
,
line_elements
):
expected_line_position
=
position
.
items
()
for
line
in
line_elements
:
...
...
@@ -197,8 +280,8 @@ class TestVectorDraw(StudioEditableBaseTest):
expected_position
=
position
.
items
()
for
point
in
point_elements
:
point_position
=
{
"cx"
:
int
(
float
(
point
.
get_attribute
(
"cx"
))),
"cy"
:
int
(
float
(
point
.
get_attribute
(
"cy"
))),
"cx"
:
int
(
round
(
float
(
point
.
get_attribute
(
"cx"
)
))),
"cy"
:
int
(
round
(
float
(
point
.
get_attribute
(
"cy"
)
))),
}
.
items
()
if
point_position
==
expected_position
:
return
point
...
...
@@ -214,8 +297,9 @@ class TestVectorDraw(StudioEditableBaseTest):
# "Vector Properties" should display correct info
self
.
check_vector_properties
(
menu
,
is_present
=
True
,
expected_label
=
"Custom properties label"
,
expected_name
=
"N"
,
expected_length
=
"4.00"
,
expected_angle
=
"45.00"
expected_name
=
"N"
,
expected_
tail
=
"2.00, 2.00"
,
expected_
length
=
"4.00"
,
expected_angle
=
"45.00"
)
self
.
check_edit_dropdown
(
menu
,
vectors
)
def
add_point
(
self
,
board
,
points
):
menu
=
self
.
exercise
.
find_element_by_css_selector
(
".menu"
)
...
...
@@ -246,7 +330,7 @@ class TestVectorDraw(StudioEditableBaseTest):
# "Vector Properties" should display correct info
self
.
check_vector_properties
(
menu
,
is_present
=
True
,
expected_label
=
"Custom properties label"
,
expected_name
=
"N"
,
expected_length
=
"4.00"
,
expected_angle
=
"45.00"
expected_name
=
"N"
,
expected_
tail
=
"2.00, 2.00"
,
expected_
length
=
"4.00"
,
expected_angle
=
"45.00"
)
def
reset
(
self
,
board
,
vectors
,
points
):
...
...
@@ -277,6 +361,21 @@ class TestVectorDraw(StudioEditableBaseTest):
status_message
=
status
.
find_element_by_css_selector
(
".status-message"
)
self
.
assertEquals
(
status_message
.
text
,
expected_message
)
def
change_property
(
self
,
property_name
,
new_value
):
menu
=
self
.
exercise
.
find_element_by_css_selector
(
".menu"
)
vector_properties
=
menu
.
find_element_by_css_selector
(
".vector-properties"
)
vector_property
=
vector_properties
.
find_element_by_css_selector
(
".vector-prop-{}"
.
format
(
property_name
)
)
vector_property_input
=
vector_property
.
find_element_by_css_selector
(
"input"
)
# Enter new value
vector_property_input
.
clear
()
vector_property_input
.
send_keys
(
new_value
)
# Find "Update" button
update_button
=
vector_properties
.
find_element_by_css_selector
(
".vector-prop-update"
)
# Click "Update" button
update_button
.
click
()
def
test_defaults
(
self
):
self
.
load_scenario
(
"xml/defaults.xml"
)
...
...
@@ -305,9 +404,10 @@ class TestVectorDraw(StudioEditableBaseTest):
# Check menu
menu
=
self
.
exercise
.
find_element_by_css_selector
(
".menu"
)
controls
=
menu
.
find_element_by_css_selector
(
".controls"
)
self
.
check_dropdown
(
controls
)
self
.
check_
add_
dropdown
(
controls
)
self
.
check_buttons
(
controls
)
self
.
check_vector_properties
(
menu
,
is_present
=
True
)
self
.
check_edit_dropdown
(
menu
)
# Check actions
self
.
check_actions
()
...
...
@@ -398,11 +498,12 @@ class TestVectorDraw(StudioEditableBaseTest):
# Check menu
menu
=
self
.
exercise
.
find_element_by_css_selector
(
".menu"
)
controls
=
menu
.
find_element_by_css_selector
(
".controls"
)
self
.
check_dropdown
(
controls
,
vectors
,
points
)
self
.
check_
add_
dropdown
(
controls
,
vectors
,
points
)
self
.
check_buttons
(
controls
,
add_vector_label
=
"Custom button label"
)
show_vector_properties
=
params
[
"show_vector_properties"
]
if
show_vector_properties
:
self
.
check_vector_properties
(
menu
,
is_present
=
True
,
expected_label
=
"Custom properties label"
)
self
.
check_edit_dropdown
(
menu
,
vectors
,
points
)
else
:
self
.
check_vector_properties
(
menu
)
...
...
@@ -451,7 +552,7 @@ class TestVectorDraw(StudioEditableBaseTest):
menu
=
self
.
exercise
.
find_element_by_css_selector
(
".menu"
)
self
.
check_vector_properties
(
menu
,
is_present
=
True
,
expected_label
=
"Custom properties label"
,
expected_name
=
"N"
,
expected_length
=
"4.00"
,
expected_angle
=
"45.00"
expected_name
=
"N"
,
expected_
tail
=
"2.00, 2.00"
,
expected_
length
=
"4.00"
,
expected_angle
=
"45.00"
)
@data
(
...
...
@@ -758,3 +859,147 @@ class TestVectorDraw(StudioEditableBaseTest):
self
.
check_status
(
answer_correct
=
False
,
expected_message
=
"Vector N does not start at correct point."
)
@data
(
{
"show_vector_properties"
:
True
,
"vectors"
:
json
.
dumps
([
{
"name"
:
"N"
,
"description"
:
"Normal force - N"
,
"tail"
:
[
2
,
2
],
"length"
:
4
,
"angle"
:
45
,
"render"
:
False
,
"expected_line_position"
:
{
"x1"
:
347
,
"y1"
:
181
,
"x2"
:
402
,
"y2"
:
125
},
"expected_tail_position"
:
{
"cx"
:
347
,
"cy"
:
181
},
"expected_tip_position"
:
{
"cx"
:
411
,
"cy"
:
117
},
}
]),
"points"
:
json
.
dumps
([]),
"expected_result"
:
json
.
dumps
({})
}
)
def
test_change_tail_property
(
self
,
params
):
self
.
load_scenario
(
"xml/custom.xml"
,
params
=
params
)
board
=
self
.
exercise
.
find_element_by_css_selector
(
"#jxgboard1"
)
# Board should not show vector initially
vectors
=
json
.
loads
(
params
[
"vectors"
])
self
.
check_vectors
(
board
,
vectors
)
# Add vector
self
.
add_vector
(
board
,
vectors
)
# Change tail
self
.
change_property
(
"tail"
,
"3, 3"
)
# Check new position: Tail updated, tip updated
vectors
[
0
][
"expected_line_position"
]
=
{
'x1'
:
370
,
'y1'
:
159
,
'x2'
:
425
,
'y2'
:
102
}
vectors
[
0
][
"expected_tail_position"
]
=
{
'cx'
:
370
,
'cy'
:
159
}
vectors
[
0
][
"expected_tip_position"
]
=
{
'cx'
:
434
,
'cy'
:
94
}
self
.
check_vectors
(
board
,
vectors
)
@data
(
{
"show_vector_properties"
:
True
,
"vectors"
:
json
.
dumps
([
{
"name"
:
"N"
,
"description"
:
"Normal force - N"
,
"tail"
:
[
2
,
2
],
"length"
:
4
,
"angle"
:
45
,
"render"
:
False
,
"expected_line_position"
:
{
"x1"
:
347
,
"y1"
:
181
,
"x2"
:
402
,
"y2"
:
125
},
"expected_tail_position"
:
{
"cx"
:
347
,
"cy"
:
181
},
"expected_tip_position"
:
{
"cx"
:
411
,
"cy"
:
117
},
}
]),
"points"
:
json
.
dumps
([]),
"expected_result"
:
json
.
dumps
({})
}
)
def
test_change_length_property
(
self
,
params
):
self
.
load_scenario
(
"xml/custom.xml"
,
params
=
params
)
board
=
self
.
exercise
.
find_element_by_css_selector
(
"#jxgboard1"
)
# Board should not show vector initially
vectors
=
json
.
loads
(
params
[
"vectors"
])
self
.
check_vectors
(
board
,
vectors
)
# Add vector
self
.
add_vector
(
board
,
vectors
)
# Change tail
self
.
change_property
(
"length"
,
"6"
)
# Check new position: Tail unchanged, tip updated
vectors
[
0
][
"expected_line_position"
]
=
{
'x1'
:
347
,
'y1'
:
181
,
'x2'
:
434
,
'y2'
:
93
}
vectors
[
0
][
"expected_tail_position"
]
=
{
'cx'
:
347
,
'cy'
:
181
}
vectors
[
0
][
"expected_tip_position"
]
=
{
'cx'
:
443
,
'cy'
:
85
}
self
.
check_vectors
(
board
,
vectors
)
@data
(
{
"show_vector_properties"
:
True
,
"vectors"
:
json
.
dumps
([
{
"name"
:
"N"
,
"description"
:
"Normal force - N"
,
"tail"
:
[
2
,
2
],
"length"
:
4
,
"angle"
:
45
,
"render"
:
False
,
"expected_line_position"
:
{
"x1"
:
347
,
"y1"
:
181
,
"x2"
:
402
,
"y2"
:
125
},
"expected_tail_position"
:
{
"cx"
:
347
,
"cy"
:
181
},
"expected_tip_position"
:
{
"cx"
:
411
,
"cy"
:
117
},
}
]),
"points"
:
json
.
dumps
([]),
"expected_result"
:
json
.
dumps
({})
}
)
def
test_change_angle_property
(
self
,
params
):
self
.
load_scenario
(
"xml/custom.xml"
,
params
=
params
)
board
=
self
.
exercise
.
find_element_by_css_selector
(
"#jxgboard1"
)
# Board should not show vector initially
vectors
=
json
.
loads
(
params
[
"vectors"
])
self
.
check_vectors
(
board
,
vectors
)
# Add vector
self
.
add_vector
(
board
,
vectors
)
# Change tail
self
.
change_property
(
"angle"
,
"170"
)
# Check new position: Tail unchanged, tip updated
vectors
[
0
][
"expected_line_position"
]
=
{
'x1'
:
347
,
'y1'
:
181
,
'x2'
:
269
,
'y2'
:
167
}
vectors
[
0
][
"expected_tail_position"
]
=
{
'cx'
:
347
,
'cy'
:
181
}
vectors
[
0
][
"expected_tip_position"
]
=
{
'cx'
:
258
,
'cy'
:
165
}
self
.
check_vectors
(
board
,
vectors
)
@data
(
{
"show_vector_properties"
:
True
,
"vectors"
:
json
.
dumps
([
{
"name"
:
"N"
,
"description"
:
"Normal force - N"
,
"tail"
:
[
2
,
2
],
"length"
:
4
,
"angle"
:
45
,
"render"
:
False
,
"expected_line_position"
:
{
"x1"
:
347
,
"y1"
:
181
,
"x2"
:
402
,
"y2"
:
125
},
"expected_tail_position"
:
{
"cx"
:
347
,
"cy"
:
181
},
"expected_tip_position"
:
{
"cx"
:
411
,
"cy"
:
117
},
}
]),
"points"
:
json
.
dumps
([]),
"expected_result"
:
json
.
dumps
({})
}
)
def
test_change_property_invalid_input
(
self
,
params
):
self
.
load_scenario
(
"xml/custom.xml"
,
params
=
params
)
board
=
self
.
exercise
.
find_element_by_css_selector
(
"#jxgboard1"
)
# Board should not show vector initially
vectors
=
json
.
loads
(
params
[
"vectors"
])
self
.
check_vectors
(
board
,
vectors
)
# Add vector
self
.
add_vector
(
board
,
vectors
)
# Change tail
self
.
change_property
(
"tail"
,
"invalid"
)
# Check new position: Tail unchanged, tip unchanged
vectors
[
0
][
"expected_line_position"
]
=
{
'x1'
:
347
,
'y1'
:
181
,
'x2'
:
402
,
'y2'
:
125
}
vectors
[
0
][
"expected_tail_position"
]
=
{
'cx'
:
347
,
'cy'
:
181
}
vectors
[
0
][
"expected_tip_position"
]
=
{
'cx'
:
411
,
'cy'
:
117
}
self
.
check_vectors
(
board
,
vectors
)
tests/integration/xml/custom.xml
View file @
d2fe0450
...
...
@@ -12,6 +12,7 @@
vector_properties_label=
"Custom properties label"
background_url=
"https://github.com/open-craft/jsinput-vectordraw/raw/master/Notes_and_Examples/2_boxIncline_multiVector/box_on_incline.png"
background_width=
"20"
background_description=
"A very informative description"
vectors=
"{{ vectors }}"
points=
"{{ points }}"
expected_result=
"{{ expected_result }}"
...
...
vectordraw/public/css/vectordraw.css
View file @
d2fe0450
...
...
@@ -20,6 +20,8 @@
pointer-events
:
none
;
/* prevents cursor from turning into caret when over a label */
}
/* Menu */
.vectordraw_block
.menu
{
width
:
100%
;
}
...
...
@@ -41,7 +43,7 @@
font-size
:
18px
;
}
.vectordraw_block
.menu
.controls
button
{
.vectordraw_block
.menu
button
{
border
:
1px
solid
#1f628d
;
border-radius
:
5px
;
margin
:
4px
0
;
...
...
@@ -53,7 +55,7 @@
text-decoration
:
none
;
}
.vectordraw_block
.menu
.controls
button
:hover
{
.vectordraw_block
.menu
button
:hover
{
background
:
#c2e0f4
;
background-image
:
-webkit-linear-gradient
(
top
,
#c2e0f4
,
#add5f0
);
background-image
:
-moz-linear-gradient
(
top
,
#c2e0f4
,
#add5f0
);
...
...
@@ -85,12 +87,39 @@
margin
:
0
0
5px
;
}
.vectordraw_block
.menu
.vector-properties
.vector-prop-bold
{
font-weight
:
bold
;
.vectordraw_block
.menu
.vector-properties
.vector-prop-list
{
display
:
table
;
width
:
100%
}
.vectordraw_block
.menu
.vector-prop-slope
{
display
:
none
;
.vectordraw_block
.menu
.vector-properties
.vector-prop-list
.row
{
display
:
table-row
;
}
.vectordraw_block
.menu
.vector-properties
.vector-prop-list
.row
.vector-prop-name
,
.vectordraw_block
.menu
.vector-properties
.vector-prop-list
.row
.vector-prop-tail
,
.vectordraw_block
.menu
.vector-properties
.vector-prop-list
.row
.vector-prop-tail-label
,
.vectordraw_block
.menu
.vector-properties
.vector-prop-list
.row
.vector-prop-length
,
.vectordraw_block
.menu
.vector-properties
.vector-prop-list
.row
.vector-prop-length-label
,
.vectordraw_block
.menu
.vector-properties
.vector-prop-list
.row
.vector-prop-angle
,
.vectordraw_block
.menu
.vector-properties
.vector-prop-list
.row
.vector-prop-angle-label
,
.vectordraw_block
.menu
.vector-properties
.vector-prop-list
.row
.vector-prop-slope
,
.vectordraw_block
.menu
.vector-properties
.vector-prop-list
.row
.vector-prop-slope-label
,
.vectordraw_block
.menu
.vector-properties
.vector-prop-list
.row
.vector-prop-update
{
display
:
table-cell
;
width
:
50%
}
.vectordraw_block
.menu
.vector-properties
.vector-prop-list
.row
.vector-prop-name
,
.vectordraw_block
.menu
.vector-properties
.vector-prop-list
.row
.vector-prop-tail
,
.vectordraw_block
.menu
.vector-properties
.vector-prop-list
.row
.vector-prop-angle
{
padding-right
:
5px
;
}
.vectordraw_block
.menu
.vector-properties
.vector-prop-list
.row
select
,
.vectordraw_block
.menu
.vector-properties
.vector-prop-list
.row
input
{
float
:
right
;
width
:
50%
;
}
.vectordraw_block
.action
button
{
...
...
@@ -101,7 +130,7 @@
}
/* Make sure screen-reader content is hidden in the workbench: */
.vectordraw_block
.
action
.
sr
{
.vectordraw_block
.sr
{
display
:
none
;
border
:
0
;
clip
:
rect
(
0
0
0
0
);
...
...
vectordraw/public/css/vectordraw_edit.css
View file @
d2fe0450
...
...
@@ -27,3 +27,107 @@
pointer-events
:
none
;
/* prevents cursor from turning into caret when over a label */
}
/* Menu */
.vectordraw_edit_block
.menu
{
width
:
100%
;
}
.vectordraw_edit_block
.menu
.controls
{
border-top-left-radius
:
5px
;
border-top-right-radius
:
5px
;
border-top
:
2px
solid
#1f628d
;
border-left
:
2px
solid
#1f628d
;
border-right
:
2px
solid
#1f628d
;
padding
:
3px
;
background-color
:
#e0e0e0
;
font-size
:
0
;
}
.vectordraw_edit_block
.menu
.controls
select
{
width
:
160px
;
margin-right
:
3px
;
font-size
:
18px
;
}
.vectordraw_edit_block
.menu
button
{
border
:
1px
solid
#1f628d
;
border-radius
:
5px
;
margin
:
4px
0
;
padding
:
5px
10px
5px
10px
;
box-shadow
:
0
1px
3px
#666
;
background-color
:
#c2e0f4
;
color
:
#1f628d
;
font-size
:
14px
;
text-decoration
:
none
;
}
.vectordraw_edit_block
.menu
button
:hover
{
background
:
#c2e0f4
;
background-image
:
-webkit-linear-gradient
(
top
,
#c2e0f4
,
#add5f0
);
background-image
:
-moz-linear-gradient
(
top
,
#c2e0f4
,
#add5f0
);
background-image
:
-ms-linear-gradient
(
top
,
#c2e0f4
,
#add5f0
);
background-image
:
-o-linear-gradient
(
top
,
#c2e0f4
,
#add5f0
);
background-image
:
linear-gradient
(
to
bottom
,
#c2e0f4
,
#add5f0
);
text-decoration
:
none
;
}
.vectordraw_edit_block
.menu
.vector-properties
{
border-top
:
2px
solid
#1f628d
;
border-left
:
2px
solid
#1f628d
;
border-right
:
2px
solid
#1f628d
;
border-bottom
:
0px
none
;
padding
:
10px
;
font-size
:
16px
;
line-height
:
1.25
;
background-color
:
#f7f7f7
;
}
.vectordraw_edit_block
.menu
.vector-properties
h3
{
font-size
:
16px
;
margin
:
0
0
5px
;
}
.vectordraw_edit_block
.menu
.vector-properties
.vector-prop-list
{
display
:
table
;
width
:
100%
}
.vectordraw_edit_block
.menu
.vector-properties
.vector-prop-list
.row
{
display
:
table-row
;
}
.vectordraw_edit_block
.menu
.vector-properties
.vector-prop-list
.row
.vector-prop-name
,
.vectordraw_edit_block
.menu
.vector-properties
.vector-prop-list
.row
.vector-prop-tail
,
.vectordraw_edit_block
.menu
.vector-properties
.vector-prop-list
.row
.vector-prop-tail-label
,
.vectordraw_edit_block
.menu
.vector-properties
.vector-prop-list
.row
.vector-prop-length
,
.vectordraw_edit_block
.menu
.vector-properties
.vector-prop-list
.row
.vector-prop-length-label
,
.vectordraw_edit_block
.menu
.vector-properties
.vector-prop-list
.row
.vector-prop-angle
,
.vectordraw_edit_block
.menu
.vector-properties
.vector-prop-list
.row
.vector-prop-angle-label
,
.vectordraw_edit_block
.menu
.vector-properties
.vector-prop-list
.row
.vector-prop-slope
,
.vectordraw_edit_block
.menu
.vector-properties
.vector-prop-list
.row
.vector-prop-slope-label
,
.vectordraw_edit_block
.menu
.vector-properties
.vector-prop-list
.row
.vector-prop-update
,
.vectordraw_edit_block
.menu
.vector-properties
.vector-prop-list
.row
.vector-remove
{
display
:
table-cell
;
width
:
50%
}
.vectordraw_edit_block
.menu
.vector-properties
.vector-prop-list
.row
.vector-prop-name
,
.vectordraw_edit_block
.menu
.vector-properties
.vector-prop-list
.row
.vector-prop-tail
,
.vectordraw_edit_block
.menu
.vector-properties
.vector-prop-list
.row
.vector-prop-angle
{
padding-right
:
5px
;
}
.vectordraw_edit_block
.menu
.vector-properties
.vector-prop-list
.row
select
,
.vectordraw_edit_block
.menu
.vector-properties
.vector-prop-list
.row
input
{
float
:
right
;
width
:
50%
;
}
.vectordraw_edit_block
.menu
.vector-properties
.vector-prop-list
.row
.vector-remove
{
vertical-align
:
bottom
;
}
.vectordraw_edit_block
.menu
.vector-properties
.vector-prop-list
.row
.vector-remove
button
{
float
:
right
;
}
vectordraw/public/js/vectordraw.js
View file @
d2fe0450
...
...
@@ -16,6 +16,8 @@ function VectorDrawXBlock(runtime, element, init_args) {
this
.
element
.
on
(
'click'
,
'.add-vector'
,
this
.
addElementFromList
.
bind
(
this
));
this
.
element
.
on
(
'click'
,
'.undo'
,
this
.
undo
.
bind
(
this
));
this
.
element
.
on
(
'click'
,
'.redo'
,
this
.
redo
.
bind
(
this
));
this
.
element
.
on
(
'change'
,
'.menu .element-list-edit'
,
this
.
onEditStart
.
bind
(
this
));
this
.
element
.
on
(
'click'
,
'.menu .vector-prop-update'
,
this
.
onEditSubmit
.
bind
(
this
));
// Prevents default image drag and drop actions in some browsers.
this
.
element
.
on
(
'mousedown'
,
'.jxgboard image'
,
function
(
evt
)
{
evt
.
preventDefault
();
});
...
...
@@ -23,6 +25,7 @@ function VectorDrawXBlock(runtime, element, init_args) {
};
VectorDraw
.
prototype
.
render
=
function
()
{
$
(
'.vector-prop-slope'
,
this
.
element
).
hide
();
// Assign the jxgboard element a random unique ID,
// because JXG.JSXGraph.initBoard needs it.
this
.
element
.
find
(
'.jxgboard'
).
prop
(
'id'
,
_
.
uniqueId
(
'jxgboard'
));
...
...
@@ -52,7 +55,8 @@ function VectorDrawXBlock(runtime, element, init_args) {
function
drawBackground
(
bg
,
ratio
)
{
var
height
=
(
bg
.
height
)
?
bg
.
height
:
bg
.
width
*
ratio
;
var
coords
=
(
bg
.
coords
)
?
bg
.
coords
:
[
-
bg
.
width
/
2
,
-
height
/
2
];
self
.
board
.
create
(
'image'
,
[
bg
.
src
,
coords
,
[
bg
.
width
,
height
]],
{
fixed
:
true
});
var
image
=
self
.
board
.
create
(
'image'
,
[
bg
.
src
,
coords
,
[
bg
.
width
,
height
]],
{
fixed
:
true
});
$
(
image
.
rendNode
).
attr
(
'alt'
,
bg
.
description
);
}
if
(
this
.
settings
.
background
)
{
...
...
@@ -64,9 +68,9 @@ function VectorDrawXBlock(runtime, element, init_args) {
}
}
var
noOptionSelected
=
true
;
var
no
Add
OptionSelected
=
true
;
function
render
OrEnableOption
(
element
,
idx
,
type
,
board
)
{
function
render
AndSetMenuOptions
(
element
,
idx
,
type
,
board
)
{
if
(
element
.
render
)
{
if
(
type
===
'point'
)
{
board
.
renderPoint
(
idx
);
...
...
@@ -74,25 +78,39 @@ function VectorDrawXBlock(runtime, element, init_args) {
board
.
renderVector
(
idx
);
}
}
else
{
// Enable corresponding option in menu ...
var
option
=
board
.
get
MenuOption
(
type
,
idx
);
o
ption
.
prop
(
'disabled'
,
false
);
// Enable corresponding option in menu
for adding vectors
...
var
addOption
=
board
.
getAdd
MenuOption
(
type
,
idx
);
addO
ption
.
prop
(
'disabled'
,
false
);
// ... and select it if no option is currently selected
if
(
noOptionSelected
)
{
o
ption
.
prop
(
'selected'
,
true
);
noOptionSelected
=
false
;
if
(
no
Add
OptionSelected
)
{
addO
ption
.
prop
(
'selected'
,
true
);
no
Add
OptionSelected
=
false
;
}
// Disable corresponding option in menu for editing vectors
var
editOption
=
board
.
getEditMenuOption
(
type
,
idx
);
editOption
.
prop
(
'disabled'
,
true
);
}
}
// a11y
// Generate and set unique ID for "Vector Properties";
// this is necessary to ensure that "aria-describedby" associations
// between vectors and the "Vector Properties" box don't break
// when multiple boards are present:
var
vectorProperties
=
$
(
".vector-properties"
,
element
);
vectorProperties
.
attr
(
"id"
,
id
+
"-vector-properties"
);
// Draw vectors and points
this
.
settings
.
points
.
forEach
(
function
(
point
,
idx
)
{
render
OrEnableOption
(
point
,
idx
,
'point'
,
this
);
render
AndSetMenuOptions
(
point
,
idx
,
'point'
,
this
);
},
this
);
this
.
settings
.
vectors
.
forEach
(
function
(
vec
,
idx
)
{
render
OrEnableOption
(
vec
,
idx
,
'vector'
,
this
);
render
AndSetMenuOptions
(
vec
,
idx
,
'vector'
,
this
);
},
this
);
// Set up event handlers
this
.
board
.
on
(
'down'
,
this
.
onBoardDown
.
bind
(
this
));
this
.
board
.
on
(
'move'
,
this
.
onBoardMove
.
bind
(
this
));
this
.
board
.
on
(
'up'
,
this
.
onBoardUp
.
bind
(
this
));
...
...
@@ -110,7 +128,7 @@ function VectorDrawXBlock(runtime, element, init_args) {
this
.
board
.
create
(
'point'
,
coords
,
point
.
style
);
if
(
!
point
.
fixed
)
{
// Disable the <option> element corresponding to point.
var
option
=
this
.
getMenuOption
(
'point'
,
idx
);
var
option
=
this
.
get
Add
MenuOption
(
'point'
,
idx
);
option
.
prop
(
'disabled'
,
true
).
prop
(
'selected'
,
false
);
}
};
...
...
@@ -121,7 +139,7 @@ function VectorDrawXBlock(runtime, element, init_args) {
if
(
object
)
{
this
.
board
.
removeAncestors
(
object
);
// Enable the <option> element corresponding to point.
var
option
=
this
.
getMenuOption
(
'point'
,
idx
);
var
option
=
this
.
get
Add
MenuOption
(
'point'
,
idx
);
option
.
prop
(
'disabled'
,
false
);
}
};
...
...
@@ -176,6 +194,7 @@ function VectorDrawXBlock(runtime, element, init_args) {
// Not sure why, but including labelColor in attributes above doesn't work,
// it only works when set explicitly with setAttribute.
tip
.
setAttribute
({
labelColor
:
style
.
labelColor
});
tip
.
label
.
setAttribute
({
fontsize
:
18
,
highlightStrokeColor
:
'black'
});
var
line_type
=
(
vec
.
type
===
'vector'
)
?
'arrow'
:
vec
.
type
;
var
line
=
this
.
board
.
create
(
line_type
,
[
tail
,
tip
],
{
...
...
@@ -184,12 +203,23 @@ function VectorDrawXBlock(runtime, element, init_args) {
strokeColor
:
style
.
color
});
tip
.
label
.
setAttribute
({
fontsize
:
18
,
highlightStrokeColor
:
'black'
});
// Disable the <option> element corresponding to vector.
var
option
=
this
.
getMenuOption
(
'vector'
,
idx
);
var
option
=
this
.
get
Add
MenuOption
(
'vector'
,
idx
);
option
.
prop
(
'disabled'
,
true
).
prop
(
'selected'
,
false
);
// a11y
var
lineElement
=
$
(
line
.
rendNode
);
var
lineID
=
lineElement
.
attr
(
"id"
);
var
titleID
=
lineID
+
"-title"
;
var
titleElement
=
$
(
"<title>"
).
attr
(
"id"
,
titleID
).
text
(
vec
.
name
);
lineElement
.
append
(
titleElement
);
lineElement
.
attr
(
"aria-labelledby"
,
titleID
);
var
vectorProperties
=
$
(
".vector-properties"
,
element
);
lineElement
.
attr
(
"aria-describedby"
,
vectorProperties
.
attr
(
"id"
));
return
line
;
};
...
...
@@ -199,17 +229,21 @@ function VectorDrawXBlock(runtime, element, init_args) {
if
(
object
)
{
this
.
board
.
removeAncestors
(
object
);
// Enable the <option> element corresponding to vector.
var
option
=
this
.
getMenuOption
(
'vector'
,
idx
);
var
option
=
this
.
get
Add
MenuOption
(
'vector'
,
idx
);
option
.
prop
(
'disabled'
,
false
);
}
};
VectorDraw
.
prototype
.
getMenuOption
=
function
(
type
,
idx
)
{
return
this
.
element
.
find
(
'.menu option[value='
+
type
+
'-'
+
idx
+
']'
);
VectorDraw
.
prototype
.
getAddMenuOption
=
function
(
type
,
idx
)
{
return
this
.
element
.
find
(
'.menu .element-list-add option[value='
+
type
+
'-'
+
idx
+
']'
);
};
VectorDraw
.
prototype
.
getEditMenuOption
=
function
(
type
,
idx
)
{
return
this
.
element
.
find
(
'.menu .element-list-edit option[value='
+
type
+
'-'
+
idx
+
']'
);
};
VectorDraw
.
prototype
.
getSelectedElement
=
function
()
{
var
selector
=
this
.
element
.
find
(
'.menu
select
'
).
val
();
var
selector
=
this
.
element
.
find
(
'.menu
.element-list-add
'
).
val
();
if
(
selector
)
{
selector
=
selector
.
split
(
'-'
);
return
{
...
...
@@ -220,6 +254,11 @@ function VectorDrawXBlock(runtime, element, init_args) {
return
{};
};
VectorDraw
.
prototype
.
enableEditOption
=
function
(
selectedElement
)
{
var
editOption
=
this
.
getEditMenuOption
(
selectedElement
.
type
,
selectedElement
.
idx
);
editOption
.
prop
(
'disabled'
,
false
);
};
VectorDraw
.
prototype
.
addElementFromList
=
function
()
{
this
.
pushHistory
();
var
selected
=
this
.
getSelectedElement
();
...
...
@@ -228,11 +267,14 @@ function VectorDrawXBlock(runtime, element, init_args) {
}
else
{
this
.
renderPoint
(
selected
.
idx
);
}
// Enable option corresponding to selected element in menu for selecting element to edit
this
.
enableEditOption
(
selected
);
};
VectorDraw
.
prototype
.
reset
=
function
()
{
this
.
pushHistory
();
JXG
.
JSXGraph
.
freeBoard
(
this
.
board
);
this
.
resetVectorProperties
();
this
.
render
();
};
...
...
@@ -298,27 +340,47 @@ function VectorDrawXBlock(runtime, element, init_args) {
x2
=
vector
.
point2
.
X
(),
y2
=
vector
.
point2
.
Y
();
var
length
=
vec_settings
.
length_factor
*
Math
.
sqrt
(
Math
.
pow
(
x2
-
x1
,
2
)
+
Math
.
pow
(
y2
-
y1
,
2
));
var
slope
=
(
y2
-
y1
)
/
(
x2
-
x1
);
var
angle
=
((
Math
.
atan2
(
y2
-
y1
,
x2
-
x1
)
/
Math
.
PI
*
180
)
-
vec_settings
.
base_angle
)
%
360
;
if
(
angle
<
0
)
{
angle
+=
360
;
}
$
(
'.vector-prop-name .value'
,
this
.
element
).
html
(
vector
.
point2
.
name
);
// labels are stored as point2 names
$
(
'.vector-prop-angle .value'
,
this
.
element
).
html
(
angle
.
toFixed
(
2
));
var
slope
=
(
y2
-
y1
)
/
(
x2
-
x1
);
// Update menu for selecting vector to edit
this
.
element
.
find
(
'.menu .element-list-edit option'
).
attr
(
'selected'
,
false
);
var
idx
=
_
.
indexOf
(
this
.
settings
.
vectors
,
vec_settings
),
editOption
=
this
.
getEditMenuOption
(
"vector"
,
idx
);
editOption
.
attr
(
'selected'
,
true
);
// Update properties
$
(
'.vector-prop-angle input'
,
this
.
element
).
val
(
angle
.
toFixed
(
2
));
if
(
vector
.
elType
!==
"line"
)
{
var
tailInput
=
x1
.
toFixed
(
2
)
+
", "
+
y1
.
toFixed
(
2
);
var
lengthInput
=
length
.
toFixed
(
2
);
if
(
vec_settings
.
length_units
)
{
lengthInput
+=
' '
+
vec_settings
.
length_units
;
}
$
(
'.vector-prop-tail input'
,
this
.
element
).
val
(
tailInput
);
$
(
'.vector-prop-length'
,
this
.
element
).
show
();
$
(
'.vector-prop-length
.value'
,
this
.
element
).
html
(
length
.
toFixed
(
2
)
+
' '
+
vec_settings
.
length_units
);
$
(
'.vector-prop-length
input'
,
this
.
element
).
val
(
lengthInput
);
$
(
'.vector-prop-slope'
,
this
.
element
).
hide
();
}
else
{
$
(
'.vector-prop-length'
,
this
.
element
).
hide
();
if
(
this
.
settings
.
show_slope_for_lines
)
{
$
(
'.vector-prop-slope'
,
this
.
element
).
show
();
$
(
'.vector-prop-slope
.value'
,
this
.
element
).
htm
l
(
slope
.
toFixed
(
2
));
$
(
'.vector-prop-slope
input'
,
this
.
element
).
va
l
(
slope
.
toFixed
(
2
));
}
}
};
VectorDraw
.
prototype
.
resetVectorProperties
=
function
(
vector
)
{
// Clear current selection
this
.
element
.
find
(
'.menu .element-list-edit option'
).
attr
(
'selected'
,
false
);
// Select default value
$
(
'.menu .element-list-edit option[value="-"]'
,
element
).
attr
(
'selected'
,
true
);
// Reset input fields to default values
$
(
'.menu .vector-prop-list input'
,
element
).
val
(
'-'
);
};
VectorDraw
.
prototype
.
isVectorTailDraggable
=
function
(
vector
)
{
return
vector
.
elType
!==
'arrow'
;
};
...
...
@@ -365,6 +427,8 @@ function VectorDrawXBlock(runtime, element, init_args) {
}
else
{
this
.
renderPoint
(
selected
.
idx
,
point_coords
);
}
// Enable option corresponding to selected element in menu for selecting element to edit
this
.
enableEditOption
(
selected
);
}
else
{
this
.
drawMode
=
false
;
...
...
@@ -395,6 +459,42 @@ function VectorDrawXBlock(runtime, element, init_args) {
this
.
dragged_vector
=
null
;
};
VectorDraw
.
prototype
.
onEditStart
=
function
(
evt
)
{
var
vectorName
=
$
(
evt
.
currentTarget
).
find
(
'option:selected'
).
data
(
'vector-name'
);
var
vectorObject
=
this
.
board
.
elementsByName
[
vectorName
];
this
.
updateVectorProperties
(
vectorObject
);
};
VectorDraw
.
prototype
.
onEditSubmit
=
function
(
evt
)
{
// Get vector that is currently "selected"
var
vectorName
=
$
(
'.element-list-edit'
,
element
).
find
(
'option:selected'
).
data
(
'vector-name'
);
// Get values from input fields
var
newTail
=
$
(
'.vector-prop-tail input'
,
element
).
val
(),
newLength
=
$
(
'.vector-prop-length input'
,
element
).
val
(),
newAngle
=
$
(
'.vector-prop-angle input'
,
element
).
val
();
// Process values
newTail
=
_
.
map
(
newTail
.
split
(
', '
),
function
(
coord
)
{
return
parseFloat
(
coord
);
});
newLength
=
parseFloat
(
newLength
);
newAngle
=
parseFloat
(
newAngle
);
var
values
=
[
newTail
[
0
],
newTail
[
1
],
newLength
,
newAngle
];
// Validate values
if
(
!
_
.
some
(
values
,
Number
.
isNaN
))
{
// Use coordinates of new tail, new length, new angle to calculate new position of tip
var
radians
=
newAngle
*
Math
.
PI
/
180
;
var
newTip
=
[
newTail
[
0
]
+
Math
.
cos
(
radians
)
*
newLength
,
newTail
[
1
]
+
Math
.
sin
(
radians
)
*
newLength
];
// Update position of vector
var
board_object
=
this
.
board
.
elementsByName
[
vectorName
];
board_object
.
point1
.
setPosition
(
JXG
.
COORDS_BY_USER
,
newTail
);
board_object
.
point2
.
setPosition
(
JXG
.
COORDS_BY_USER
,
newTip
);
this
.
board
.
update
();
}
};
VectorDraw
.
prototype
.
getVectorCoords
=
function
(
name
)
{
var
object
=
this
.
board
.
elementsByName
[
name
];
if
(
object
)
{
...
...
vectordraw/public/js/vectordraw_edit.js
View file @
d2fe0450
...
...
@@ -10,6 +10,10 @@ function VectorDrawXBlockEdit(runtime, element, init_args) {
this
.
numberOfVectors
=
this
.
settings
.
vectors
.
length
;
this
.
element
=
$
(
'#'
+
element_id
,
element
);
this
.
element
.
on
(
'click'
,
'.add-vector'
,
this
.
onAddVector
.
bind
(
this
));
this
.
element
.
on
(
'change'
,
'.menu .element-list-edit'
,
this
.
onEditStart
.
bind
(
this
));
this
.
element
.
on
(
'click'
,
'.menu .vector-prop-update'
,
this
.
onEditSubmit
.
bind
(
this
));
this
.
element
.
on
(
'click'
,
'.vector-remove'
,
this
.
onRemoveVector
.
bind
(
this
));
// Prevents default image drag and drop actions in some browsers.
this
.
element
.
on
(
'mousedown'
,
'.jxgboard image'
,
function
(
evt
)
{
evt
.
preventDefault
();
});
...
...
@@ -17,6 +21,7 @@ function VectorDrawXBlockEdit(runtime, element, init_args) {
};
VectorDraw
.
prototype
.
render
=
function
()
{
$
(
'.vector-prop-slope'
,
this
.
element
).
hide
();
// Assign the jxgboard element a random unique ID,
// because JXG.JSXGraph.initBoard needs it.
this
.
element
.
find
(
'.jxgboard'
).
prop
(
'id'
,
_
.
uniqueId
(
'jxgboard'
));
...
...
@@ -49,7 +54,8 @@ function VectorDrawXBlockEdit(runtime, element, init_args) {
function
drawBackground
(
bg
,
ratio
)
{
var
height
=
(
bg
.
height
)
?
bg
.
height
:
bg
.
width
*
ratio
;
var
coords
=
(
bg
.
coords
)
?
bg
.
coords
:
[
-
bg
.
width
/
2
,
-
height
/
2
];
self
.
board
.
create
(
'image'
,
[
bg
.
src
,
coords
,
[
bg
.
width
,
height
]],
{
fixed
:
true
});
var
image
=
self
.
board
.
create
(
'image'
,
[
bg
.
src
,
coords
,
[
bg
.
width
,
height
]],
{
fixed
:
true
});
$
(
image
.
rendNode
).
attr
(
'alt'
,
bg
.
description
);
}
if
(
this
.
settings
.
background
)
{
...
...
@@ -163,6 +169,66 @@ function VectorDrawXBlockEdit(runtime, element, init_args) {
return
line
;
};
VectorDraw
.
prototype
.
getEditMenuOption
=
function
(
type
,
idx
)
{
return
this
.
element
.
find
(
'.menu .element-list-edit option[value='
+
type
+
'-'
+
idx
+
']'
);
};
VectorDraw
.
prototype
.
onAddVector
=
function
(
evt
)
{
if
(
!
this
.
wasUsed
)
{
this
.
wasUsed
=
true
;
}
// Add vector that starts at center of board and has a predefined length and angle
var
defaultCoords
=
[[
0
,
0
],
[
0
,
3
]],
defaultVector
=
this
.
getDefaultVector
(
defaultCoords
);
this
.
settings
.
vectors
.
push
(
defaultVector
);
var
lastIndex
=
this
.
numberOfVectors
-
1
,
vector
=
this
.
renderVector
(
lastIndex
);
this
.
addEditMenuOption
(
defaultVector
.
name
,
lastIndex
);
this
.
updateVectorProperties
(
vector
);
};
VectorDraw
.
prototype
.
addEditMenuOption
=
function
(
vectorName
,
idx
)
{
// 1. Find dropdown for selecting vector to edit
var
editMenu
=
this
.
element
.
find
(
'.menu .element-list-edit'
);
// 2. Remove current selection(s)
editMenu
.
find
(
'option'
).
attr
(
'selected'
,
false
);
// 3. Create option for newly added vector
var
newOption
=
$
(
'<option>'
)
.
attr
(
'value'
,
'vector-'
+
idx
)
.
attr
(
'data-vector-name'
,
vectorName
)
.
text
(
vectorName
);
// 4. Append option to dropdown
editMenu
.
append
(
newOption
);
// 5. Select newly added option
newOption
.
attr
(
'selected'
,
true
);
};
VectorDraw
.
prototype
.
onRemoveVector
=
function
(
evt
)
{
if
(
!
this
.
wasUsed
)
{
this
.
wasUsed
=
true
;
}
// 1. Remove selected vector from board
var
vectorName
=
$
(
'.element-list-edit'
,
element
).
find
(
'option:selected'
).
data
(
'vector-name'
);
var
boardObject
=
this
.
board
.
elementsByName
[
vectorName
];
this
.
board
.
removeAncestors
(
boardObject
);
// 2. Mark vector as "deleted" so it will be removed from "vectors" field on save
var
vectorSettings
=
this
.
getVectorSettingsByName
(
""
+
vectorName
);
vectorSettings
.
deleted
=
true
;
// 3. Remove entry that corresponds to selected vector from menu for selecting vector to edit
var
idx
=
_
.
indexOf
(
this
.
settings
.
vectors
,
vectorSettings
),
editOption
=
this
.
getEditMenuOption
(
"vector"
,
idx
);
editOption
.
remove
();
// 4. Reset input fields for vector properties to default values
this
.
resetVectorProperties
();
};
VectorDraw
.
prototype
.
resetVectorProperties
=
function
(
vector
)
{
// Select default value
$
(
'.menu .element-list-edit option[value="-"]'
,
element
).
attr
(
'selected'
,
true
);
// Reset input fields to default values
$
(
'.menu .vector-prop-list input'
,
element
).
val
(
'-'
);
};
VectorDraw
.
prototype
.
getMouseCoords
=
function
(
evt
)
{
var
i
=
evt
[
JXG
.
touchProperty
]
?
0
:
undefined
;
var
c_pos
=
this
.
board
.
getCoordsTopLeftCorner
(
evt
,
i
);
...
...
@@ -186,6 +252,51 @@ function VectorDrawXBlockEdit(runtime, element, init_args) {
return
null
;
};
VectorDraw
.
prototype
.
getVectorSettingsByName
=
function
(
name
)
{
return
_
.
find
(
this
.
settings
.
vectors
,
function
(
vec
)
{
return
vec
.
name
===
name
;
});
};
VectorDraw
.
prototype
.
updateVectorProperties
=
function
(
vector
)
{
var
vec_settings
=
this
.
getVectorSettingsByName
(
vector
.
name
);
var
x1
=
vector
.
point1
.
X
(),
y1
=
vector
.
point1
.
Y
(),
x2
=
vector
.
point2
.
X
(),
y2
=
vector
.
point2
.
Y
();
var
length
=
vec_settings
.
length_factor
*
Math
.
sqrt
(
Math
.
pow
(
x2
-
x1
,
2
)
+
Math
.
pow
(
y2
-
y1
,
2
));
var
angle
=
((
Math
.
atan2
(
y2
-
y1
,
x2
-
x1
)
/
Math
.
PI
*
180
)
-
vec_settings
.
base_angle
)
%
360
;
if
(
angle
<
0
)
{
angle
+=
360
;
}
var
slope
=
(
y2
-
y1
)
/
(
x2
-
x1
);
// Update menu for selecting vector to edit
this
.
element
.
find
(
'.menu .element-list-edit option'
).
attr
(
'selected'
,
false
);
var
idx
=
_
.
indexOf
(
this
.
settings
.
vectors
,
vec_settings
),
editOption
=
this
.
getEditMenuOption
(
"vector"
,
idx
);
editOption
.
attr
(
'selected'
,
true
);
// Update properties
$
(
'.vector-prop-angle input'
,
this
.
element
).
val
(
angle
.
toFixed
(
2
));
if
(
vector
.
elType
!==
"line"
)
{
var
tailInput
=
x1
.
toFixed
(
2
)
+
", "
+
y1
.
toFixed
(
2
);
var
lengthInput
=
length
.
toFixed
(
2
);
if
(
vec_settings
.
length_units
)
{
lengthInput
+=
' '
+
vec_settings
.
length_units
;
}
$
(
'.vector-prop-tail input'
,
this
.
element
).
val
(
tailInput
);
$
(
'.vector-prop-length'
,
this
.
element
).
show
();
$
(
'.vector-prop-length input'
,
this
.
element
).
val
(
lengthInput
);
$
(
'.vector-prop-slope'
,
this
.
element
).
hide
();
}
else
{
$
(
'.vector-prop-length'
,
this
.
element
).
hide
();
if
(
this
.
settings
.
show_slope_for_lines
)
{
$
(
'.vector-prop-slope'
,
this
.
element
).
show
();
$
(
'.vector-prop-slope input'
,
this
.
element
).
val
(
slope
.
toFixed
(
2
));
}
}
};
VectorDraw
.
prototype
.
isVectorTailDraggable
=
function
(
vector
)
{
return
vector
.
elType
!==
'arrow'
;
};
...
...
@@ -261,6 +372,7 @@ function VectorDrawXBlockEdit(runtime, element, init_args) {
if
(
vectorPoint
)
{
this
.
dragged_vector
=
this
.
getVectorForObject
(
vectorPoint
);
this
.
dragged_vector
.
point1
.
setProperty
({
fixed
:
false
});
this
.
updateVectorProperties
(
this
.
dragged_vector
);
}
}
};
...
...
@@ -270,6 +382,9 @@ function VectorDrawXBlockEdit(runtime, element, init_args) {
var
coords
=
this
.
getMouseCoords
(
evt
);
this
.
dragged_vector
.
point2
.
moveTo
(
coords
.
usrCoords
);
}
if
(
this
.
dragged_vector
)
{
this
.
updateVectorProperties
(
this
.
dragged_vector
);
}
};
VectorDraw
.
prototype
.
onBoardUp
=
function
(
evt
)
{
...
...
@@ -283,6 +398,45 @@ function VectorDrawXBlockEdit(runtime, element, init_args) {
this
.
dragged_vector
=
null
;
};
VectorDraw
.
prototype
.
onEditStart
=
function
(
evt
)
{
var
vectorName
=
$
(
evt
.
currentTarget
).
find
(
'option:selected'
).
data
(
'vector-name'
);
var
vectorObject
=
this
.
board
.
elementsByName
[
vectorName
];
this
.
updateVectorProperties
(
vectorObject
);
};
VectorDraw
.
prototype
.
onEditSubmit
=
function
(
evt
)
{
if
(
!
this
.
wasUsed
)
{
this
.
wasUsed
=
true
;
}
// Get vector that is currently "selected"
var
vectorName
=
$
(
'.element-list-edit'
,
element
).
find
(
'option:selected'
).
data
(
'vector-name'
);
// Get values from input fields
var
newTail
=
$
(
'.vector-prop-tail input'
,
element
).
val
(),
newLength
=
$
(
'.vector-prop-length input'
,
element
).
val
(),
newAngle
=
$
(
'.vector-prop-angle input'
,
element
).
val
();
// Process values
newTail
=
_
.
map
(
newTail
.
split
(
', '
),
function
(
coord
)
{
return
parseFloat
(
coord
);
});
newLength
=
parseFloat
(
newLength
);
newAngle
=
parseFloat
(
newAngle
);
var
values
=
[
newTail
[
0
],
newTail
[
1
],
newLength
,
newAngle
];
// Validate values
if
(
!
_
.
some
(
values
,
Number
.
isNaN
))
{
// Use coordinates of new tail, new length, new angle to calculate new position of tip
var
radians
=
newAngle
*
Math
.
PI
/
180
;
var
newTip
=
[
newTail
[
0
]
+
Math
.
cos
(
radians
)
*
newLength
,
newTail
[
1
]
+
Math
.
sin
(
radians
)
*
newLength
];
// Update position of vector
var
board_object
=
this
.
board
.
elementsByName
[
vectorName
];
board_object
.
point1
.
setPosition
(
JXG
.
COORDS_BY_USER
,
newTail
);
board_object
.
point2
.
setPosition
(
JXG
.
COORDS_BY_USER
,
newTip
);
this
.
board
.
update
();
}
};
VectorDraw
.
prototype
.
getVectorCoords
=
function
(
name
)
{
var
object
=
this
.
board
.
elementsByName
[
name
];
return
{
...
...
@@ -294,6 +448,9 @@ function VectorDrawXBlockEdit(runtime, element, init_args) {
VectorDraw
.
prototype
.
getState
=
function
()
{
var
vectors
=
[];
this
.
settings
.
vectors
.
forEach
(
function
(
vec
)
{
if
(
vec
.
deleted
)
{
return
;
}
var
coords
=
this
.
getVectorCoords
(
vec
.
name
),
tail
=
coords
.
tail
,
tip
=
coords
.
tip
,
...
...
vectordraw/templates/html/vectordraw.html
View file @
d2fe0450
...
...
@@ -10,9 +10,10 @@
{% endif %}
<div
id=
"vectordraw"
>
<div
class=
"menu"
>
<div
class=
"menu"
style=
"width: {{ self.menu_width }}px;"
>
<div
class=
"controls"
>
<select>
<span
class=
"sr"
id=
"element-list-add-label"
>
{% trans "Select element to add to board" %}
</span>
<select
class=
"element-list-add"
aria-labelledby=
"element-list-add-label"
>
{% for vector in self.get_vectors %}
<option
value=
"vector-{{ forloop.counter0 }}"
>
{{ vector.description }}
...
...
@@ -29,29 +30,88 @@
<button
class=
"add-vector"
>
{{ self.add_vector_label }}
</button>
<button
class=
"reset"
>
Reset
</button>
<button
class=
"redo"
title=
"Redo"
><span
class=
"fa fa-repeat"
/></button>
<button
class=
"undo"
title=
"Undo"
><span
class=
"fa fa-undo"
/></button>
<button
class=
"reset"
>
<span
class=
"reset-label"
aria-hidden=
"true"
>
{% trans "Reset" %}
</span>
<span
class=
"sr"
>
{% trans "Reset board to initial state" %}
</span>
</button>
<button
class=
"redo"
title=
"Redo"
>
<span
class=
"redo-label fa fa-repeat"
aria-hidden=
"true"
></span>
<span
class=
"sr"
>
{% trans "Redo last action" %}
</span>
</button>
<button
class=
"undo"
title=
"Undo"
>
<span
class=
"undo-label fa fa-undo"
aria-hidden=
"true"
></span>
<span
class=
"sr"
>
{% trans "Undo last action" %}
</span>
</button>
</div>
{% if self.show_vector_properties %}
<div
class=
"vector-properties"
>
<div
class=
"vector-properties"
aria-live=
"polite"
>
<h3>
{{ self.vector_properties_label }}
</h3>
<div
class=
"vector-prop-list"
>
<div
class=
"row"
>
<div
class=
"vector-prop-name"
>
{% trans "name" %}:
<span
class=
"value vector-prop-bold"
>
-
</span>
<span
id=
"vector-prop-name-label"
>
{% trans "name" %}:
</span>
<select
class=
"element-list-edit"
aria-labelledby=
"vector-prop-name-label"
>
<option
value=
"-"
selected=
"selected"
disabled=
"disabled"
>
-
</option>
{% for vector in self.get_vectors %}
<option
value=
"vector-{{ forloop.counter0 }}"
data-vector-name=
"{{ vector.name }}"
>
{{ vector.name }}
</option>
{% endfor %}
{% for point in self.get_points %}
{% if not point.fixed %}
<option
value=
"point-{{ forloop.counter0 }}"
>
{{ point.name }}
</option>
{% endif %}
{% endfor %}
</select>
</div>
</div>
<div
class=
"row"
>
<div
class=
"vector-prop-tail"
>
<span
id=
"vector-prop-tail-label"
>
{% trans "tail position" %}:
</span>
<input
type=
"text"
value=
"-"
aria-labelledby=
"vector-prop-tail-label"
>
</div>
<div
class=
"vector-prop-length"
>
{% trans "length" %}:
<span
class=
"value"
>
-
</span>
<span
id=
"vector-prop-length-label"
>
{% trans "length" %}:
</span>
<input
type=
"text"
value=
"-"
aria-labelledby=
"vector-prop-length-label"
>
</div>
</div>
<div
class=
"row"
>
<div
class=
"vector-prop-angle"
>
{% trans "angle" %}:
<span
class=
"value"
>
-
</span>
°
<span
id=
"vector-prop-angle-label"
>
{% trans "angle" %}:
</span>
<input
type=
"text"
value=
"-"
aria-labelledby=
"vector-prop-angle-label"
>
</div>
<div
class=
"vector-prop-slope"
>
{% trans "slope" %}:
<span
class=
"value"
>
-
</span>
<span
id=
"vector-prop-slope-label"
>
{% trans "slope" %}:
</span>
<input
type=
"text"
value=
"-"
aria-labelledby=
"vector-prop-slope-label"
disabled=
"disabled"
>
</div>
</div>
<div
class=
"row"
>
<div
class=
"vector-prop-update"
>
<button
class=
"update"
>
<span
class=
"update-label"
aria-hidden=
"true"
>
{% trans "Update" %}
</span>
<span
class=
"sr"
>
{% trans "Update properties of selected element" %}
</span>
</button>
</div>
</div>
</div>
</div>
{% endif %}
</div>
<div
class=
"jxgboard"
style=
"width: {{ self.width }}px; height: {{ self.height }}px;"
></div>
<div
class=
"jxgboard"
style=
"width: {{ self.width }}px; height: {{ self.height }}px;"
aria-live=
"polite"
></div>
</div>
<div
class=
"vectordraw-status"
>
...
...
@@ -62,7 +122,7 @@
<div
class=
"action"
>
<button
class=
"check"
>
<span
class=
"check-label"
aria-hidden=
"true"
>
{% trans "Check" %}
</span>
<span
class=
"sr"
>
{% trans "Check your answer" %}
</span>
<span
class=
"sr"
>
{% trans "Check your answer" %}
</span>
</button>
</div>
...
...
vectordraw/templates/html/vectordraw_edit.html
View file @
d2fe0450
...
...
@@ -73,14 +73,91 @@
To add a vector, left-click the board where you want the vector to originate.
Keep holding down the left mouse button and drag your mouse pointer across the board
to achieve the desired length and angle for the vector.
Alternatively, you can click "Create vector", which will add a new vector
that starts at the center of the board and has a predefined length (3) and angle (90).
To modify an existing vector, left-click it, hold down the left mouse button,
and move your mouse pointer across the board.
Alternatively, you can select an existing vector from the dropdown menu
modify its tail position, length, and angle by changing the values
in the corresponding input fields, and click "Update" to update its position on the board.
To remove an existing vector, left-click it or select it from the dropdown menu,
then click "Remove".
Note that if you make changes using the board below, any changes you made via the "Vectors" field above
will be overwritten when you save the settings for this exercise by clicking the "Save" button below.
{% endblocktrans %}
</p>
<div
id=
"vectordraw"
>
<div
class=
"menu"
style=
"width: {{ self.menu_width }}px;"
>
<div
class=
"controls"
>
<button
class=
"add-vector"
>
{% trans "Create vector" %}
</button>
</div>
<div
class=
"vector-properties"
aria-live=
"polite"
>
<h3>
{{ self.vector_properties_label }}
</h3>
<div
class=
"vector-prop-list"
>
<div
class=
"row"
>
<div
class=
"vector-prop-name"
>
<span
id=
"vector-prop-name-label"
>
{% trans "name" %}:
</span>
<select
class=
"element-list-edit"
aria-labelledby=
"vector-prop-name-label"
>
<option
value=
"-"
selected=
"selected"
disabled=
"disabled"
>
-
</option>
{% for vector in self.get_vectors %}
<option
value=
"vector-{{ forloop.counter0 }}"
data-vector-name=
"{{ vector.name }}"
>
{{ vector.name }}
</option>
{% endfor %}
</select>
</div>
</div>
<div
class=
"row"
>
<div
class=
"vector-prop-tail"
>
<span
id=
"vector-prop-tail-label"
>
{% trans "tail position" %}:
</span>
<input
type=
"text"
value=
"-"
aria-labelledby=
"vector-prop-tail-label"
>
</div>
<div
class=
"vector-prop-length"
>
<span
id=
"vector-prop-length-label"
>
{% trans "length" %}:
</span>
<input
type=
"text"
value=
"-"
aria-labelledby=
"vector-prop-length-label"
>
</div>
</div>
<div
class=
"row"
>
<div
class=
"vector-prop-angle"
>
<span
id=
"vector-prop-angle-label"
>
{% trans "angle" %}:
</span>
<input
type=
"text"
value=
"-"
aria-labelledby=
"vector-prop-angle-label"
>
</div>
<div
class=
"vector-prop-slope"
>
<span
id=
"vector-prop-slope-label"
>
{% trans "slope" %}:
</span>
<input
type=
"text"
value=
"-"
aria-labelledby=
"vector-prop-slope-label"
disabled=
"disabled"
>
</div>
</div>
<div
class=
"row"
>
<div
class=
"vector-prop-update"
>
<button
class=
"update"
>
<span
class=
"update-label"
aria-hidden=
"true"
>
{% trans "Update" %}
</span>
<span
class=
"sr"
>
{% trans "Update properties of selected element" %}
</span>
</button>
</div>
<div
class=
"vector-remove"
>
<button
class=
"remove"
>
<span
class=
"remove-label"
aria-hidden=
"true"
>
{% trans "Remove" %}
</span>
<span
class=
"sr"
>
{% trans "Remove selected element" %}
</span>
</button>
</div>
</div>
</div>
</div>
</div>
<div
class=
"jxgboard"
style=
"width: {{ self.width }}px; height: {{ self.height }}px;"
tabindex=
"0"
>
...
...
vectordraw/vectordraw.py
View file @
d2fe0450
...
...
@@ -7,6 +7,7 @@ from xblock.core import XBlock
from
xblock.exceptions
import
JsonHandlerError
from
xblock.fields
import
Scope
,
Boolean
,
Dict
,
Float
,
Integer
,
String
from
xblock.fragment
import
Fragment
from
xblock.validation
import
ValidationMessage
from
xblockutils.resources
import
ResourceLoader
from
xblockutils.studio_editable
import
StudioEditableXBlockMixin
...
...
@@ -133,6 +134,17 @@ class VectorDrawXBlock(StudioEditableXBlockMixin, XBlock):
scope
=
Scope
.
content
)
background_description
=
String
(
display_name
=
"Background description"
,
help
=
(
"Please provide a description of the image for non-visual users. "
"The description should provide sufficient information that would allow anyone "
"to solve the problem if the image did not load."
),
default
=
""
,
scope
=
Scope
.
content
)
vectors
=
String
(
display_name
=
"Vectors"
,
help
=
(
...
...
@@ -218,6 +230,7 @@ class VectorDrawXBlock(StudioEditableXBlockMixin, XBlock):
'background_url'
,
'background_width'
,
'background_height'
,
'background_description'
,
'vectors'
,
'points'
,
'expected_result'
,
...
...
@@ -251,6 +264,14 @@ class VectorDrawXBlock(StudioEditableXBlockMixin, XBlock):
}
@property
def
menu_width
(
self
):
"""
Width of SVG canvas (controlled by JSXGraph) consistently ends up being 4px larger
than self.width. Adjust menu size accordingly to ensure that board and menu line up.
"""
return
self
.
width
+
4
@property
def
user_state
(
self
):
"""
Return user state, which is a combination of most recent answer and result.
...
...
@@ -269,6 +290,7 @@ class VectorDrawXBlock(StudioEditableXBlockMixin, XBlock):
'src'
:
self
.
background_url
,
'width'
:
self
.
background_width
,
'height'
:
self
.
background_height
,
'description'
:
self
.
background_description
,
}
def
_get_default_vector
(
self
):
# pylint: disable=no-self-use
...
...
@@ -420,6 +442,32 @@ class VectorDrawXBlock(StudioEditableXBlockMixin, XBlock):
)
return
fragment
def
validate_field_data
(
self
,
validation
,
data
):
"""
Validate this block's field data.
"""
super
(
VectorDrawXBlock
,
self
)
.
validate_field_data
(
validation
,
data
)
def
add_error
(
msg
):
""" Helper function for adding validation messages. """
validation
.
add
(
ValidationMessage
(
ValidationMessage
.
ERROR
,
msg
))
if
data
.
background_url
.
strip
():
if
data
.
background_width
==
0
and
data
.
background_height
==
0
:
add_error
(
u"You specified a background image but no width or height. "
"For the image to display, you need to specify a non-zero value "
"for at least one of them."
)
if
not
data
.
background_description
.
strip
():
add_error
(
u"No background description set. "
"This means that it will be more difficult for non-visual users "
"to solve the problem. "
"Please provide a description that contains sufficient information "
"that would allow anyone to solve the problem if the image did not load."
)
def
_validate_check_answer_data
(
self
,
data
):
# pylint: disable=no-self-use
"""
Validate answer data submitted by user.
...
...
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