Commit a34a9dd0 by Diana Huang

Merge branch 'master' into diana/open-ended-ui-updates

parents c7421f04 5d6c5de8
......@@ -34,6 +34,8 @@ import chem
import chem.chemcalc
import chem.chemtools
import chem.miller
import verifiers
import verifiers.draganddrop
import calc
from correctmap import CorrectMap
......@@ -69,7 +71,8 @@ global_context = {'random': random,
'eia': eia,
'chemcalc': chem.chemcalc,
'chemtools': chem.chemtools,
'miller': chem.miller}
'miller': chem.miller,
'draganddrop': verifiers.draganddrop}
# These should be removed from HTML output, including all subelements
html_problem_semantics = ["codeparam", "responseparam", "answer", "script", "hintgroup", "openendedparam","openendedrubric"]
......
......@@ -13,6 +13,9 @@ Module containing the problem elements which render into input objects
- imageinput (for clickable image)
- optioninput (for option list)
- filesubmission (upload a file)
- crystallography
- vsepr_input
- drag_and_drop
These are matched by *.html files templates/*.html which are mako templates with the
actual html.
......@@ -41,6 +44,7 @@ from lxml import etree
import re
import shlex # for splitting quoted strings
import sys
import os
from registry import TagRegistry
......@@ -692,7 +696,7 @@ class VseprInput(InputTypeBase):
@classmethod
def get_attributes(cls):
"""
Note: height, width are required.
Note: height, width, molecules and geometries are required.
"""
return [Attribute('height'),
Attribute('width'),
......@@ -735,3 +739,93 @@ class ChemicalEquationInput(InputTypeBase):
registry.register(ChemicalEquationInput)
#-----------------------------------------------------------------------------
class DragAndDropInput(InputTypeBase):
"""
Input for drag and drop problems. Allows student to drag and drop images and
labels to base image.
"""
template = 'drag_and_drop_input.html'
tags = ['drag_and_drop_input']
def setup(self):
def parse(tag, tag_type):
"""Parses <tag ... /> xml element to dictionary. Stores
'draggable' and 'target' tags with attributes to dictionary and
returns last.
Args:
tag: xml etree element <tag...> with attributes
tag_type: 'draggable' or 'target'.
If tag_type is 'draggable' : all attributes except id
(name or label or icon or can_reuse) are optional
If tag_type is 'target' all attributes (name, x, y, w, h)
are required. (x, y) - coordinates of center of target,
w, h - weight and height of target.
Returns:
Dictionary of vaues of attributes:
dict{'name': smth, 'label': smth, 'icon': smth,
'can_reuse': smth}.
"""
tag_attrs = dict()
tag_attrs['draggable'] = {'id': Attribute._sentinel,
'label': "", 'icon': "",
'can_reuse': ""}
tag_attrs['target'] = {'id': Attribute._sentinel,
'x': Attribute._sentinel,
'y': Attribute._sentinel,
'w': Attribute._sentinel,
'h': Attribute._sentinel}
dic = dict()
for attr_name in tag_attrs[tag_type].keys():
dic[attr_name] = Attribute(attr_name,
default=tag_attrs[tag_type][attr_name]).parse_from_xml(tag)
if tag_type == 'draggable' and not self.no_labels:
dic['label'] = dic['label'] or dic['id']
return dic
# add labels to images?:
self.no_labels = Attribute('no_labels',
default="False").parse_from_xml(self.xml)
to_js = dict()
# image drag and drop onto
to_js['base_image'] = Attribute('img').parse_from_xml(self.xml)
# outline places on image where to drag adn drop
to_js['target_outline'] = Attribute('target_outline',
default="False").parse_from_xml(self.xml)
# one draggable per target?
to_js['one_per_target'] = Attribute('one_per_target',
default="True").parse_from_xml(self.xml)
# list of draggables
to_js['draggables'] = [parse(draggable, 'draggable') for draggable in
self.xml.iterchildren('draggable')]
# list of targets
to_js['targets'] = [parse(target, 'target') for target in
self.xml.iterchildren('target')]
# custom background color for labels:
label_bg_color = Attribute('label_bg_color',
default=None).parse_from_xml(self.xml)
if label_bg_color:
to_js['label_bg_color'] = label_bg_color
self.loaded_attributes['drag_and_drop_json'] = json.dumps(to_js)
self.to_render.add('drag_and_drop_json')
registry.register(DragAndDropInput)
#--------------------------------------------------------------------------------------------------------------------
......@@ -33,7 +33,7 @@ from correctmap import CorrectMap
from datetime import datetime
from util import *
from lxml import etree
from lxml.html.soupparser import fromstring as fromstring_bs # uses Beautiful Soup!!! FIXME?
from lxml.html.soupparser import fromstring as fromstring_bs # uses Beautiful Soup!!! FIXME?
import xqueue_interface
log = logging.getLogger('mitx.' + __name__)
......@@ -869,7 +869,9 @@ def sympy_check2():
response_tag = 'customresponse'
allowed_inputfields = ['textline', 'textbox', 'crystallography', 'chemicalequationinput', 'vsepr_input']
allowed_inputfields = ['textline', 'textbox', 'crystallography',
'chemicalequationinput', 'vsepr_input',
'drag_and_drop_input']
def setup_response(self):
xml = self.xml
......@@ -1044,7 +1046,7 @@ def sympy_check2():
pretty_print=True)
#msg = etree.tostring(fromstring_bs(msg),pretty_print=True)
msg = msg.replace('&#13;', '')
#msg = re.sub('<html>(.*)</html>','\\1',msg,flags=re.M|re.DOTALL) # python 2.7
#msg = re.sub('<html>(.*)</html>','\\1',msg,flags=re.M|re.DOTALL) # python 2.7
msg = re.sub('(?ms)<html>(.*)</html>', '\\1', msg)
messages[0] = msg
......@@ -1763,7 +1765,7 @@ class ImageResponse(LoncapaResponse):
def get_score(self, student_answers):
correct_map = CorrectMap()
expectedset = self.get_answers()
for aid in self.answer_ids: # loop through IDs of <imageinput>
for aid in self.answer_ids: # loop through IDs of <imageinput>
# fields in our stanza
given = student_answers[aid] # this should be a string of the form '[x,y]'
correct_map.set(aid, 'incorrect')
......
<section id="inputtype_${id}" class="capa_inputtype" >
<div class="drag_and_drop_problem_div" id="drag_and_drop_div_${id}"
data-plain-id="${id}">
</div>
<div class="drag_and_drop_problem_json" id="drag_and_drop_json_${id}"
style="display:none;">${drag_and_drop_json}</div>
<div class="script_placeholder" data-src="/static/js/capa/drag_and_drop.js"></div>
% if status == 'unsubmitted':
<div class="unanswered" id="status_${id}">
% elif status == 'correct':
<div class="correct" id="status_${id}">
% elif status == 'incorrect':
<div class="incorrect" id="status_${id}">
% elif status == 'incomplete':
<div class="incorrect" id="status_${id}">
% endif
<input type="text" name="input_${id}" id="input_${id}" value="${value|h}"
style="display:none;"/>
<p class="status">
% if status == 'unsubmitted':
unanswered
% elif status == 'correct':
correct
% elif status == 'incorrect':
incorrect
% elif status == 'incomplete':
incomplete
% endif
</p>
<p id="answer_${id}" class="answer"></p>
% if msg:
<span class="message">${msg|n}</span>
% endif
% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
</div>
% endif
</section>
......@@ -9,13 +9,14 @@ TODO:
- check rendering -- e.g. msg should appear in the rendered output. If possible, test that
templates are escaping things properly.
- test unicode in values, parameters, etc.
- test various html escapes
- test funny xml chars -- should never get xml parse error if things are escaped properly.
"""
import json
from lxml import etree
import unittest
import xml.sax.saxutils as saxutils
......@@ -501,3 +502,70 @@ class ChemicalEquationTest(unittest.TestCase):
}
self.assertEqual(context, expected)
class DragAndDropTest(unittest.TestCase):
'''
Check that drag and drop inputs work
'''
def test_rendering(self):
path_to_images = '/static/images/'
xml_str = """
<drag_and_drop_input id="prob_1_2" img="{path}about_1.png" target_outline="false">
<draggable id="1" label="Label 1"/>
<draggable id="name_with_icon" label="cc" icon="{path}cc.jpg"/>
<draggable id="with_icon" label="arrow-left" icon="{path}arrow-left.png" />
<draggable id="5" label="Label2" />
<draggable id="2" label="Mute" icon="{path}mute.png" />
<draggable id="name_label_icon3" label="spinner" icon="{path}spinner.gif" />
<draggable id="name4" label="Star" icon="{path}volume.png" />
<draggable id="7" label="Label3" />
<target id="t1" x="210" y="90" w="90" h="90"/>
<target id="t2" x="370" y="160" w="90" h="90"/>
</drag_and_drop_input>
""".format(path=path_to_images)
element = etree.fromstring(xml_str)
value = 'abc'
state = {'value': value,
'status': 'unsubmitted'}
user_input = { # order matters, for string comparison
"target_outline": "false",
"base_image": "/static/images/about_1.png",
"draggables": [
{"can_reuse": "", "label": "Label 1", "id": "1", "icon": ""},
{"can_reuse": "", "label": "cc", "id": "name_with_icon", "icon": "/static/images/cc.jpg", },
{"can_reuse": "", "label": "arrow-left", "id": "with_icon", "icon": "/static/images/arrow-left.png", "can_reuse": ""},
{"can_reuse": "", "label": "Label2", "id": "5", "icon": "", "can_reuse": ""},
{"can_reuse": "", "label": "Mute", "id": "2", "icon": "/static/images/mute.png", "can_reuse": ""},
{"can_reuse": "", "label": "spinner", "id": "name_label_icon3", "icon": "/static/images/spinner.gif", "can_reuse": ""},
{"can_reuse": "", "label": "Star", "id": "name4", "icon": "/static/images/volume.png", "can_reuse": ""},
{"can_reuse": "", "label": "Label3", "id": "7", "icon": "", "can_reuse": ""}],
"one_per_target": "True",
"targets": [
{"y": "90", "x": "210", "id": "t1", "w": "90", "h": "90"},
{"y": "160", "x": "370", "id": "t2", "w": "90", "h": "90"}
]
}
the_input = lookup_tag('drag_and_drop_input')(test_system, element, state)
context = the_input._get_render_context()
expected = {'id': 'prob_1_2',
'value': value,
'status': 'unsubmitted',
'msg': '',
'drag_and_drop_json': json.dumps(user_input)
}
# as we are dumping 'draggables' dicts while dumping user_input, string
# comparison will fail, as order of keys is random.
self.assertEqual(json.loads(context['drag_and_drop_json']), user_input)
context.pop('drag_and_drop_json')
expected.pop('drag_and_drop_json')
self.assertEqual(context, expected)
""" Grader of drag and drop input.
Client side behavior: user can drag and drop images from list on base image.
Then json returned from client is:
{
"draggable": [
{ "image1": "t1" },
{ "ant": "t2" },
{ "molecule": "t3" },
]
}
values are target names.
or:
{
"draggable": [
{ "image1": "[10, 20]" },
{ "ant": "[30, 40]" },
{ "molecule": "[100, 200]" },
]
}
values are (x,y) coordinates of centers of dragged images.
"""
import json
class PositionsCompare(list):
""" Class for comparing positions.
Args:
list or string::
"abc" - target
[10, 20] - list of integers
[[10,20], 200] list of list and integer
"""
def __eq__(self, other):
""" Compares two arguments.
Default lists behavior is conversion of string "abc" to list
["a", "b", "c"]. We will use that.
If self or other is empty - returns False.
Args:
self, other: str, unicode, list, int, float
Returns: bool
"""
# checks if self or other is not empty list (empty lists = false)
if not self or not other:
return False
if (isinstance(self[0], (list, int, float)) and
isinstance(other[0], (list, int, float))):
return self.coordinate_positions_compare(other)
elif (isinstance(self[0], (unicode, str)) and
isinstance(other[0], (unicode, str))):
return ''.join(self) == ''.join(other)
else: # improper argument types: no (float / int or lists of list
#and float / int pair) or two string / unicode lists pair
return False
def __ne__(self, other):
return not self.__eq__(other)
def coordinate_positions_compare(self, other, r=10):
""" Checks if self is equal to other inside radius of forgiveness
(default 10 px).
Args:
self, other: [x, y] or [[x, y], r], where r is radius of
forgiveness;
x, y, r: int
Returns: bool.
"""
# get max radius of forgiveness
if isinstance(self[0], list): # [(x, y), r] case
r = max(self[1], r)
x1, y1 = self[0]
else:
x1, y1 = self
if isinstance(other[0], list): # [(x, y), r] case
r = max(other[1], r)
x2, y2 = other[0]
else:
x2, y2 = other
if (x2 - x1) ** 2 + (y2 - y1) ** 2 > r * r:
return False
return True
class DragAndDrop(object):
""" Grader class for drag and drop inputtype.
"""
def grade(self):
''' Grader user answer.
Checks if every draggable isplaced on proper target or on proper
coordinates within radius of forgiveness (default is 10).
Returns: bool.
'''
for draggable in self.excess_draggables:
if not self.excess_draggables[draggable]:
return False # user answer has more draggables than correct answer
# Number of draggables in user_groups may be differ that in
# correct_groups, that is incorrect, except special case with 'number'
for groupname, draggable_ids in self.correct_groups.items():
# 'number' rule special case
# for reusable draggables we may get in self.user_groups
# {'1': [u'2', u'2', u'2'], '0': [u'1', u'1'], '2': [u'3']}
# if '+number' is in rule - do not remove duplicates and strip
# '+number' from rule
current_rule = self.correct_positions[groupname].keys()[0]
if 'number' in current_rule:
rule_values = self.correct_positions[groupname][current_rule]
# clean rule, do not do clean duplicate items
self.correct_positions[groupname].pop(current_rule, None)
parsed_rule = current_rule.replace('+', '').replace('number', '')
self.correct_positions[groupname][parsed_rule] = rule_values
else: # remove dublicates
self.user_groups[groupname] = list(set(self.user_groups[groupname]))
if sorted(draggable_ids) != sorted(self.user_groups[groupname]):
return False
# Check that in every group, for rule of that group, user positions of
# every element are equal with correct positions
for groupname in self.correct_groups:
rules_executed = 0
for rule in ('exact', 'anyof', 'unordered_equal'):
# every group has only one rule
if self.correct_positions[groupname].get(rule, None):
rules_executed += 1
if not self.compare_positions(
self.correct_positions[groupname][rule],
self.user_positions[groupname]['user'], flag=rule):
return False
if not rules_executed: # no correct rules for current group
# probably xml content mistake - wrong rules names
return False
return True
def compare_positions(self, correct, user, flag):
""" Compares two lists of positions with flag rules. Order of
correct/user arguments is matter only in 'anyof' flag.
Rules description:
'exact' means 1-1 ordered relationship::
[el1, el2, el3] is 'exact' equal to [el5, el6, el7] when
el1 == el5, el2 == el6, el3 == el7.
Equality function is custom, see below.
'anyof' means subset relationship::
user = [el1, el2] is 'anyof' equal to correct = [el1, el2, el3]
when
set(user) <= set(correct).
'anyof' is ordered relationship. It always checks if user
is subset of correct
Equality function is custom, see below.
Examples:
- many draggables per position:
user ['1','2','2','2'] is 'anyof' equal to ['1', '2', '3']
- draggables can be placed in any order:
user ['1','2','3','4'] is 'anyof' equal to ['4', '2', '1', 3']
'unordered_equal' is same as 'exact' but disregards on order
Equality functions:
Equality functon depends on type of element. They declared in
PositionsCompare class. For position like targets
ids ("t1", "t2", etc..) it is string equality function. For coordinate
positions ([1,2] or [[1,2], 15]) it is coordinate_positions_compare
function (see docstrings in PositionsCompare class)
Args:
correst, user: lists of positions
Returns: True if within rule lists are equal, otherwise False.
"""
if flag == 'exact':
if len(correct) != len(user):
return False
for el1, el2 in zip(correct, user):
if PositionsCompare(el1) != PositionsCompare(el2):
return False
if flag == 'anyof':
for u_el in user:
for c_el in correct:
if PositionsCompare(u_el) == PositionsCompare(c_el):
break
else:
# General: the else is executed after the for,
# only if the for terminates normally (not by a break)
# In this case, 'for' is terminated normally if every element
# from 'correct' list isn't equal to concrete element from
# 'user' list. So as we found one element from 'user' list,
# that not in 'correct' list - we return False
return False
if flag == 'unordered_equal':
if len(correct) != len(user):
return False
temp = correct[:]
for u_el in user:
for c_el in temp:
if PositionsCompare(u_el) == PositionsCompare(c_el):
temp.remove(c_el)
break
else:
# same as upper - if we found element from 'user' list,
# that not in 'correct' list - we return False.
return False
return True
def __init__(self, correct_answer, user_answer):
""" Populates DragAndDrop variables from user_answer and correct_answer.
If correct_answer is dict, converts it to list.
Correct answer in dict form is simpe structure for fast and simple
grading. Example of correct answer dict example::
correct_answer = {'name4': 't1',
'name_with_icon': 't1',
'5': 't2',
'7':'t2'}
It is draggable_name: dragable_position mapping.
Advanced form converted from simple form uses 'exact' rule
for matching.
Correct answer in list form is designed for advanced cases::
correct_answers = [
{
'draggables': ['1', '2', '3', '4', '5', '6'],
'targets': [
's_left', 's_right', 's_sigma', 's_sigma_star', 'p_pi_1', 'p_pi_2'],
'rule': 'anyof'},
{
'draggables': ['7', '8', '9', '10'],
'targets': ['p_left_1', 'p_left_2', 'p_right_1', 'p_right_2'],
'rule': 'anyof'
}
]
Advanced answer in list form is list of dicts, and every dict must have
3 keys: 'draggables', 'targets' and 'rule'. 'Draggables' value is
list of draggables ids, 'targes' values are list of targets ids, 'rule'
value one of 'exact', 'anyof', 'unordered_equal', 'anyof+number',
'unordered_equal+number'
Advanced form uses "all dicts must match with their rule" logic.
Same draggable cannot appears more that in one dict.
Behavior is more widely explained in sphinx documentation.
Args:
user_answer: json
correct_answer: dict or list
"""
self.correct_groups = dict() # correct groups from xml
self.correct_positions = dict() # correct positions for comparing
self.user_groups = dict() # will be populated from user answer
self.user_positions = dict() # will be populated from user answer
# convert from dict answer format to list format
if isinstance(correct_answer, dict):
tmp = []
for key, value in correct_answer.items():
tmp_dict = {'draggables': [], 'targets': [], 'rule': 'exact'}
tmp_dict['draggables'].append(key)
tmp_dict['targets'].append(value)
tmp.append(tmp_dict)
correct_answer = tmp
user_answer = json.loads(user_answer)
# check if we have draggables that are not in correct answer:
self.excess_draggables = {}
# create identical data structures from user answer and correct answer
for i in xrange(0, len(correct_answer)):
groupname = str(i)
self.correct_groups[groupname] = correct_answer[i]['draggables']
self.correct_positions[groupname] = {correct_answer[i]['rule']:
correct_answer[i]['targets']}
self.user_groups[groupname] = []
self.user_positions[groupname] = {'user': []}
for draggable_dict in user_answer['draggables']:
# draggable_dict is 1-to-1 {draggable_name: position}
draggable_name = draggable_dict.keys()[0]
if draggable_name in self.correct_groups[groupname]:
self.user_groups[groupname].append(draggable_name)
self.user_positions[groupname]['user'].append(
draggable_dict[draggable_name])
self.excess_draggables[draggable_name] = True
else:
self.excess_draggables[draggable_name] = \
self.excess_draggables.get(draggable_name, False)
def grade(user_input, correct_answer):
""" Creates DragAndDrop instance from user_input and correct_answer and
calls DragAndDrop.grade for grading.
Supports two interfaces for correct_answer: dict and list.
Args:
user_input: json. Format::
{ "draggables":
[{"1": [10, 10]}, {"name_with_icon": [20, 20]}]}'
or
{"draggables": [{"1": "t1"}, \
{"name_with_icon": "t2"}]}
correct_answer: dict or list.
Dict form::
{'1': 't1', 'name_with_icon': 't2'}
or
{'1': '[10, 10]', 'name_with_icon': '[[10, 10], 20]'}
List form::
correct_answer = [
{
'draggables': ['l3_o', 'l10_o'],
'targets': ['t1_o', 't9_o'],
'rule': 'anyof'
},
{
'draggables': ['l1_c','l8_c'],
'targets': ['t5_c','t6_c'],
'rule': 'anyof'
}
]
Returns: bool
"""
return DragAndDrop(correct_answer=correct_answer,
user_answer=user_input).grade()
import unittest
import draganddrop
from draganddrop import PositionsCompare
class Test_PositionsCompare(unittest.TestCase):
""" describe"""
def test_nested_list_and_list1(self):
self.assertEqual(PositionsCompare([[1, 2], 40]), PositionsCompare([1, 3]))
def test_nested_list_and_list2(self):
self.assertNotEqual(PositionsCompare([1, 12]), PositionsCompare([1, 1]))
def test_list_and_list1(self):
self.assertNotEqual(PositionsCompare([[1, 2], 12]), PositionsCompare([1, 15]))
def test_list_and_list2(self):
self.assertEqual(PositionsCompare([1, 11]), PositionsCompare([1, 1]))
def test_numerical_list_and_string_list(self):
self.assertNotEqual(PositionsCompare([1, 2]), PositionsCompare(["1"]))
def test_string_and_string_list1(self):
self.assertEqual(PositionsCompare("1"), PositionsCompare(["1"]))
def test_string_and_string_list2(self):
self.assertEqual(PositionsCompare("abc"), PositionsCompare("abc"))
def test_string_and_string_list3(self):
self.assertNotEqual(PositionsCompare("abd"), PositionsCompare("abe"))
def test_float_and_string(self):
self.assertNotEqual(PositionsCompare([3.5, 5.7]), PositionsCompare(["1"]))
def test_floats_and_ints(self):
self.assertEqual(PositionsCompare([3.5, 4.5]), PositionsCompare([5, 7]))
class Test_DragAndDrop_Grade(unittest.TestCase):
def test_targets_true(self):
user_input = '{"draggables": [{"1": "t1"}, \
{"name_with_icon": "t2"}]}'
correct_answer = {'1': 't1', 'name_with_icon': 't2'}
self.assertTrue(draganddrop.grade(user_input, correct_answer))
def test_targets_false(self):
user_input = '{"draggables": [{"1": "t1"}, \
{"name_with_icon": "t2"}]}'
correct_answer = {'1': 't3', 'name_with_icon': 't2'}
self.assertFalse(draganddrop.grade(user_input, correct_answer))
def test_multiple_images_per_target_true(self):
user_input = '{\
"draggables": [{"1": "t1"}, {"name_with_icon": "t2"}, \
{"2": "t1"}]}'
correct_answer = {'1': 't1', 'name_with_icon': 't2',
'2': 't1'}
self.assertTrue(draganddrop.grade(user_input, correct_answer))
def test_multiple_images_per_target_false(self):
user_input = '{\
"draggables": [{"1": "t1"}, {"name_with_icon": "t2"}, \
{"2": "t1"}]}'
correct_answer = {'1': 't2', 'name_with_icon': 't2',
'2': 't1'}
self.assertFalse(draganddrop.grade(user_input, correct_answer))
def test_targets_and_positions(self):
user_input = '{"draggables": [{"1": [10,10]}, \
{"name_with_icon": [[10,10],4]}]}'
correct_answer = {'1': [10, 10], 'name_with_icon': [[10, 10], 4]}
self.assertTrue(draganddrop.grade(user_input, correct_answer))
def test_position_and_targets(self):
user_input = '{"draggables": [{"1": "t1"}, {"name_with_icon": "t2"}]}'
correct_answer = {'1': 't1', 'name_with_icon': 't2'}
self.assertTrue(draganddrop.grade(user_input, correct_answer))
def test_positions_exact(self):
user_input = '{"draggables": \
[{"1": [10, 10]}, {"name_with_icon": [20, 20]}]}'
correct_answer = {'1': [10, 10], 'name_with_icon': [20, 20]}
self.assertTrue(draganddrop.grade(user_input, correct_answer))
def test_positions_false(self):
user_input = '{"draggables": \
[{"1": [10, 10]}, {"name_with_icon": [20, 20]}]}'
correct_answer = {'1': [25, 25], 'name_with_icon': [20, 20]}
self.assertFalse(draganddrop.grade(user_input, correct_answer))
def test_positions_true_in_radius(self):
user_input = '{"draggables": \
[{"1": [10, 10]}, {"name_with_icon": [20, 20]}]}'
correct_answer = {'1': [14, 14], 'name_with_icon': [20, 20]}
self.assertTrue(draganddrop.grade(user_input, correct_answer))
def test_positions_true_in_manual_radius(self):
user_input = '{"draggables": \
[{"1": [10, 10]}, {"name_with_icon": [20, 20]}]}'
correct_answer = {'1': [[40, 10], 30], 'name_with_icon': [20, 20]}
self.assertTrue(draganddrop.grade(user_input, correct_answer))
def test_positions_false_in_manual_radius(self):
user_input = '{"draggables": \
[{"1": [10, 10]}, {"name_with_icon": [20, 20]}]}'
correct_answer = {'1': [[40, 10], 29], 'name_with_icon': [20, 20]}
self.assertFalse(draganddrop.grade(user_input, correct_answer))
def test_correct_answer_not_has_key_from_user_answer(self):
user_input = '{"draggables": [{"1": "t1"}, \
{"name_with_icon": "t2"}]}'
correct_answer = {'3': 't3', 'name_with_icon': 't2'}
self.assertFalse(draganddrop.grade(user_input, correct_answer))
def test_anywhere(self):
"""Draggables can be places anywhere on base image.
Place grass in the middle of the image and ant in the
right upper corner."""
user_input = '{"draggables": \
[{"ant":[610.5,57.449951171875]},{"grass":[322.5,199.449951171875]}]}'
correct_answer = {'grass': [[300, 200], 200], 'ant': [[500, 0], 200]}
self.assertTrue(draganddrop.grade(user_input, correct_answer))
def test_lcao_correct(self):
"""Describe carbon molecule in LCAO-MO"""
user_input = '{"draggables":[{"1":"s_left"}, \
{"5":"s_right"},{"4":"s_sigma"},{"6":"s_sigma_star"},{"7":"p_left_1"}, \
{"8":"p_left_2"},{"10":"p_right_1"},{"9":"p_right_2"}, \
{"2":"p_pi_1"},{"3":"p_pi_2"},{"11":"s_sigma_name"}, \
{"13":"s_sigma_star_name"},{"15":"p_pi_name"},{"16":"p_pi_star_name"}, \
{"12":"p_sigma_name"},{"14":"p_sigma_star_name"}]}'
correct_answer = [{
'draggables': ['1', '2', '3', '4', '5', '6'],
'targets': [
's_left', 's_right', 's_sigma', 's_sigma_star', 'p_pi_1', 'p_pi_2'
],
'rule': 'anyof'
}, {
'draggables': ['7', '8', '9', '10'],
'targets': ['p_left_1', 'p_left_2', 'p_right_1', 'p_right_2'],
'rule': 'anyof'
}, {
'draggables': ['11', '12'],
'targets': ['s_sigma_name', 'p_sigma_name'],
'rule': 'anyof'
}, {
'draggables': ['13', '14'],
'targets': ['s_sigma_star_name', 'p_sigma_star_name'],
'rule': 'anyof'
}, {
'draggables': ['15'],
'targets': ['p_pi_name'],
'rule': 'anyof'
}, {
'draggables': ['16'],
'targets': ['p_pi_star_name'],
'rule': 'anyof'
}]
self.assertTrue(draganddrop.grade(user_input, correct_answer))
def test_lcao_extra_element_incorrect(self):
"""Describe carbon molecule in LCAO-MO"""
user_input = '{"draggables":[{"1":"s_left"}, \
{"5":"s_right"},{"4":"s_sigma"},{"6":"s_sigma_star"},{"7":"p_left_1"}, \
{"8":"p_left_2"},{"17":"p_left_3"},{"10":"p_right_1"},{"9":"p_right_2"}, \
{"2":"p_pi_1"},{"3":"p_pi_2"},{"11":"s_sigma_name"}, \
{"13":"s_sigma_star_name"},{"15":"p_pi_name"},{"16":"p_pi_star_name"}, \
{"12":"p_sigma_name"},{"14":"p_sigma_star_name"}]}'
correct_answer = [{
'draggables': ['1', '2', '3', '4', '5', '6'],
'targets': [
's_left', 's_right', 's_sigma', 's_sigma_star', 'p_pi_1', 'p_pi_2'
],
'rule': 'anyof'
}, {
'draggables': ['7', '8', '9', '10'],
'targets': ['p_left_1', 'p_left_2', 'p_right_1', 'p_right_2'],
'rule': 'anyof'
}, {
'draggables': ['11', '12'],
'targets': ['s_sigma_name', 'p_sigma_name'],
'rule': 'anyof'
}, {
'draggables': ['13', '14'],
'targets': ['s_sigma_star_name', 'p_sigma_star_name'],
'rule': 'anyof'
}, {
'draggables': ['15'],
'targets': ['p_pi_name'],
'rule': 'anyof'
}, {
'draggables': ['16'],
'targets': ['p_pi_star_name'],
'rule': 'anyof'
}]
self.assertFalse(draganddrop.grade(user_input, correct_answer))
def test_reuse_draggable_no_mupliples(self):
"""Test reusable draggables (no mupltiple draggables per target)"""
user_input = '{"draggables":[{"1":"target1"}, \
{"2":"target2"},{"1":"target3"},{"2":"target4"},{"2":"target5"}, \
{"3":"target6"}]}'
correct_answer = [
{
'draggables': ['1'],
'targets': ['target1', 'target3'],
'rule': 'anyof'
},
{
'draggables': ['2'],
'targets': ['target2', 'target4', 'target5'],
'rule': 'anyof'
},
{
'draggables': ['3'],
'targets': ['target6'],
'rule': 'anyof'
}]
self.assertTrue(draganddrop.grade(user_input, correct_answer))
def test_reuse_draggable_with_mupliples(self):
"""Test reusable draggables with mupltiple draggables per target"""
user_input = '{"draggables":[{"1":"target1"}, \
{"2":"target2"},{"1":"target1"},{"2":"target4"},{"2":"target4"}, \
{"3":"target6"}]}'
correct_answer = [
{
'draggables': ['1'],
'targets': ['target1', 'target3'],
'rule': 'anyof'
},
{
'draggables': ['2'],
'targets': ['target2', 'target4'],
'rule': 'anyof'
},
{
'draggables': ['3'],
'targets': ['target6'],
'rule': 'anyof'
}]
self.assertTrue(draganddrop.grade(user_input, correct_answer))
def test_reuse_many_draggable_with_mupliples(self):
"""Test reusable draggables with mupltiple draggables per target"""
user_input = '{"draggables":[{"1":"target1"}, \
{"2":"target2"},{"1":"target1"},{"2":"target4"},{"2":"target4"}, \
{"3":"target6"}, {"4": "target3"}, {"5": "target4"}, \
{"5": "target5"}, {"6": "target2"}]}'
correct_answer = [
{
'draggables': ['1', '4'],
'targets': ['target1', 'target3'],
'rule': 'anyof'
},
{
'draggables': ['2', '6'],
'targets': ['target2', 'target4'],
'rule': 'anyof'
},
{
'draggables': ['5'],
'targets': ['target4', 'target5'],
'rule': 'anyof'
},
{
'draggables': ['3'],
'targets': ['target6'],
'rule': 'anyof'
}]
self.assertTrue(draganddrop.grade(user_input, correct_answer))
def test_reuse_many_draggable_with_mupliples_wrong(self):
"""Test reusable draggables with mupltiple draggables per target"""
user_input = '{"draggables":[{"1":"target1"}, \
{"2":"target2"},{"1":"target1"}, \
{"2":"target3"}, \
{"2":"target4"}, \
{"3":"target6"}, {"4": "target3"}, {"5": "target4"}, \
{"5": "target5"}, {"6": "target2"}]}'
correct_answer = [
{
'draggables': ['1', '4'],
'targets': ['target1', 'target3'],
'rule': 'anyof'
},
{
'draggables': ['2', '6'],
'targets': ['target2', 'target4'],
'rule': 'anyof'
},
{
'draggables': ['5'],
'targets': ['target4', 'target5'],
'rule': 'anyof'
},
{
'draggables': ['3'],
'targets': ['target6'],
'rule': 'anyof'
}]
self.assertFalse(draganddrop.grade(user_input, correct_answer))
def test_label_10_targets_with_a_b_c_false(self):
"""Test reusable draggables (no mupltiple draggables per target)"""
user_input = '{"draggables":[{"a":"target1"}, \
{"b":"target2"},{"c":"target3"},{"a":"target4"},{"b":"target5"}, \
{"c":"target6"}, {"a":"target7"},{"b":"target8"},{"c":"target9"}, \
{"a":"target1"}]}'
correct_answer = [
{
'draggables': ['a'],
'targets': ['target1', 'target4', 'target7', 'target10'],
'rule': 'unordered_equal'
},
{
'draggables': ['b'],
'targets': ['target2', 'target5', 'target8'],
'rule': 'unordered_equal'
},
{
'draggables': ['c'],
'targets': ['target3', 'target6', 'target9'],
'rule': 'unordered_equal'
}]
self.assertFalse(draganddrop.grade(user_input, correct_answer))
def test_label_10_targets_with_a_b_c_(self):
"""Test reusable draggables (no mupltiple draggables per target)"""
user_input = '{"draggables":[{"a":"target1"}, \
{"b":"target2"},{"c":"target3"},{"a":"target4"},{"b":"target5"}, \
{"c":"target6"}, {"a":"target7"},{"b":"target8"},{"c":"target9"}, \
{"a":"target10"}]}'
correct_answer = [
{
'draggables': ['a'],
'targets': ['target1', 'target4', 'target7', 'target10'],
'rule': 'unordered_equal'
},
{
'draggables': ['b'],
'targets': ['target2', 'target5', 'target8'],
'rule': 'unordered_equal'
},
{
'draggables': ['c'],
'targets': ['target3', 'target6', 'target9'],
'rule': 'unordered_equal'
}]
self.assertTrue(draganddrop.grade(user_input, correct_answer))
def test_label_10_targets_with_a_b_c_multiple(self):
"""Test reusable draggables (mupltiple draggables per target)"""
user_input = '{"draggables":[{"a":"target1"}, \
{"b":"target2"},{"c":"target3"},{"b":"target5"}, \
{"c":"target6"}, {"a":"target7"},{"b":"target8"},{"c":"target9"}, \
{"a":"target1"}]}'
correct_answer = [
{
'draggables': ['a', 'a', 'a'],
'targets': ['target1', 'target4', 'target7', 'target10'],
'rule': 'anyof+number'
},
{
'draggables': ['b', 'b', 'b'],
'targets': ['target2', 'target5', 'target8'],
'rule': 'anyof+number'
},
{
'draggables': ['c', 'c', 'c'],
'targets': ['target3', 'target6', 'target9'],
'rule': 'anyof+number'
}]
self.assertTrue(draganddrop.grade(user_input, correct_answer))
def test_label_10_targets_with_a_b_c_multiple_false(self):
"""Test reusable draggables (mupltiple draggables per target)"""
user_input = '{"draggables":[{"a":"target1"}, \
{"b":"target2"},{"c":"target3"},{"a":"target4"},{"b":"target5"}, \
{"c":"target6"}, {"a":"target7"},{"b":"target8"},{"c":"target9"}, \
{"a":"target1"}]}'
correct_answer = [
{
'draggables': ['a', 'a', 'a'],
'targets': ['target1', 'target4', 'target7', 'target10'],
'rule': 'anyof+number'
},
{
'draggables': ['b', 'b', 'b'],
'targets': ['target2', 'target5', 'target8'],
'rule': 'anyof+number'
},
{
'draggables': ['c', 'c', 'c'],
'targets': ['target3', 'target6', 'target9'],
'rule': 'anyof+number'
}]
self.assertFalse(draganddrop.grade(user_input, correct_answer))
def test_label_10_targets_with_a_b_c_reused(self):
"""Test a b c in 10 labels reused"""
user_input = '{"draggables":[{"a":"target1"}, \
{"b":"target2"},{"c":"target3"},{"b":"target5"}, \
{"c":"target6"}, {"b":"target8"},{"c":"target9"}, \
{"a":"target10"}]}'
correct_answer = [
{
'draggables': ['a', 'a'],
'targets': ['target1', 'target10'],
'rule': 'unordered_equal+number'
},
{
'draggables': ['b', 'b', 'b'],
'targets': ['target2', 'target5', 'target8'],
'rule': 'unordered_equal+number'
},
{
'draggables': ['c', 'c', 'c'],
'targets': ['target3', 'target6', 'target9'],
'rule': 'unordered_equal+number'
}]
self.assertTrue(draganddrop.grade(user_input, correct_answer))
def test_label_10_targets_with_a_b_c_reused_false(self):
"""Test a b c in 10 labels reused false"""
user_input = '{"draggables":[{"a":"target1"}, \
{"b":"target2"},{"c":"target3"},{"b":"target5"}, {"a":"target8"},\
{"c":"target6"}, {"b":"target8"},{"c":"target9"}, \
{"a":"target10"}]}'
correct_answer = [
{
'draggables': ['a', 'a'],
'targets': ['target1', 'target10'],
'rule': 'unordered_equal+number'
},
{
'draggables': ['b', 'b', 'b'],
'targets': ['target2', 'target5', 'target8'],
'rule': 'unordered_equal+number'
},
{
'draggables': ['c', 'c', 'c'],
'targets': ['target3', 'target6', 'target9'],
'rule': 'unordered_equal+number'
}]
self.assertFalse(draganddrop.grade(user_input, correct_answer))
def test_mixed_reuse_and_not_reuse(self):
"""Test reusable draggables """
user_input = '{"draggables":[{"a":"target1"}, \
{"b":"target2"},{"c":"target3"}, {"a":"target4"},\
{"a":"target5"}]}'
correct_answer = [
{
'draggables': ['a', 'b'],
'targets': ['target1', 'target2', 'target4', 'target5'],
'rule': 'anyof'
},
{
'draggables': ['c'],
'targets': ['target3'],
'rule': 'exact'
}]
self.assertTrue(draganddrop.grade(user_input, correct_answer))
def test_mixed_reuse_and_not_reuse_number(self):
"""Test reusable draggables with number """
user_input = '{"draggables":[{"a":"target1"}, \
{"b":"target2"},{"c":"target3"}, {"a":"target4"}]}'
correct_answer = [
{
'draggables': ['a', 'a', 'b'],
'targets': ['target1', 'target2', 'target4'],
'rule': 'anyof+number'
},
{
'draggables': ['c'],
'targets': ['target3'],
'rule': 'exact'
}]
self.assertTrue(draganddrop.grade(user_input, correct_answer))
def test_mixed_reuse_and_not_reuse_number_false(self):
"""Test reusable draggables with numbers, but wrong"""
user_input = '{"draggables":[{"a":"target1"}, \
{"b":"target2"},{"c":"target3"}, {"a":"target4"}, {"a":"target10"}]}'
correct_answer = [
{
'draggables': ['a', 'a', 'b'],
'targets': ['target1', 'target2', 'target4', 'target10'],
'rule': 'anyof_number'
},
{
'draggables': ['c'],
'targets': ['target3'],
'rule': 'exact'
}]
self.assertFalse(draganddrop.grade(user_input, correct_answer))
def test_alternative_correct_answer(self):
user_input = '{"draggables":[{"name_with_icon":"t1"},\
{"name_with_icon":"t1"},{"name_with_icon":"t1"},{"name4":"t1"}, \
{"name4":"t1"}]}'
correct_answer = [
{'draggables': ['name4'], 'targets': ['t1', 't1'], 'rule': 'exact'},
{'draggables': ['name_with_icon'], 'targets': ['t1', 't1', 't1'],
'rule': 'exact'}
]
self.assertTrue(draganddrop.grade(user_input, correct_answer))
class Test_DragAndDrop_Populate(unittest.TestCase):
def test_1(self):
correct_answer = {'1': [[40, 10], 29], 'name_with_icon': [20, 20]}
user_input = '{"draggables": \
[{"1": [10, 10]}, {"name_with_icon": [20, 20]}]}'
dnd = draganddrop.DragAndDrop(correct_answer, user_input)
correct_groups = {'1': ['name_with_icon'], '0': ['1']}
correct_positions = {'1': {'exact': [[20, 20]]}, '0': {'exact': [[[40, 10], 29]]}}
user_groups = {'1': [u'name_with_icon'], '0': [u'1']}
user_positions = {'1': {'user': [[20, 20]]}, '0': {'user': [[10, 10]]}}
self.assertEqual(correct_groups, dnd.correct_groups)
self.assertEqual(correct_positions, dnd.correct_positions)
self.assertEqual(user_groups, dnd.user_groups)
self.assertEqual(user_positions, dnd.user_positions)
class Test_DraAndDrop_Compare_Positions(unittest.TestCase):
def test_1(self):
dnd = draganddrop.DragAndDrop({'1': 't1'}, '{"draggables": [{"1": "t1"}]}')
self.assertTrue(dnd.compare_positions(correct=[[1, 1], [2, 3]],
user=[[2, 3], [1, 1]],
flag='anyof'))
def test_2a(self):
dnd = draganddrop.DragAndDrop({'1': 't1'}, '{"draggables": [{"1": "t1"}]}')
self.assertTrue(dnd.compare_positions(correct=[[1, 1], [2, 3]],
user=[[2, 3], [1, 1]],
flag='exact'))
def test_2b(self):
dnd = draganddrop.DragAndDrop({'1': 't1'}, '{"draggables": [{"1": "t1"}]}')
self.assertFalse(dnd.compare_positions(correct=[[1, 1], [2, 3]],
user=[[2, 13], [1, 1]],
flag='exact'))
def test_3(self):
dnd = draganddrop.DragAndDrop({'1': 't1'}, '{"draggables": [{"1": "t1"}]}')
self.assertFalse(dnd.compare_positions(correct=["a", "b"],
user=["a", "b", "c"],
flag='anyof'))
def test_4(self):
dnd = draganddrop.DragAndDrop({'1': 't1'}, '{"draggables": [{"1": "t1"}]}')
self.assertTrue(dnd.compare_positions(correct=["a", "b", "c"],
user=["a", "b"],
flag='anyof'))
def test_5(self):
dnd = draganddrop.DragAndDrop({'1': 't1'}, '{"draggables": [{"1": "t1"}]}')
self.assertFalse(dnd.compare_positions(correct=["a", "b", "c"],
user=["a", "c", "b"],
flag='exact'))
def test_6(self):
dnd = draganddrop.DragAndDrop({'1': 't1'}, '{"draggables": [{"1": "t1"}]}')
self.assertTrue(dnd.compare_positions(correct=["a", "b", "c"],
user=["a", "c", "b"],
flag='anyof'))
def test_7(self):
dnd = draganddrop.DragAndDrop({'1': 't1'}, '{"draggables": [{"1": "t1"}]}')
self.assertFalse(dnd.compare_positions(correct=["a", "b", "b"],
user=["a", "c", "b"],
flag='anyof'))
def suite():
testcases = [Test_PositionsCompare,
Test_DragAndDrop_Populate,
Test_DragAndDrop_Grade,
Test_DraAndDrop_Compare_Positions
]
suites = []
for testcase in testcases:
suites.append(unittest.TestLoader().loadTestsFromTestCase(testcase))
return unittest.TestSuite(suites)
if __name__ == "__main__":
unittest.TextTestRunner(verbosity=2).run(suite())
......@@ -83,7 +83,8 @@ class CapaModule(XModule):
resource_string(__name__, 'js/src/javascript_loader.coffee'),
],
'js': [resource_string(__name__, 'js/src/capa/imageinput.js'),
resource_string(__name__, 'js/src/capa/schematic.js')]}
resource_string(__name__, 'js/src/capa/schematic.js')
]}
js_module_name = "Problem"
css = {'scss': [resource_string(__name__, 'css/capa/display.scss')]}
......
var SequenceNav = function($element) {
var _this = this;
var $element = $element;
......@@ -44,7 +41,7 @@ var SequenceNav = function($element) {
var leftPercent = clamp(-left / padding, 0, 1);
$leftShadow.css('opacity', leftPercent);
var rightPercent = clamp((maxScroll + left) / padding, 0, 1);
$rightShadow.css('opacity', rightPercent);
};
......@@ -95,5 +92,5 @@ var SequenceNav = function($element) {
$(window).bind('resize', updateWidths);
setTimeout(function() {
checkPosition();
}, 200);
}
\ No newline at end of file
}, 200);
};
// Wrapper for RequireJS. It will make the standard requirejs(), require(), and
// define() functions from Require JS available inside the anonymous function.
//
// See https://edx-wiki.atlassian.net/wiki/display/LMS/Integration+of+Require+JS+into+the+system
(function (requirejs, require, define) {
requirejs.config({
'baseUrl': '/static/js/capa/drag_and_drop/'
});
// The current JS file will be loaded and run each time. It will require a
// single dependency which will be loaded and stored by RequireJS. On
// subsequent runs, RequireJS will return the dependency from memory, rather
// than loading it again from the server. For that reason, it is a good idea to
// keep the current JS file as small as possible, and move everything else into
// RequireJS module dependencies.
requirejs(['main'], function (Main) {
Main();
});
// End of wrapper for RequireJS. As you can see, we are passing
// namespaced Require JS variables to an anonymous function. Within
// it, you can use the standard requirejs(), require(), and define()
// functions as if they were in the global namespace.
}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); // End-of: (function (requirejs, require, define)
// Wrapper for RequireJS. It will make the standard requirejs(), require(), and
// define() functions from Require JS available inside the anonymous function.
//
// See https://edx-wiki.atlassian.net/wiki/display/LMS/Integration+of+Require+JS+into+the+system
(function (requirejs, require, define) {
define(['logme'], function (logme) {
return BaseImage;
function BaseImage(state) {
var baseImageElContainer;
baseImageElContainer = $(
'<div ' +
'class="base_image_container" ' +
'style=" ' +
'position: relative; ' +
'margin-bottom: 25px; ' +
'margin-left: auto; ' +
'margin-right: auto; ' +
'" ' +
'></div>'
);
state.baseImageEl = $('<img />');
state.baseImageEl.attr('src', state.config.baseImage);
state.baseImageEl.load(function () {
baseImageElContainer.css({
'width': this.width,
'height': this.height
});
state.baseImageEl.appendTo(baseImageElContainer);
baseImageElContainer.appendTo(state.containerEl);
state.baseImageEl.mousedown(function (event) {
event.preventDefault();
});
state.baseImageLoaded = true;
});
state.baseImageEl.error(function () {
logme('ERROR: Image "' + state.config.baseImage + '" was not found!');
baseImageElContainer.html(
'<span style="color: red;">' +
'ERROR: Image "' + state.config.baseImage + '" was not found!' +
'</span>'
);
baseImageElContainer.appendTo(state.containerEl);
});
}
});
// End of wrapper for RequireJS. As you can see, we are passing
// namespaced Require JS variables to an anonymous function. Within
// it, you can use the standard requirejs(), require(), and define()
// functions as if they were in the global namespace.
}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); // End-of: (function (requirejs, require, define)
// Wrapper for RequireJS. It will make the standard requirejs(), require(), and
// define() functions from Require JS available inside the anonymous function.
//
// See https://edx-wiki.atlassian.net/wiki/display/LMS/Integration+of+Require+JS+into+the+system
(function (requirejs, require, define) {
define(['logme'], function (logme) {
return configParser;
function configParser(state, config) {
state.config = {
'draggables': [],
'baseImage': '',
'targets': [],
'onePerTarget': null, // Specified by user. No default.
'targetOutline': true,
'labelBgColor': '#d6d6d6',
'individualTargets': null, // Depends on 'targets'.
'errors': 0 // Number of errors found while parsing config.
};
getDraggables(state, config);
getBaseImage(state, config);
getTargets(state, config);
getOnePerTarget(state, config);
getTargetOutline(state, config);
getLabelBgColor(state, config);
setIndividualTargets(state);
if (state.config.errors !== 0) {
return false;
}
return true;
}
function getDraggables(state, config) {
if (config.hasOwnProperty('draggables') === false) {
logme('ERROR: "config" does not have a property "draggables".');
state.config.errors += 1;
} else if ($.isArray(config.draggables) === true) {
(function (i) {
while (i < config.draggables.length) {
if (processDraggable(state, config.draggables[i]) !== true) {
state.config.errors += 1;
}
i += 1;
}
}(0));
} else if ($.isPlainObject(config.draggables) === true) {
if (processDraggable(state, config.draggables) !== true) {
state.config.errors += 1;
}
} else {
logme('ERROR: The type of config.draggables is no supported.');
state.config.errors += 1;
}
}
function getBaseImage(state, config) {
if (config.hasOwnProperty('base_image') === false) {
logme('ERROR: "config" does not have a property "base_image".');
state.config.errors += 1;
} else if (typeof config.base_image === 'string') {
state.config.baseImage = config.base_image;
} else {
logme('ERROR: Property config.base_image is not of type "string".');
state.config.errors += 1;
}
}
function getTargets(state, config) {
if (config.hasOwnProperty('targets') === false) {
// It is possible that no "targets" were specified. This is not an error.
// In this case the default value of "[]" (empty array) will be used.
// Draggables can be positioned anywhere on the image, and the server will
// get an answer in the form of (x, y) coordinates for each draggable.
} else if ($.isArray(config.targets) === true) {
(function (i) {
while (i < config.targets.length) {
if (processTarget(state, config.targets[i]) !== true) {
state.config.errors += 1;
}
i += 1;
}
}(0));
} else if ($.isPlainObject(config.targets) === true) {
if (processTarget(state, config.targets) !== true) {
state.config.errors += 1;
}
} else {
logme('ERROR: Property config.targets is not of a supported type.');
state.config.errors += 1;
}
}
function getOnePerTarget(state, config) {
if (config.hasOwnProperty('one_per_target') === false) {
logme('ERROR: "config" does not have a property "one_per_target".');
state.config.errors += 1;
} else if (typeof config.one_per_target === 'string') {
if (config.one_per_target.toLowerCase() === 'true') {
state.config.onePerTarget = true;
} else if (config.one_per_target.toLowerCase() === 'false') {
state.config.onePerTarget = false;
} else {
logme('ERROR: Property config.one_per_target can either be "true", or "false".');
state.config.errors += 1;
}
} else {
logme('ERROR: Property config.one_per_target is not of a supported type.');
state.config.errors += 1;
}
}
function getTargetOutline(state, config) {
if (config.hasOwnProperty('target_outline') === false) {
// It is possible that no "target_outline" was specified. This is not an error.
// In this case the default value of 'true' (boolean) will be used.
} else if (typeof config.target_outline === 'string') {
if (config.target_outline.toLowerCase() === 'true') {
state.config.targetOutline = true;
} else if (config.target_outline.toLowerCase() === 'false') {
state.config.targetOutline = false;
} else {
logme('ERROR: Property config.target_outline can either be "true", or "false".');
state.config.errors += 1;
}
} else {
logme('ERROR: Property config.target_outline is not of a supported type.');
state.config.errors += 1;
}
}
function getLabelBgColor(state, config) {
if (config.hasOwnProperty('label_bg_color') === false) {
// It is possible that no "label_bg_color" was specified. This is not an error.
// In this case the default value of '#d6d6d6' (string) will be used.
} else if (typeof config.label_bg_color === 'string') {
state.config.labelBgColor = config.label_bg_color;
} else {
logme('ERROR: Property config.label_bg_color is not of a supported type.');
returnStatus = false;
}
}
function setIndividualTargets(state) {
if (state.config.targets.length === 0) {
state.config.individualTargets = false;
} else {
state.config.individualTargets = true;
}
}
function processDraggable(state, obj) {
if (
(attrIsString(obj, 'id') === false) ||
(attrIsString(obj, 'icon') === false) ||
(attrIsString(obj, 'label') === false) ||
(attrIsBoolean(obj, 'can_reuse', false) === false)
) {
return false;
}
state.config.draggables.push(obj);
return true;
}
function processTarget(state, obj) {
if (
(attrIsString(obj, 'id') === false) ||
(attrIsInteger(obj, 'w') === false) ||
(attrIsInteger(obj, 'h') === false) ||
(attrIsInteger(obj, 'x') === false) ||
(attrIsInteger(obj, 'y') === false)
) {
return false;
}
state.config.targets.push(obj);
return true;
}
function attrIsString(obj, attr) {
if (obj.hasOwnProperty(attr) === false) {
logme('ERROR: Attribute "obj.' + attr + '" is not present.');
return false;
} else if (typeof obj[attr] !== 'string') {
logme('ERROR: Attribute "obj.' + attr + '" is not a string.');
return false;
}
return true;
}
function attrIsInteger(obj, attr) {
var tempInt;
if (obj.hasOwnProperty(attr) === false) {
logme('ERROR: Attribute "obj.' + attr + '" is not present.');
return false;
}
tempInt = parseInt(obj[attr], 10);
if (isFinite(tempInt) === false) {
logme('ERROR: Attribute "obj.' + attr + '" is not an integer.');
return false;
}
obj[attr] = tempInt;
return true;
}
function attrIsBoolean(obj, attr, defaultVal) {
if (obj.hasOwnProperty(attr) === false) {
if (defaultVal === undefined) {
logme('ERROR: Attribute "obj.' + attr + '" is not present.');
return false;
} else {
obj[attr] = defaultVal;
return true;
}
}
if (obj[attr] === '') {
obj[attr] = defaultVal;
} else if ((obj[attr] === 'false') || (obj[attr] === false)) {
obj[attr] = false;
} else if ((obj[attr] === 'true') || (obj[attr] === true)) {
obj[attr] = true;
} else {
logme('ERROR: Attribute "obj.' + attr + '" is not a boolean.');
return false;
}
return true;
}
});
// End of wrapper for RequireJS. As you can see, we are passing
// namespaced Require JS variables to an anonymous function. Within
// it, you can use the standard requirejs(), require(), and define()
// functions as if they were in the global namespace.
}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); // End-of: (function (requirejs, require, define)
// Wrapper for RequireJS. It will make the standard requirejs(), require(), and
// define() functions from Require JS available inside the anonymous function.
//
// See https://edx-wiki.atlassian.net/wiki/display/LMS/Integration+of+Require+JS+into+the+system
(function (requirejs, require, define) {
define(['logme'], function (logme) {
return Container;
function Container(state) {
state.containerEl = $(
'<div ' +
'style=" ' +
'clear: both; ' +
'width: 665px; ' +
'margin-left: auto; ' +
'margin-right: auto; ' +
'" ' +
'></div>'
);
$('#inputtype_' + state.problemId).before(state.containerEl);
}
});
// End of wrapper for RequireJS. As you can see, we are passing
// namespaced Require JS variables to an anonymous function. Within
// it, you can use the standard requirejs(), require(), and define()
// functions as if they were in the global namespace.
}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); // End-of: (function (requirejs, require, define)
// Wrapper for RequireJS. It will make the standard requirejs(), require(), and
// define() functions from Require JS available inside the anonymous function.
//
// See https://edx-wiki.atlassian.net/wiki/display/LMS/Integration+of+Require+JS+into+the+system
(function (requirejs, require, define) {
define(['logme', 'update_input'], function (logme, updateInput) {
return {
'init': init
};
function init(state) {
(function (c1) {
while (c1 < state.config.draggables.length) {
processDraggable(state, state.config.draggables[c1]);
c1 += 1
}
}(0));
}
function makeDraggableCopy(callbackFunc) {
var draggableObj, property;
// Make a full proper copy of the draggable object, with some modifications.
draggableObj = {};
for (property in this) {
if (this.hasOwnProperty(property) === true) {
draggableObj[property] = this[property];
}
}
// The modifications to the draggable copy.
draggableObj.isOriginal = false; // This new draggable is a copy.
draggableObj.uniqueId = draggableObj.state.getUniqueId(); // Is newly set.
draggableObj.stateDraggablesIndex = null; // Will be set.
draggableObj.containerEl = null; // Not needed, since a copy will never return to a container element.
draggableObj.iconEl = null; // Will be created.
draggableObj.labelEl = null; // Will be created.
// Create DOM elements and attach events.
if (draggableObj.originalConfigObj.icon.length > 0) {
draggableObj.iconEl = $('<img />');
draggableObj.iconEl.attr('src', draggableObj.originalConfigObj.icon);
draggableObj.iconEl.load(function () {
draggableObj.iconEl.css({
'position': 'absolute',
'width': draggableObj.iconWidthSmall,
'height': draggableObj.iconHeightSmall,
'left': 50 - draggableObj.iconWidthSmall * 0.5,
'top': ((draggableObj.originalConfigObj.label.length > 0) ? 5 : 50 - draggableObj.iconHeightSmall * 0.5)
});
if (draggableObj.originalConfigObj.label.length > 0) {
draggableObj.labelEl = $(
'<div ' +
'style=" ' +
'position: absolute; ' +
'color: black; ' +
'font-size: 0.95em; ' +
'" ' +
'>' +
draggableObj.originalConfigObj.label +
'</div>'
);
draggableObj.labelEl.css({
'left': 50 - draggableObj.labelWidth * 0.5,
'top': 5 + draggableObj.iconHeightSmall + 5
});
draggableObj.attachMouseEventsTo('labelEl');
}
draggableObj.attachMouseEventsTo('iconEl');
draggableObj.stateDraggablesIndex = draggableObj.state.draggables.push(draggableObj);
setTimeout(function () {
callbackFunc(draggableObj);
}, 0);
});
return;
} else {
if (draggableObj.originalConfigObj.label.length > 0) {
draggableObj.iconEl = $(
'<div ' +
'style=" ' +
'position: absolute; ' +
'color: black; ' +
'font-size: 0.95em; ' +
'" ' +
'>' +
draggableObj.originalConfigObj.label +
'</div>'
);
draggableObj.iconEl.css({
'left': 50 - draggableObj.iconWidthSmall * 0.5,
'top': 50 - draggableObj.iconHeightSmall * 0.5
});
draggableObj.attachMouseEventsTo('iconEl');
draggableObj.stateDraggablesIndex = draggableObj.state.draggables.push(draggableObj);
setTimeout(function () {
callbackFunc(draggableObj);
}, 0);
return;
}
}
}
function attachMouseEventsTo(element) {
var self;
self = this;
this[element].mousedown(function (event) {
self.mouseDown(event);
});
this[element].mouseup(function (event) {
self.mouseUp(event);
});
this[element].mousemove(function (event) {
self.mouseMove(event);
});
}
function moveDraggableTo(moveType, target) {
var self, offset;
if (this.hasLoaded === false) {
self = this;
setTimeout(function () {
self.moveDraggableTo(moveType, target);
}, 50);
return;
}
if ((this.isReusable === true) && (this.isOriginal === true)) {
this.makeDraggableCopy(function (draggableCopy) {
draggableCopy.moveDraggableTo(moveType, target);
});
return;
}
offset = 0;
if (this.state.config.targetOutline === true) {
offset = 1;
}
this.inContainer = false;
if (this.isOriginal === true) {
this.containerEl.hide();
this.iconEl.detach();
}
this.iconEl.css({
'background-color': this.iconElBGColor,
'padding-left': this.iconElPadding,
'padding-right': this.iconElPadding,
'border': this.iconElBorder,
'width': this.iconWidth,
'height': this.iconHeight
});
if (moveType === 'target') {
this.iconEl.css({
'left': target.offset.left + 0.5 * target.w - this.iconWidth * 0.5 + offset - this.iconElLeftOffset,
'top': target.offset.top + 0.5 * target.h - this.iconHeight * 0.5 + offset
});
} else {
this.iconEl.css({
'left': target.x - this.iconWidth * 0.5 + offset - this.iconElLeftOffset,
'top': target.y - this.iconHeight * 0.5 + offset
});
}
this.iconEl.appendTo(this.state.baseImageEl.parent());
if (this.labelEl !== null) {
if (this.isOriginal === true) {
this.labelEl.detach();
}
this.labelEl.css({
'background-color': this.state.config.labelBgColor,
'padding-left': 8,
'padding-right': 8,
'border': '1px solid black'
});
if (moveType === 'target') {
this.labelEl.css({
'left': target.offset.left + 0.5 * target.w - this.labelWidth * 0.5 + offset - 9, // Account for padding, border.
'top': target.offset.top + 0.5 * target.h + this.iconHeight * 0.5 + 5 + offset
});
} else {
this.labelEl.css({
'left': target.x - this.labelWidth * 0.5 + offset - 9, // Account for padding, border.
'top': target.y - this.iconHeight * 0.5 + this.iconHeight + 5 + offset
});
}
this.labelEl.appendTo(this.state.baseImageEl.parent());
}
if (moveType === 'target') {
target.addDraggable(this);
} else {
this.x = target.x;
this.y = target.y;
}
this.zIndex = 1000;
this.correctZIndexes();
if (this.isOriginal === true) {
this.state.numDraggablesInSlider -= 1;
this.state.updateArrowOpacity();
}
}
function processDraggable(state, obj) {
var draggableObj;
draggableObj = {
'uniqueId': state.getUniqueId(),
'originalConfigObj': obj,
'stateDraggablesIndex': null,
'id': obj.id,
'isReusable': obj.can_reuse,
'isOriginal': true,
'x': -1,
'y': -1,
'zIndex': 1,
'containerEl': null,
'iconEl': null,
'iconElBGColor': null,
'iconElPadding': null,
'iconElBorder': null,
'iconElLeftOffset': null,
'iconWidth': null,
'iconHeight': null,
'iconWidthSmall': null,
'iconHeightSmall': null,
'labelEl': null,
'labelWidth': null,
'hasLoaded': false,
'inContainer': true,
'mousePressed': false,
'onTarget': null,
'onTargetIndex': null,
'state': state,
'mouseDown': mouseDown,
'mouseUp': mouseUp,
'mouseMove': mouseMove,
'checkLandingElement': checkLandingElement,
'checkIfOnTarget': checkIfOnTarget,
'snapToTarget': snapToTarget,
'correctZIndexes': correctZIndexes,
'moveBackToSlider': moveBackToSlider,
'moveDraggableTo': moveDraggableTo,
'makeDraggableCopy': makeDraggableCopy,
'attachMouseEventsTo': attachMouseEventsTo
};
draggableObj.containerEl = $(
'<div ' +
'style=" ' +
'width: 100px; ' +
'height: 100px; ' +
'display: inline; ' +
'float: left; ' +
'overflow: hidden; ' +
'border-left: 1px solid #CCC; ' +
'border-right: 1px solid #CCC; ' +
'text-align: center; ' +
'position: relative; ' +
'" ' +
'></div>'
);
draggableObj.containerEl.appendTo(state.sliderEl);
if (obj.icon.length > 0) {
draggableObj.iconElBGColor = 'transparent';
draggableObj.iconElPadding = 0;
draggableObj.iconElBorder = 'none';
draggableObj.iconElLeftOffset = 0;
draggableObj.iconEl = $('<img />');
draggableObj.iconEl.attr('src', obj.icon);
draggableObj.iconEl.load(function () {
draggableObj.iconWidth = this.width;
draggableObj.iconHeight = this.height;
if (draggableObj.iconWidth >= draggableObj.iconHeight) {
draggableObj.iconWidthSmall = 60;
draggableObj.iconHeightSmall = draggableObj.iconWidthSmall * (draggableObj.iconHeight / draggableObj.iconWidth);
} else {
draggableObj.iconHeightSmall = 60;
draggableObj.iconWidthSmall = draggableObj.iconHeightSmall * (draggableObj.iconWidth / draggableObj.iconHeight);
}
draggableObj.iconEl.css({
'position': 'absolute',
'width': draggableObj.iconWidthSmall,
'height': draggableObj.iconHeightSmall,
'left': 50 - draggableObj.iconWidthSmall * 0.5,
'top': ((obj.label.length > 0) ? 5 : 50 - draggableObj.iconHeightSmall * 0.5)
});
draggableObj.iconEl.appendTo(draggableObj.containerEl);
if (obj.label.length > 0) {
draggableObj.labelEl = $(
'<div ' +
'style=" ' +
'position: absolute; ' +
'color: black; ' +
'font-size: 0.95em; ' +
'" ' +
'>' +
obj.label +
'</div>'
);
draggableObj.labelEl.appendTo(draggableObj.containerEl);
draggableObj.labelWidth = draggableObj.labelEl.width();
draggableObj.labelEl.css({
'left': 50 - draggableObj.labelWidth * 0.5,
'top': 5 + draggableObj.iconHeightSmall + 5
});
draggableObj.attachMouseEventsTo('labelEl');
}
draggableObj.hasLoaded = true;
});
} else {
// To make life easier, if there is no icon, but there is a
// label, we will create a label and store it as if it was an
// icon. All the existing code will work, and the user will
// see a label instead of an icon.
if (obj.label.length > 0) {
draggableObj.iconElBGColor = state.config.labelBgColor;
draggableObj.iconElPadding = 8;
draggableObj.iconElBorder = '1px solid black';
draggableObj.iconElLeftOffset = 9;
draggableObj.iconEl = $(
'<div ' +
'style=" ' +
'position: absolute; ' +
'color: black; ' +
'font-size: 0.95em; ' +
'" ' +
'>' +
obj.label +
'</div>'
);
draggableObj.iconEl.appendTo(draggableObj.containerEl);
draggableObj.iconWidth = draggableObj.iconEl.width();
draggableObj.iconHeight = draggableObj.iconEl.height();
draggableObj.iconWidthSmall = draggableObj.iconWidth;
draggableObj.iconHeightSmall = draggableObj.iconHeight;
draggableObj.iconEl.css({
'left': 50 - draggableObj.iconWidthSmall * 0.5,
'top': 50 - draggableObj.iconHeightSmall * 0.5
});
draggableObj.hasLoaded = true;
} else {
// If no icon and no label, don't create a draggable.
return;
}
}
draggableObj.attachMouseEventsTo('iconEl');
draggableObj.attachMouseEventsTo('containerEl');
state.numDraggablesInSlider += 1;
draggableObj.stateDraggablesIndex = state.draggables.push(draggableObj) - 1;
}
function mouseDown(event) {
if (this.mousePressed === false) {
// So that the browser does not perform a default drag.
// If we don't do this, each drag operation will
// potentially cause the highlghting of the dragged element.
event.preventDefault();
event.stopPropagation();
// If this draggable is just being dragged out of the
// container, we must perform some additional tasks.
if (this.inContainer === true) {
if ((this.isReusable === true) && (this.isOriginal === true)) {
this.makeDraggableCopy(function (draggableCopy) {
draggableCopy.mouseDown(event);
});
return;
}
if (this.isOriginal === true) {
this.containerEl.hide();
this.iconEl.detach();
}
this.iconEl.css({
'background-color': this.iconElBGColor,
'padding-left': this.iconElPadding,
'padding-right': this.iconElPadding,
'border': this.iconElBorder,
'width': this.iconWidth,
'height': this.iconHeight,
'left': event.pageX - this.state.baseImageEl.offset().left - this.iconWidth * 0.5 - this.iconElLeftOffset,
'top': event.pageY - this.state.baseImageEl.offset().top - this.iconHeight * 0.5
});
this.iconEl.appendTo(this.state.baseImageEl.parent());
if (this.labelEl !== null) {
if (this.isOriginal === true) {
this.labelEl.detach();
}
this.labelEl.css({
'background-color': this.state.config.labelBgColor,
'padding-left': 8,
'padding-right': 8,
'border': '1px solid black',
'left': event.pageX - this.state.baseImageEl.offset().left - this.labelWidth * 0.5 - 9, // Account for padding, border.
'top': event.pageY - this.state.baseImageEl.offset().top + this.iconHeight * 0.5 + 5
});
this.labelEl.appendTo(this.state.baseImageEl.parent());
}
this.inContainer = false;
if (this.isOriginal === true) {
this.state.numDraggablesInSlider -= 1;
}
}
this.zIndex = 1000;
this.iconEl.css('z-index', '1000');
if (this.labelEl !== null) {
this.labelEl.css('z-index', '1000');
}
this.mousePressed = true;
this.state.currentMovingDraggable = this;
}
}
function mouseUp() {
if (this.mousePressed === true) {
this.state.currentMovingDraggable = null;
this.checkLandingElement();
}
}
function mouseMove(event) {
if (this.mousePressed === true) {
// Because we have also attached a 'mousemove' event to the
// 'document' (that will do the same thing), let's tell the
// browser not to bubble up this event. The attached event
// on the 'document' will only be triggered when the mouse
// pointer leaves the draggable while it is in the middle
// of a drag operation (user moves the mouse very quickly).
event.stopPropagation();
this.iconEl.css({
'left': event.pageX - this.state.baseImageEl.offset().left - this.iconWidth * 0.5 - this.iconElLeftOffset,
'top': event.pageY - this.state.baseImageEl.offset().top - this.iconHeight * 0.5
});
if (this.labelEl !== null) {
this.labelEl.css({
'left': event.pageX - this.state.baseImageEl.offset().left - this.labelWidth * 0.5 - 9, // Acoount for padding, border.
'top': event.pageY - this.state.baseImageEl.offset().top + this.iconHeight * 0.5 + 5
});
}
}
}
// At this point the mouse was realeased, and we need to check
// where the draggable eneded up. Based on several things, we
// will either move the draggable back to the slider, or update
// the input with the user's answer (X-Y position of the draggable,
// or the ID of the target where it landed.
function checkLandingElement() {
var positionIE;
this.mousePressed = false;
positionIE = this.iconEl.position();
if (this.state.config.individualTargets === true) {
if (this.checkIfOnTarget(positionIE) === true) {
this.correctZIndexes();
} else {
if (this.onTarget !== null) {
this.onTarget.removeDraggable(this);
}
this.moveBackToSlider();
if (this.isOriginal === true) {
this.state.numDraggablesInSlider += 1;
}
}
} else {
if (
(positionIE.left < 0) ||
(positionIE.left + this.iconWidth > this.state.baseImageEl.width()) ||
(positionIE.top < 0) ||
(positionIE.top + this.iconHeight > this.state.baseImageEl.height())
) {
this.moveBackToSlider();
this.x = -1;
this.y = -1;
if (this.isOriginal === true) {
this.state.numDraggablesInSlider += 1;
}
} else {
this.correctZIndexes();
this.x = positionIE.left + this.iconWidth * 0.5;
this.y = positionIE.top + this.iconHeight * 0.5;
}
}
if (this.isOriginal === true) {
this.state.updateArrowOpacity();
}
updateInput.update(this.state);
}
// Determine if a draggable, after it was relased, ends up on a
// target. We do this by iterating over all of the targets, and
// for each one we check whether the draggable's center is
// within the target's dimensions.
//
// positionIE is the object as returned by
//
// this.iconEl.position()
function checkIfOnTarget(positionIE) {
var c1, target;
for (c1 = 0; c1 < this.state.targets.length; c1 += 1) {
target = this.state.targets[c1];
// If only one draggable per target is allowed, and
// the current target already has a draggable on it
// (with an ID different from the one we are checking
// against), then go to next target.
if (
(this.state.config.onePerTarget === true) &&
(target.draggableList.length === 1) &&
(target.draggableList[0].uniqueId !== this.uniqueId)
) {
continue;
}
// Check if the draggable's center coordinate is within
// the target's dimensions. If not, go to next target.
if (
(positionIE.top + this.iconHeight * 0.5 < target.offset.top) ||
(positionIE.top + this.iconHeight * 0.5 > target.offset.top + target.h) ||
(positionIE.left + this.iconWidth * 0.5 < target.offset.left) ||
(positionIE.left + this.iconWidth * 0.5 > target.offset.left + target.w)
) {
continue;
}
// If the draggable was moved from one target to
// another, then we need to remove it from the
// previous target's draggables list, and add it to the
// new target's draggables list.
if ((this.onTarget !== null) && (this.onTarget.id !== target.id)) {
this.onTarget.removeDraggable(this);
target.addDraggable(this);
}
// If the draggable was moved from the slider to a
// target, remember the target, and add ID to the
// target's draggables list.
else if (this.onTarget === null) {
target.addDraggable(this);
}
// Reposition the draggable so that it's center
// coincides with the center of the target.
this.snapToTarget(target);
// Target was found.
return true;
}
// Target was not found.
return false;
}
function snapToTarget(target) {
var offset;
offset = 0;
if (this.state.config.targetOutline === true) {
offset = 1;
}
this.iconEl.css({
'left': target.offset.left + 0.5 * target.w - this.iconWidth * 0.5 + offset - this.iconElLeftOffset,
'top': target.offset.top + 0.5 * target.h - this.iconHeight * 0.5 + offset
});
if (this.labelEl !== null) {
this.labelEl.css({
'left': target.offset.left + 0.5 * target.w - this.labelWidth * 0.5 + offset - 9, // Acoount for padding, border.
'top': target.offset.top + 0.5 * target.h + this.iconHeight * 0.5 + 5 + offset
});
}
}
// Go through all of the draggables subtract 1 from the z-index
// of all whose z-index is higher than the old z-index of the
// current element. After, set the z-index of the current
// element to 1 + N (where N is the number of draggables - i.e.
// the highest z-index possible).
//
// This will make sure that after releasing a draggable, it
// will be on top of all of the other draggables. Also, the
// ordering of the visibility (z-index) of the other draggables
// will not change.
function correctZIndexes() {
var c1, highestZIndex;
highestZIndex = -10000;
if (this.state.config.individualTargets === true) {
if (this.onTarget.draggableList.length > 0) {
for (c1 = 0; c1 < this.onTarget.draggableList.length; c1 += 1) {
if (
(this.onTarget.draggableList[c1].zIndex > highestZIndex) &&
(this.onTarget.draggableList[c1].zIndex !== 1000)
) {
highestZIndex = this.onTarget.draggableList[c1].zIndex;
}
}
} else {
highestZIndex = 0;
}
} else {
for (c1 = 0; c1 < this.state.draggables.length; c1++) {
if (this.inContainer === false) {
if (
(this.state.draggables[c1].zIndex > highestZIndex) &&
(this.state.draggables[c1].zIndex !== 1000)
) {
highestZIndex = this.state.draggables[c1].zIndex;
}
}
}
}
if (highestZIndex === -10000) {
highestZIndex = 0;
}
this.zIndex = highestZIndex + 1;
this.iconEl.css('z-index', this.zIndex);
if (this.labelEl !== null) {
this.labelEl.css('z-index', this.zIndex);
}
}
// If a draggable was released in a wrong positione, we will
// move it back to the slider, placing it in the same position
// that it was dragged out of.
function moveBackToSlider() {
var c1;
if (this.isOriginal === false) {
this.iconEl.remove();
if (this.labelEl !== null) {
this.labelEl.remove();
}
this.state.draggables.splice(this.stateDraggablesIndex, 1);
for (c1 = 0; c1 < this.state.draggables; c1 += 1) {
if (this.state.draggables[c1].stateDraggablesIndex > this.stateDraggablesIndex) {
this.state.draggables[c1].stateDraggablesIndex -= 1;
}
}
return;
}
this.containerEl.show();
this.zIndex = 1;
this.iconEl.detach();
this.iconEl.css({
'border': 'none',
'background-color': 'transparent',
'padding-left': 0,
'padding-right': 0,
'z-index': this.zIndex,
'width': this.iconWidthSmall,
'height': this.iconHeightSmall,
'left': 50 - this.iconWidthSmall * 0.5,
'top': ((this.labelEl !== null) ? 5 : 50 - this.iconHeightSmall * 0.5)
});
this.iconEl.appendTo(this.containerEl);
if (this.labelEl !== null) {
this.labelEl.detach();
this.labelEl.css({
'border': 'none',
'background-color': 'transparent',
'padding-left': 0,
'padding-right': 0,
'z-index': this.zIndex,
'left': 50 - this.labelWidth * 0.5,
'top': 5 + this.iconHeightSmall + 5
});
this.labelEl.appendTo(this.containerEl);
}
this.inContainer = true;
}
});
// End of wrapper for RequireJS. As you can see, we are passing
// namespaced Require JS variables to an anonymous function. Within
// it, you can use the standard requirejs(), require(), and define()
// functions as if they were in the global namespace.
}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); // End-of: (function (requirejs, require, define)
// Wrapper for RequireJS. It will make the standard requirejs(), require(), and
// define() functions from Require JS available inside the anonymous function.
//
// See https://edx-wiki.atlassian.net/wiki/display/LMS/Integration+of+Require+JS+into+the+system
(function (requirejs, require, define) {
define([], function () {
var debugMode;
debugMode = true;
return logme;
function logme() {
var i;
if (
(debugMode !== true) ||
(typeof window.console === 'undefined')
) {
return;
}
i = 0;
while (i < arguments.length) {
window.console.log(arguments[i]);
i += 1;
}
}
});
// End of wrapper for RequireJS. As you can see, we are passing
// namespaced Require JS variables to an anonymous function. Within
// it, you can use the standard requirejs(), require(), and define()
// functions as if they were in the global namespace.
}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); // End-of: (function (requirejs, require, define)
// Wrapper for RequireJS. It will make the standard requirejs(), require(), and
// define() functions from Require JS available inside the anonymous function.
//
// See https://edx-wiki.atlassian.net/wiki/display/LMS/Integration+of+Require+JS+into+the+system
(function (requirejs, require, define) {
define(
['logme', 'state', 'config_parser', 'container', 'base_image', 'scroller', 'draggables', 'targets', 'update_input'],
function (logme, State, configParser, Container, BaseImage, Scroller, Draggables, Targets, updateInput) {
return Main;
function Main() {
$('.drag_and_drop_problem_div').each(processProblem);
}
// $(value) - get the element of the entire problem
function processProblem(index, value) {
var problemId, config, state;
if ($(value).attr('data-problem-processed') === 'true') {
// This problem was already processed by us before, so we will
// skip it.
return;
}
$(value).attr('data-problem-processed', 'true');
problemId = $(value).attr('data-plain-id');
if (typeof problemId !== 'string') {
logme('ERROR: Could not find the ID of the problem DOM element.');
return;
}
try {
config = JSON.parse($('#drag_and_drop_json_' + problemId).html());
} catch (err) {
logme('ERROR: Could not parse the JSON configuration options.');
logme('Error message: "' + err.message + '".');
return;
}
state = State(problemId);
if (configParser(state, config) !== true) {
logme('ERROR: Could not make sense of the JSON configuration options.');
return;
}
Container(state);
BaseImage(state);
(function addContent() {
if (state.baseImageLoaded !== true) {
setTimeout(addContent, 50);
return;
}
Targets(state);
Scroller(state);
Draggables.init(state);
state.updateArrowOpacity();
// Update the input element, checking first that it is not filled with
// an answer from the server.
if (updateInput.check(state) === false) {
updateInput.update(state);
}
}());
}
});
// End of wrapper for RequireJS. As you can see, we are passing
// namespaced Require JS variables to an anonymous function. Within
// it, you can use the standard requirejs(), require(), and define()
// functions as if they were in the global namespace.
}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); // End-of: (function (requirejs, require, define)
// Wrapper for RequireJS. It will make the standard requirejs(), require(), and
// define() functions from Require JS available inside the anonymous function.
//
// See https://edx-wiki.atlassian.net/wiki/display/LMS/Integration+of+Require+JS+into+the+system
(function (requirejs, require, define) {
define(['logme'], function (logme) {
return Scroller;
function Scroller(state) {
var parentEl, moveLeftEl, showEl, moveRightEl, showElLeftMargin;
parentEl = $(
'<div ' +
'style=" ' +
'width: 665px; ' +
'height: 102px; ' +
'margin-left: auto; ' +
'margin-right: auto; ' +
'" ' +
'></div>'
);
moveLeftEl = $(
'<div ' +
'style=" ' +
'width: 40px; ' +
'height: 102px; ' +
'display: inline; ' +
'float: left; ' +
'" ' +
'>' +
'<div ' +
'style=" ' +
'width: 38px; ' +
'height: 100px; '+
'border: 1px solid #CCC; ' +
'background-color: #EEE; ' +
'background-image: -webkit-linear-gradient(top, #EEE, #DDD); ' +
'background-image: -moz-linear-gradient(top, #EEE, #DDD); ' +
'background-image: -ms-linear-gradient(top, #EEE, #DDD); ' +
'background-image: -o-linear-gradient(top, #EEE, #DDD); ' +
'background-image: linear-gradient(top, #EEE, #DDD); ' +
'-webkit-box-shadow: 0 1px 0 rgba(255, 255, 255, 0.7) inset; ' +
'box-shadow: 0 1px 0 rgba(255, 255, 255, 0.7) inset; ' +
'background-image: url(\'/static/images/arrow-left.png\'); ' +
'background-position: center center; ' +
'background-repeat: no-repeat; ' +
'" ' +
'></div>' +
'</div>'
);
moveLeftEl.appendTo(parentEl);
// The below is necessary to prevent the browser thinking that we want
// to perform a drag operation, or a highlight operation. If we don't
// do this, the browser will then highlight with a gray shade the
// element.
moveLeftEl.mousemove(function (event) { event.preventDefault(); });
moveLeftEl.mousedown(function (event) { event.preventDefault(); });
// This event will be responsible for moving the scroller left.
// Hidden draggables will be shown.
moveLeftEl.mouseup(function (event) {
event.preventDefault();
// When there are no more hidden draggables, prevent from
// scrolling infinitely.
if (showElLeftMargin > -102) {
return;
}
showElLeftMargin += 102;
// We scroll by changing the 'margin-left' CSS property smoothly.
state.sliderEl.animate({
'margin-left': showElLeftMargin + 'px'
}, 100, function () {
updateArrowOpacity();
});
});
showEl = $(
'<div ' +
'style=" ' +
'width: 585px; ' +
'height: 102px; ' +
'overflow: hidden; ' +
'display: inline; ' +
'float: left; ' +
'" ' +
'></div>'
);
showEl.appendTo(parentEl);
showElLeftMargin = 0;
// Element where the draggables will be contained. It is very long
// so that any SANE number of draggables will fit in a single row. It
// will be contained in a parent element whose 'overflow' CSS value
// will be hidden, preventing the long row from fully being visible.
state.sliderEl = $(
'<div ' +
'style=" ' +
'width: 20000px; ' +
'height: 100px; ' +
'border-top: 1px solid #CCC; ' +
'border-bottom: 1px solid #CCC; ' +
'" ' +
'></div>'
);
state.sliderEl.appendTo(showEl);
state.sliderEl.mousedown(function (event) {
event.preventDefault();
});
moveRightEl = $(
'<div ' +
'style=" ' +
'width: 40px; ' +
'height: 102px; ' +
'display: inline; ' +
'float: left; ' +
'" ' +
'>' +
'<div ' +
'style=" ' +
'width: 38px; ' +
'height: 100px; '+
'border: 1px solid #CCC; ' +
'background-color: #EEE; ' +
'background-image: -webkit-linear-gradient(top, #EEE, #DDD); ' +
'background-image: -moz-linear-gradient(top, #EEE, #DDD); ' +
'background-image: -ms-linear-gradient(top, #EEE, #DDD); ' +
'background-image: -o-linear-gradient(top, #EEE, #DDD); ' +
'background-image: linear-gradient(top, #EEE, #DDD); ' +
'-webkit-box-shadow: 0 1px 0 rgba(255, 255, 255, 0.7) inset; ' +
'box-shadow: 0 1px 0 rgba(255, 255, 255, 0.7) inset; ' +
'background-image: url(\'/static/images/arrow-right.png\'); ' +
'background-position: center center; ' +
'background-repeat: no-repeat; ' +
'" ' +
'></div>' +
'</div>'
);
moveRightEl.appendTo(parentEl);
// The below is necessary to prevent the browser thinking that we want
// to perform a drag operation, or a highlight operation. If we don't
// do this, the browser will then highlight with a gray shade the
// element.
moveRightEl.mousemove(function (event) { event.preventDefault(); });
moveRightEl.mousedown(function (event) { event.preventDefault(); });
// This event will be responsible for moving the scroller right.
// Hidden draggables will be shown.
moveRightEl.mouseup(function (event) {
event.preventDefault();
// When there are no more hidden draggables, prevent from
// scrolling infinitely.
if (showElLeftMargin < -102 * (state.numDraggablesInSlider - 6)) {
return;
}
showElLeftMargin -= 102;
// We scroll by changing the 'margin-left' CSS property smoothly.
state.sliderEl.animate({
'margin-left': showElLeftMargin + 'px'
}, 100, function () {
updateArrowOpacity();
});
});
parentEl.appendTo(state.containerEl);
// Make the function available throughout the application. We need to
// call it in several places:
//
// 1.) When initially reading answer from server, if draggables will be
// positioned on the base image, the scroller's right and left arrows
// opacity must be updated.
//
// 2.) When creating draggable elements, the scroller's right and left
// arrows opacity must be updated according to the number of
// draggables.
state.updateArrowOpacity = updateArrowOpacity;
return;
function updateArrowOpacity() {
moveLeftEl.children('div').css('opacity', '1');
moveRightEl.children('div').css('opacity', '1');
if (showElLeftMargin < -102 * (state.numDraggablesInSlider - 6)) {
moveRightEl.children('div').css('opacity', '.4');
}
if (showElLeftMargin > -102) {
moveLeftEl.children('div').css('opacity', '.4');
}
}
} // End-of: function Scroller(state)
});
// End of wrapper for RequireJS. As you can see, we are passing
// namespaced Require JS variables to an anonymous function. Within
// it, you can use the standard requirejs(), require(), and define()
// functions as if they were in the global namespace.
}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); // End-of: (function (requirejs, require, define)
// Wrapper for RequireJS. It will make the standard requirejs(), require(), and
// define() functions from Require JS available inside the anonymous function.
//
// See https://edx-wiki.atlassian.net/wiki/display/LMS/Integration+of+Require+JS+into+the+system
(function (requirejs, require, define) {
define([], function () {
return State;
function State(problemId) {
var state;
state = {
'config': null,
'baseImageEl': null,
'baseImageLoaded': false,
'containerEl': null,
'sliderEl': null,
'problemId': problemId,
'draggables': [],
'numDraggablesInSlider': 0,
'currentMovingDraggable': null,
'targets': [],
'updateArrowOpacity': null,
'uniqueId': 0,
'salt': makeSalt(),
'getUniqueId': getUniqueId
};
$(document).mousemove(function (event) {
documentMouseMove(state, event);
});
return state;
}
function getUniqueId() {
this.uniqueId += 1;
return this.salt + '_' + this.uniqueId.toFixed(0);
}
function makeSalt() {
var text, possible, i;
text = '';
possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for(i = 0; i < 5; i += 1) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
function documentMouseMove(state, event) {
if (state.currentMovingDraggable !== null) {
state.currentMovingDraggable.iconEl.css(
'left',
event.pageX -
state.baseImageEl.offset().left -
state.currentMovingDraggable.iconWidth * 0.5
- state.currentMovingDraggable.iconElLeftOffset
);
state.currentMovingDraggable.iconEl.css(
'top',
event.pageY -
state.baseImageEl.offset().top -
state.currentMovingDraggable.iconHeight * 0.5
);
if (state.currentMovingDraggable.labelEl !== null) {
state.currentMovingDraggable.labelEl.css(
'left',
event.pageX -
state.baseImageEl.offset().left -
state.currentMovingDraggable.labelWidth * 0.5
- 9 // Account for padding, border.
);
state.currentMovingDraggable.labelEl.css(
'top',
event.pageY -
state.baseImageEl.offset().top +
state.currentMovingDraggable.iconHeight * 0.5 +
5
);
}
}
}
});
// End of wrapper for RequireJS. As you can see, we are passing
// namespaced Require JS variables to an anonymous function. Within
// it, you can use the standard requirejs(), require(), and define()
// functions as if they were in the global namespace.
}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); // End-of: (function (requirejs, require, define)
// Wrapper for RequireJS. It will make the standard requirejs(), require(), and
// define() functions from Require JS available inside the anonymous function.
//
// See https://edx-wiki.atlassian.net/wiki/display/LMS/Integration+of+Require+JS+into+the+system
(function (requirejs, require, define) {
define(['logme'], function (logme) {
return Targets;
function Targets(state) {
(function (c1) {
while (c1 < state.config.targets.length) {
processTarget(state, state.config.targets[c1]);
c1 += 1;
}
}(0));
}
function processTarget(state, obj) {
var targetEl, borderCss, numTextEl, targetObj;
borderCss = '';
if (state.config.targetOutline === true) {
borderCss = 'border: 1px dashed gray; ';
}
targetEl = $(
'<div ' +
'style=" ' +
'display: block; ' +
'position: absolute; ' +
'width: ' + obj.w + 'px; ' +
'height: ' + obj.h + 'px; ' +
'top: ' + obj.y + 'px; ' +
'left: ' + obj.x + 'px; ' +
borderCss +
'" ' +
'></div>'
);
targetEl.appendTo(state.baseImageEl.parent());
targetEl.mousedown(function (event) {
event.preventDefault();
});
if (state.config.onePerTarget === false) {
numTextEl = $(
'<div ' +
'style=" ' +
'display: block; ' +
'position: absolute; ' +
'width: 24px; ' +
'height: 24px; ' +
'top: ' + obj.y + 'px; ' +
'left: ' + (obj.x + obj.w - 24) + 'px; ' +
'border: 1px solid black; ' +
'text-align: center; ' +
'z-index: 500; ' +
'background-color: white; ' +
'font-size: 0.95em; ' +
'color: #009fe2; ' +
'cursor: pointer; ' +
'" ' +
'>0</div>'
);
} else {
numTextEl = null;
}
targetObj = {
'id': obj.id,
'w': obj.w,
'h': obj.h,
'el': targetEl,
'offset': targetEl.position(),
'draggableList': [],
'state': state,
'targetEl': targetEl,
'numTextEl': numTextEl,
'updateNumTextEl': updateNumTextEl,
'removeDraggable': removeDraggable,
'addDraggable': addDraggable
};
if (state.config.onePerTarget === false) {
numTextEl.appendTo(state.baseImageEl.parent());
numTextEl.mousedown(function (event) {
event.preventDefault();
});
numTextEl.mouseup(function () {
cycleDraggableOrder.call(targetObj)
});
}
state.targets.push(targetObj);
}
function removeDraggable(draggable) {
var c1;
this.draggableList.splice(draggable.onTargetIndex, 1);
// An item from the array was removed. We need to updated all indexes accordingly.
// Shift all indexes down by one if they are higher than the index of the removed item.
c1 = 0;
while (c1 < this.draggableList.length) {
if (this.draggableList[c1].onTargetIndex > draggable.onTargetIndex) {
this.draggableList[c1].onTargetIndex -= 1;
}
c1 += 1;
}
draggable.onTarget = null;
draggable.onTargetIndex = null;
this.updateNumTextEl();
}
function addDraggable(draggable) {
draggable.onTarget = this;
draggable.onTargetIndex = this.draggableList.push(draggable) - 1;
this.updateNumTextEl();
}
/*
* function cycleDraggableOrder
*
* Parameters:
* none - This function does not expect any parameters.
*
* Returns:
* undefined - The return value of this function is not used.
*
* Description:
* Go through all draggables that are on the current target, and decrease their
* z-index by 1, making sure that the bottom-most draggable ends up on the top.
*/
function cycleDraggableOrder() {
var c1, lowestZIndex, highestZIndex;
if (this.draggableList.length < 2) {
return;
}
highestZIndex = -10000;
lowestZIndex = 10000;
for (c1 = 0; c1 < this.draggableList.length; c1 += 1) {
if (this.draggableList[c1].zIndex < lowestZIndex) {
lowestZIndex = this.draggableList[c1].zIndex;
}
if (this.draggableList[c1].zIndex > highestZIndex) {
highestZIndex = this.draggableList[c1].zIndex;
}
}
for (c1 = 0; c1 < this.draggableList.length; c1 += 1) {
if (this.draggableList[c1].zIndex === lowestZIndex) {
this.draggableList[c1].zIndex = highestZIndex;
} else {
this.draggableList[c1].zIndex -= 1;
}
this.draggableList[c1].iconEl.css('z-index', this.draggableList[c1].zIndex);
if (this.draggableList[c1].labelEl !== null) {
this.draggableList[c1].labelEl.css('z-index', this.draggableList[c1].zIndex);
}
}
}
function updateNumTextEl() {
if (this.numTextEl !== null) {
this.numTextEl.html(this.draggableList.length);
}
}
});
// End of wrapper for RequireJS. As you can see, we are passing
// namespaced Require JS variables to an anonymous function. Within
// it, you can use the standard requirejs(), require(), and define()
// functions as if they were in the global namespace.
}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); // End-of: (function (requirejs, require, define)
// Wrapper for RequireJS. It will make the standard requirejs(), require(), and
// define() functions from Require JS available inside the anonymous function.
//
// See https://edx-wiki.atlassian.net/wiki/display/LMS/Integration+of+Require+JS+into+the+system
(function (requirejs, require, define) {
define(['logme'], function (logme) {
return {
'check': check,
'update': update
};
function update(state) {
var draggables, tempObj;
draggables = [];
if (state.config.individualTargets === false) {
(function (c1) {
while (c1 < state.draggables.length) {
if (state.draggables[c1].x !== -1) {
tempObj = {};
tempObj[state.draggables[c1].id] = [
state.draggables[c1].x,
state.draggables[c1].y
];
draggables.push(tempObj);
tempObj = null;
}
c1 += 1;
}
}(0));
} else {
(function (c1) {
while (c1 < state.targets.length) {
(function (c2) {
while (c2 < state.targets[c1].draggableList.length) {
tempObj = {};
tempObj[state.targets[c1].draggableList[c2].id] = state.targets[c1].id;
draggables.push(tempObj);
tempObj = null;
c2 += 1;
}
}(0));
c1 += 1;
}
}(0));
}
$('#input_' + state.problemId).val(JSON.stringify({'draggables': draggables}));
}
// Check if input has an answer from server. If yes, then position
// all draggables according to answer.
function check(state) {
var inputElVal;
inputElVal = $('#input_' + state.problemId).val();
if (inputElVal.length === 0) {
return false;
}
repositionDraggables(state, JSON.parse(inputElVal));
return true;
}
function getUseTargets(answer) {
if ($.isArray(answer.draggables) === false) {
logme('ERROR: answer.draggables is not an array.');
return;
} else if (answer.draggables.length === 0) {
return;
}
if ($.isPlainObject(answer.draggables[0]) === false) {
logme('ERROR: answer.draggables array does not contain objects.');
return;
}
for (c1 in answer.draggables[0]) {
if (answer.draggables[0].hasOwnProperty(c1) === false) {
continue;
}
if (typeof answer.draggables[0][c1] === 'string') {
// use_targets = true;
return true;
} else if (
($.isArray(answer.draggables[0][c1]) === true) &&
(answer.draggables[0][c1].length === 2)
) {
// use_targets = false;
return false;
} else {
logme('ERROR: answer.draggables[0] is inconsidtent.');
return;
}
}
logme('ERROR: answer.draggables[0] is an empty object.');
return;
}
function processAnswerTargets(state, answer) {
var draggableId, draggable, targetId, target;
(function (c1) {
while (c1 < answer.draggables.length) {
for (draggableId in answer.draggables[c1]) {
if (answer.draggables[c1].hasOwnProperty(draggableId) === false) {
continue;
}
if ((draggable = getById(state, 'draggables', draggableId)) === null) {
logme(
'ERROR: In answer there exists a ' +
'draggable ID "' + draggableId + '". No ' +
'draggable with this ID could be found.'
);
continue;
}
targetId = answer.draggables[c1][draggableId];
if ((target = getById(state, 'targets', targetId)) === null) {
logme(
'ERROR: In answer there exists a target ' +
'ID "' + targetId + '". No target with this ' +
'ID could be found.'
);
continue;
}
draggable.moveDraggableTo('target', target);
}
c1 += 1;
}
}(0));
}
function processAnswerPositions(state, answer) {
var draggableId, draggable;
(function (c1) {
while (c1 < answer.draggables.length) {
for (draggableId in answer.draggables[c1]) {
if (answer.draggables[c1].hasOwnProperty(draggableId) === false) {
continue;
}
if ((draggable = getById(state, 'draggables', draggableId)) === null) {
logme(
'ERROR: In answer there exists a ' +
'draggable ID "' + draggableId + '". No ' +
'draggable with this ID could be found.'
);
continue;
}
draggable.moveDraggableTo('XY', {
'x': answer.draggables[c1][draggableId][0],
'y': answer.draggables[c1][draggableId][1]
});
}
c1 += 1;
}
}(0));
}
function repositionDraggables(state, answer) {
if (answer.draggables.length === 0) {
return;
}
if (state.config.individualTargets !== getUseTargets(answer)) {
logme('ERROR: JSON config is not consistent with server response.');
return;
}
if (state.config.individualTargets === true) {
processAnswerTargets(state, answer);
} else if (state.config.individualTargets === false) {
processAnswerPositions(state, answer);
}
}
function getById(state, type, id) {
return (function (c1) {
while (c1 < state[type].length) {
if (type === 'draggables') {
if ((state[type][c1].id === id) && (state[type][c1].isOriginal === true)) {
return state[type][c1];
}
} else { // 'targets'
if (state[type][c1].id === id) {
return state[type][c1];
}
}
c1 += 1;
}
return null;
}(0));
}
});
// End of wrapper for RequireJS. As you can see, we are passing
// namespaced Require JS variables to an anonymous function. Within
// it, you can use the standard requirejs(), require(), and define()
// functions as if they were in the global namespace.
}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); // End-of: (function (requirejs, require, define)
<problem display_name="Drag and drop demos: drag and drop icons or labels
to proper positions." >
<customresponse>
<text>
<h4>[Anyof rule example]</h4><br/>
<h4>Please label hydrogen atoms connected with left carbon atom.</h4>
<br/>
</text>
<drag_and_drop_input img="/static/images/images_list/ethglycol.jpg" target_outline="true"
one_per_target="true" no_labels="true" label_bg_color="rgb(222, 139, 238)">
<draggable id="1" label="Hydrogen" />
<draggable id="2" label="Hydrogen" />
<target id="t1_o" x="10" y="67" w="100" h="100"/>
<target id="t2" x="133" y="3" w="70" h="70"/>
<target id="t3" x="2" y="384" w="70" h="70"/>
<target id="t4" x="95" y="386" w="70" h="70"/>
<target id="t5_c" x="94" y="293" w="91" h="91"/>
<target id="t6_c" x="328" y="294" w="91" h="91"/>
<target id="t7" x="393" y="463" w="70" h="70"/>
<target id="t8" x="344" y="214" w="70" h="70"/>
<target id="t9_o" x="445" y="162" w="100" h="100"/>
<target id="t10" x="591" y="132" w="70" h="70"/>
</drag_and_drop_input>
<answer type="loncapa/python"><![CDATA[
correct_answer = [
{'draggables': ['1', '2'],
'targets': ['t2', 't3', 't4' ],
'rule':'anyof'
}]
if draganddrop.grade(submission[0], correct_answer):
correct = ['correct']
else:
correct = ['incorrect']
]]></answer>
</customresponse>
<customresponse>
<text>
<h4>[Complex grading example]</h4><br/>
<h4>Describe carbon molecule in LCAO-MO.</h4>
<br/>
</text>
<drag_and_drop_input img="/static/images/images_list/lcao-mo/lcao-mo.jpg" target_outline="true" >
<!-- filled bond -->
<draggable id="1" icon="/static/images/images_list/lcao-mo/u_d.png" />
<draggable id="2" icon="/static/images/images_list/lcao-mo/u_d.png" />
<draggable id="3" icon="/static/images/images_list/lcao-mo/u_d.png" />
<draggable id="4" icon="/static/images/images_list/lcao-mo/u_d.png" />
<draggable id="5" icon="/static/images/images_list/lcao-mo/u_d.png" />
<draggable id="6" icon="/static/images/images_list/lcao-mo/u_d.png" />
<!-- up bond -->
<draggable id="7" icon="/static/images/images_list/lcao-mo/up.png"/>
<draggable id="8" icon="/static/images/images_list/lcao-mo/up.png"/>
<draggable id="9" icon="/static/images/images_list/lcao-mo/up.png"/>
<draggable id="10" icon="/static/images/images_list/lcao-mo/up.png"/>
<!-- sigma -->
<draggable id="11" icon="/static/images/images_list/lcao-mo/sigma.png"/>
<draggable id="12" icon="/static/images/images_list/lcao-mo/sigma.png"/>
<!-- sigma* -->
<draggable id="13" icon="/static/images/images_list/lcao-mo/sigma_s.png"/>
<draggable id="14" icon="/static/images/images_list/lcao-mo/sigma_s.png"/>
<!-- pi -->
<draggable id="15" icon="/static/images/images_list/lcao-mo/pi.png" />
<!-- pi* -->
<draggable id="16" icon="/static/images/images_list/lcao-mo/pi_s.png" />
<!-- images that should not be dragged -->
<draggable id="17" icon="/static/images/images_list/lcao-mo/d.png" />
<draggable id="18" icon="/static/images/images_list/lcao-mo/d.png" />
<!-- positions of electrons and electron pairs -->
<target id="s_left" x="130" y="360" w="32" h="32"/>
<target id="s_right" x="505" y="360" w="32" h="32"/>
<target id="s_sigma" x="320" y="425" w="32" h="32"/>
<target id="s_sigma_star" x="320" y="290" w="32" h="32"/>
<target id="p_left_1" x="80" y="100" w="32" h="32"/>
<target id="p_left_2" x="125" y="100" w="32" h="32"/>
<target id="p_left_3" x="175" y="100" w="32" h="32"/>
<target id="p_right_1" x="465" y="100" w="32" h="32"/>
<target id="p_right_2" x="515" y="100" w="32" h="32"/>
<target id="p_right_3" x="560" y="100" w="32" h="32"/>
<target id="p_pi_1" x="290" y="220" w="32" h="32"/>
<target id="p_pi_2" x="335" y="220" w="32" h="32"/>
<target id="p_sigma" x="315" y="170" w="32" h="32"/>
<target id="p_pi_star_1" x="290" y="40" w="32" h="32"/>
<target id="p_pi_star_2" x="340" y="40" w="32" h="32"/>
<target id="p_sigma_star" x="315" y="0" w="32" h="32"/>
<!-- positions of names of energy levels -->
<target id="s_sigma_name" x="400" y="425" w="32" h="32"/>
<target id="s_sigma_star_name" x="400" y="290" w="32" h="32"/>
<target id="p_pi_name" x="400" y="220" w="32" h="32"/>
<target id="p_sigma_name" x="400" y="170" w="32" h="32"/>
<target id="p_pi_star_name" x="400" y="40" w="32" h="32"/>
<target id="p_sigma_star_name" x="400" y="0" w="32" h="32"/>
</drag_and_drop_input>
<answer type="loncapa/python"><![CDATA[
correct_answer = [
{
'draggables': ['1', '2', '3', '4', '5', '6'],
'targets': [
's_left', 's_right', 's_sigma', 's_sigma_star', 'p_pi_1', 'p_pi_2'
],
'rule': 'unordered_equal'
}, {
'draggables': ['7','8', '9', '10'],
'targets': ['p_left_1', 'p_left_2', 'p_right_1','p_right_2'],
'rule': 'unordered_equal'
}, {
'draggables': ['11', '12'],
'targets': ['s_sigma_name', 'p_sigma_name'],
'rule': 'unordered_equal'
}, {
'draggables': ['13', '14'],
'targets': ['s_sigma_star_name', 'p_sigma_star_name'],
'rule': 'unordered_equal'
}, {
'draggables': ['15'],
'targets': ['p_pi_name'],
'rule': 'unordered_equal'
}, {
'draggables': ['16'],
'targets': ['p_pi_star_name'],
'rule': 'unordered_equal'
}]
if draganddrop.grade(submission[0], correct_answer):
correct = ['correct']
else:
correct = ['incorrect']
]]></answer>
</customresponse>
<customresponse>
<text>
<h4>[Another complex grading example]</h4><br/>
<h4>Describe oxygen molecule in LCAO-MO</h4>
<br/>
</text>
<drag_and_drop_input img="/static/images/images_list/lcao-mo/lcao-mo.jpg" target_outline="true" one_per_target="true">
<!-- filled bond -->
<draggable id="1" icon="/static/images/images_list/lcao-mo/u_d.png" />
<draggable id="2" icon="/static/images/images_list/lcao-mo/u_d.png" />
<draggable id="3" icon="/static/images/images_list/lcao-mo/u_d.png" />
<draggable id="4" icon="/static/images/images_list/lcao-mo/u_d.png" />
<draggable id="5" icon="/static/images/images_list/lcao-mo/u_d.png" />
<draggable id="6" icon="/static/images/images_list/lcao-mo/u_d.png" />
<draggable id="v_fb_1" icon="/static/images/images_list/lcao-mo/u_d.png" />
<draggable id="v_fb_2" icon="/static/images/images_list/lcao-mo/u_d.png" />
<draggable id="v_fb_3" icon="/static/images/images_list/lcao-mo/u_d.png" />
<!-- up bond -->
<draggable id="7" icon="/static/images/images_list/lcao-mo/up.png"/>
<draggable id="8" icon="/static/images/images_list/lcao-mo/up.png"/>
<draggable id="9" icon="/static/images/images_list/lcao-mo/up.png"/>
<draggable id="10" icon="/static/images/images_list/lcao-mo/up.png"/>
<draggable id="v_ub_1" icon="/static/images/images_list/lcao-mo/up.png"/>
<draggable id="v_ub_2" icon="/static/images/images_list/lcao-mo/up.png"/>
<!-- sigma -->
<draggable id="11" icon="/static/images/images_list/lcao-mo/sigma.png"/>
<draggable id="12" icon="/static/images/images_list/lcao-mo/sigma.png"/>
<!-- sigma* -->
<draggable id="13" icon="/static/images/images_list/lcao-mo/sigma_s.png"/>
<draggable id="14" icon="/static/images/images_list/lcao-mo/sigma_s.png"/>
<!-- pi -->
<draggable id="15" icon="/static/images/images_list/lcao-mo/pi.png" />
<!-- pi* -->
<draggable id="16" icon="/static/images/images_list/lcao-mo/pi_s.png" />
<!-- images that should not be dragged -->
<draggable id="17" icon="/static/images/images_list/lcao-mo/d.png" />
<draggable id="18" icon="/static/images/images_list/lcao-mo/d.png" />
<!-- positions of electrons and electron pairs -->
<target id="s_left" x="130" y="360" w="32" h="32"/>
<target id="s_right" x="505" y="360" w="32" h="32"/>
<target id="s_sigma" x="320" y="425" w="32" h="32"/>
<target id="s_sigma_star" x="320" y="290" w="32" h="32"/>
<target id="p_left_1" x="80" y="100" w="32" h="32"/>
<target id="p_left_2" x="125" y="100" w="32" h="32"/>
<target id="p_left_3" x="175" y="100" w="32" h="32"/>
<target id="p_right_1" x="465" y="100" w="32" h="32"/>
<target id="p_right_2" x="515" y="100" w="32" h="32"/>
<target id="p_right_3" x="560" y="100" w="32" h="32"/>
<target id="p_pi_1" x="290" y="220" w="32" h="32"/>
<target id="p_pi_2" x="335" y="220" w="32" h="32"/>
<target id="p_sigma" x="315" y="170" w="32" h="32"/>
<target id="p_pi_star_1" x="290" y="40" w="32" h="32"/>
<target id="p_pi_star_2" x="340" y="40" w="32" h="32"/>
<target id="p_sigma_star" x="315" y="0" w="32" h="32"/>
<!-- positions of names of energy levels -->
<target id="s_sigma_name" x="400" y="425" w="32" h="32"/>
<target id="s_sigma_star_name" x="400" y="290" w="32" h="32"/>
<target id="p_pi_name" x="400" y="220" w="32" h="32"/>
<target id="p_pi_star_name" x="400" y="40" w="32" h="32"/>
<target id="p_sigma_name" x="400" y="170" w="32" h="32"/>
<target id="p_sigma_star_name" x="400" y="0" w="32" h="32"/>
</drag_and_drop_input>
<answer type="loncapa/python"><![CDATA[
correct_answer = [{
'draggables': ['1', '2', '3', '4', '5', '6', 'v_fb_1', 'v_fb_2', 'v_fb_3'],
'targets': [
's_left', 's_right', 's_sigma', 's_sigma_star', 'p_pi_1', 'p_pi_2',
'p_sigma', 'p_left_1', 'p_right_3'
],
'rule': 'anyof'
}, {
'draggables': ['7', '8', '9', '10', 'v_ub_1', 'v_ub_2'],
'targets': [
'p_left_2', 'p_left_3', 'p_right_1', 'p_right_2', 'p_pi_star_1',
'p_pi_star_2'
],
'rule': 'anyof'
}, {
'draggables': ['11', '12'],
'targets': ['s_sigma_name', 'p_sigma_name'],
'rule': 'anyof'
}, {
'draggables': ['13', '14'],
'targets': ['s_sigma_star_name', 'p_sigma_star_name'],
'rule': 'anyof'
}, {
'draggables': ['15'],
'targets': ['p_pi_name'],
'rule': 'anyof'
}, {
'draggables': ['16'],
'targets': ['p_pi_star_name'],
'rule': 'anyof'
}]
if draganddrop.grade(submission[0], correct_answer):
correct = ['correct']
else:
correct = ['incorrect']
]]></answer>
</customresponse>
<customresponse>
<text>
<h4>[Individual targets with outlines, One draggable per target]</h4><br/>
<h4>
Drag -Ant- to first position and -Star- to third position </h4><br/>
</text>
<drag_and_drop_input img="/static/images/cow.png" target_outline="true">
<draggable id="1" label="Label 1"/>
<draggable id="name_with_icon" label="Ant" icon="/static/images/images_list/ant.jpg"/>
<draggable id="with_icon" label="Cloud" icon="/static/images/images_list/cloud.jpg" />
<draggable id="5" label="Label2" />
<draggable id="2" label="Drop" icon="/static/images/images_list/drop.jpg" />
<draggable id="name_label_icon3" label="Grass" icon="/static/images/images_list/grass.jpg" />
<draggable id="name4" label="Star" icon="/static/images/images_list/star.png" />
<draggable id="7" label="Label3" />
<target id="t1" x="20" y="20" w="90" h="90"/>
<target id="t2" x="300" y="100" w="90" h="90"/>
<target id="t3" x="150" y="40" w="50" h="50"/>
</drag_and_drop_input>
<answer type="loncapa/python"><![CDATA[
correct_answer = {'name_with_icon': 't1', 'name4': 't2'}
if draganddrop.grade(submission[0], correct_answer):
correct = ['correct']
else:
correct = ['incorrect']
]]></answer>
</customresponse>
<customresponse>
<text>
<h4>[SMALL IMAGE, Individual targets WITHOUT outlines, One draggable
per target]</h4><br/>
<h4>
Move -Star- to the volcano opening, and -Label3- on to
the right ear of the cow.
</h4><br/>
</text>
<drag_and_drop_input img="/static/images/cow3.png" target_outline="false">
<draggable id="1" label="Label 1"/>
<draggable id="name_with_icon" label="Ant" icon="/static/images/images_list/ant.jpg"/>
<draggable id="with_icon" label="Cloud" icon="/static/images/images_list/cloud.jpg" />
<draggable id="5" label="Label2" />
<draggable id="2" label="Drop" icon="/static/images/images_list/drop.jpg" />
<draggable id="name_label_icon3" label="Grass" icon="/static/images/images_list/grass.jpg" />
<draggable id="name4" label="Star" icon="/static/images/images_list/star.png" />
<draggable id="7" label="Label3" />
<target id="t1" x="111" y="58" w="90" h="90"/>
<target id="t2" x="212" y="90" w="90" h="90"/>
</drag_and_drop_input>
<answer type="loncapa/python"><![CDATA[
correct_answer = {'name4': 't1',
'7': 't2'}
if draganddrop.grade(submission[0], correct_answer):
correct = ['correct']
else:
correct = ['incorrect']
]]></answer>
</customresponse>
<customresponse>
<text>
<h4>[Many draggables per target]</h4><br/>
<h4>Move -Star- and -Ant- to most left target
and -Label3- and -Label2- to most right target.</h4><br/>
</text>
<drag_and_drop_input img="/static/images/cow.png" target_outline="true" one_per_target="false">
<draggable id="1" label="Label 1"/>
<draggable id="name_with_icon" label="Ant" icon="/static/images/images_list/ant.jpg"/>
<draggable id="with_icon" label="Cloud" icon="/static/images/images_list/cloud.jpg" />
<draggable id="5" label="Label2" />
<draggable id="2" label="Drop" icon="/static/images/images_list/drop.jpg" />
<draggable id="name_label_icon3" label="Grass" icon="/static/images/images_list/grass.jpg" />
<draggable id="name4" label="Star" icon="/static/images/images_list/star.png" />
<draggable id="7" label="Label3" />
<target id="t1" x="20" y="20" w="90" h="90"/>
<target id="t2" x="300" y="100" w="90" h="90"/>
<target id="t3" x="150" y="40" w="50" h="50"/>
</drag_and_drop_input>
<answer type="loncapa/python"><![CDATA[
correct_answer = {'name4': 't1',
'name_with_icon': 't1',
'5': 't2',
'7':'t2'}
if draganddrop.grade(submission[0], correct_answer):
correct = ['correct']
else:
correct = ['incorrect']
]]></answer>
</customresponse>
<customresponse>
<text>
<h4>[Draggables can be placed anywhere on base image]</h4><br/>
<h4>
Place -Grass- in the middle of the image and -Ant- in the
right upper corner.</h4><br/>
</text>
<drag_and_drop_input img="/static/images/cow.png" >
<draggable id="1" label="Label 1"/>
<draggable id="ant" label="Ant" icon="/static/images/images_list/ant.jpg"/>
<draggable id="with_icon" label="Cloud" icon="/static/images/images_list/cloud.jpg" />
<draggable id="5" label="Label2" />
<draggable id="2" label="Drop" icon="/static/images/images_list/drop.jpg" />
<draggable id="grass" label="Grass" icon="/static/images/images_list/grass.jpg" />
<draggable id="name4" label="Star" icon="/static/images/images_list/star.png" />
<draggable id="7" label="Label3" />
</drag_and_drop_input>
<answer type="loncapa/python"><![CDATA[
correct_answer = {'grass': [[300, 200], 200],
'ant': [[500, 0], 200]}
if draganddrop.grade(submission[0], correct_answer):
correct = ['correct']
else:
correct = ['incorrect']
]]></answer>
</customresponse>
<customresponse>
<text>
<h4>[Another anyof example]</h4><br/>
<h4>Please identify the Carbon and Oxygen atoms in the molecule.</h4><br/>
</text>
<drag_and_drop_input img="/static/images/images_list/ethglycol.jpg" target_outline="true" one_per_target="true">
<draggable id="l1_c" label="Carbon" />
<draggable id="l2" label="Methane"/>
<draggable id="l3_o" label="Oxygen" />
<draggable id="l4" label="Calcium" />
<draggable id="l5" label="Methane"/>
<draggable id="l6" label="Calcium" />
<draggable id="l7" label="Hydrogen" />
<draggable id="l8_c" label="Carbon" />
<draggable id="l9" label="Hydrogen" />
<draggable id="l10_o" label="Oxygen" />
<target id="t1_o" x="10" y="67" w="100" h="100"/>
<target id="t2" x="133" y="3" w="70" h="70"/>
<target id="t3" x="2" y="384" w="70" h="70"/>
<target id="t4" x="95" y="386" w="70" h="70"/>
<target id="t5_c" x="94" y="293" w="91" h="91"/>
<target id="t6_c" x="328" y="294" w="91" h="91"/>
<target id="t7" x="393" y="463" w="70" h="70"/>
<target id="t8" x="344" y="214" w="70" h="70"/>
<target id="t9_o" x="445" y="162" w="100" h="100"/>
<target id="t10" x="591" y="132" w="70" h="70"/>
</drag_and_drop_input>
<answer type="loncapa/python"><![CDATA[
correct_answer = [
{
'draggables': ['l3_o', 'l10_o'],
'targets': ['t1_o', 't9_o'],
'rule': 'anyof'
},
{
'draggables': ['l1_c','l8_c'],
'targets': ['t5_c','t6_c'],
'rule': 'anyof'
}
]
if draganddrop.grade(submission[0], correct_answer):
correct = ['correct']
else:
correct = ['incorrect']
]]></answer>
</customresponse>
<customresponse>
<text>
<h4>[Again another anyof example]</h4><br/>
<h4>If the element appears in this molecule, drag the label onto it</h4>
<br/>
</text>
<drag_and_drop_input img="/static/images/images_list/ethglycol.jpg" target_outline="true"
one_per_target="true" no_labels="true" label_bg_color="rgb(222, 139, 238)">
<draggable id="1" label="Hydrogen" />
<draggable id="2" label="Hydrogen" />
<draggable id="3" label="Nytrogen" />
<draggable id="4" label="Nytrogen" />
<draggable id="5" label="Boron" />
<draggable id="6" label="Boron" />
<draggable id="7" label="Carbon" />
<draggable id="8" label="Carbon" />
<target id="t1_o" x="10" y="67" w="100" h="100"/>
<target id="t2_h" x="133" y="3" w="70" h="70"/>
<target id="t3_h" x="2" y="384" w="70" h="70"/>
<target id="t4_h" x="95" y="386" w="70" h="70"/>
<target id="t5_c" x="94" y="293" w="91" h="91"/>
<target id="t6_c" x="328" y="294" w="91" h="91"/>
<target id="t7_h" x="393" y="463" w="70" h="70"/>
<target id="t8_h" x="344" y="214" w="70" h="70"/>
<target id="t9_o" x="445" y="162" w="100" h="100"/>
<target id="t10_h" x="591" y="132" w="70" h="70"/>
</drag_and_drop_input>
<answer type="loncapa/python"><![CDATA[
correct_answer = [
{
'draggables': ['7', '8'],
'targets': ['t5_c', 't6_c'],
'rule': 'anyof'
},
{
'draggables': ['1', '2'],
'targets': ['t2_h', 't3_h', 't4_h', 't7_h', 't8_h', 't10_h'],
'rule': 'anyof'
}]
if draganddrop.grade(submission[0], correct_answer):
correct = ['correct']
else:
correct = ['incorrect']
]]></answer>
</customresponse>
<customresponse>
<text>
<h4>[Wrong base image url example]
</h4><br/>
</text>
<drag_and_drop_input img="/static/images/cow3_bad.png" target_outline="false">
<draggable id="1" label="Label 1"/>
<draggable id="name_with_icon" label="Ant" icon="/static/images/images_list/ant.jpg"/>
<draggable id="with_icon" label="Cloud" icon="/static/images/images_list/cloud.jpg" />
<draggable id="5" label="Label2" />
<draggable id="2" label="Drop" icon="/static/images/images_list/drop.jpg" />
<draggable id="name_label_icon3" label="Grass" icon="/static/images/images_list/grass.jpg" />
<draggable id="name4" label="Star" icon="/static/images/images_list/star.png" />
<draggable id="7" label="Label3" />
<target id="t1" x="111" y="58" w="90" h="90"/>
<target id="t2" x="212" y="90" w="90" h="90"/>
</drag_and_drop_input>
<answer type="loncapa/python"><![CDATA[
correct_answer = {'name4': 't1',
'7': 't2'}
if draganddrop.grade(submission[0], correct_answer):
correct = ['correct']
else:
correct = ['incorrect']
]]></answer>
</customresponse>
</problem>
<problem display_name="Drag and drop demos: drag and drop icons or labels
to proper positions." >
<customresponse>
<text>
<h4>[Draggable is reusable example]</h4>
<br/>
<h4>Please label all hydrogen atoms.</h4>
<br/>
</text>
<drag_and_drop_input
img="/static/images/images_list/ethglycol.jpg"
target_outline="true"
one_per_target="true"
no_labels="true"
label_bg_color="rgb(222, 139, 238)"
>
<draggable id="1" label="Hydrogen" can_reuse='true' />
<target id="t1_o" x="10" y="67" w="100" h="100" />
<target id="t2" x="133" y="3" w="70" h="70" />
<target id="t3" x="2" y="384" w="70" h="70" />
<target id="t4" x="95" y="386" w="70" h="70" />
<target id="t5_c" x="94" y="293" w="91" h="91" />
<target id="t6_c" x="328" y="294" w="91" h="91" />
<target id="t7" x="393" y="463" w="70" h="70" />
<target id="t8" x="344" y="214" w="70" h="70" />
<target id="t9_o" x="445" y="162" w="100" h="100" />
<target id="t10" x="591" y="132" w="70" h="70" />
</drag_and_drop_input>
<answer type="loncapa/python">
<![CDATA[
correct_answer = [{
'draggables': ['1'],
'targets': ['t2', 't3', 't4', 't7', 't8', 't10'],
'rule': 'exact'
}]
if draganddrop.grade(submission[0], correct_answer):
correct = ['correct']
else:
correct = ['incorrect']
]]>
</answer>
</customresponse>
<customresponse>
<text>
<h4>[Complex grading example]</h4><br/>
<h4>Describe carbon molecule in LCAO-MO.</h4>
<br/>
</text>
<drag_and_drop_input img="/static/images/images_list/lcao-mo/lcao-mo.jpg" target_outline="true" >
<!-- filled bond -->
<draggable id="1" icon="/static/images/images_list/lcao-mo/u_d.png" can_reuse="true" />
<!-- up bond -->
<draggable id="7" icon="/static/images/images_list/lcao-mo/up.png" can_reuse="true" />
<!-- sigma -->
<draggable id="11" icon="/static/images/images_list/lcao-mo/sigma.png" can_reuse="true" />
<!-- sigma* -->
<draggable id="13" icon="/static/images/images_list/lcao-mo/sigma_s.png" can_reuse="true" />
<!-- pi -->
<draggable id="15" icon="/static/images/images_list/lcao-mo/pi.png" can_reuse="true" />
<!-- pi* -->
<draggable id="16" icon="/static/images/images_list/lcao-mo/pi_s.png" can_reuse="true" />
<!-- images that should not be dragged -->
<draggable id="17" icon="/static/images/images_list/lcao-mo/d.png" can_reuse="true" />
<!-- positions of electrons and electron pairs -->
<target id="s_left" x="130" y="360" w="32" h="32"/>
<target id="s_right" x="505" y="360" w="32" h="32"/>
<target id="s_sigma" x="320" y="425" w="32" h="32"/>
<target id="s_sigma_star" x="320" y="290" w="32" h="32"/>
<target id="p_left_1" x="80" y="100" w="32" h="32"/>
<target id="p_left_2" x="125" y="100" w="32" h="32"/>
<target id="p_left_3" x="175" y="100" w="32" h="32"/>
<target id="p_right_1" x="465" y="100" w="32" h="32"/>
<target id="p_right_2" x="515" y="100" w="32" h="32"/>
<target id="p_right_3" x="560" y="100" w="32" h="32"/>
<target id="p_pi_1" x="290" y="220" w="32" h="32"/>
<target id="p_pi_2" x="335" y="220" w="32" h="32"/>
<target id="p_sigma" x="315" y="170" w="32" h="32"/>
<target id="p_pi_star_1" x="290" y="40" w="32" h="32"/>
<target id="p_pi_star_2" x="340" y="40" w="32" h="32"/>
<target id="p_sigma_star" x="315" y="0" w="32" h="32"/>
<!-- positions of names of energy levels -->
<target id="s_sigma_name" x="400" y="425" w="32" h="32"/>
<target id="s_sigma_star_name" x="400" y="290" w="32" h="32"/>
<target id="p_pi_name" x="400" y="220" w="32" h="32"/>
<target id="p_sigma_name" x="400" y="170" w="32" h="32"/>
<target id="p_pi_star_name" x="400" y="40" w="32" h="32"/>
<target id="p_sigma_star_name" x="400" y="0" w="32" h="32"/>
</drag_and_drop_input>
<answer type="loncapa/python"><![CDATA[
correct_answer = [
{
'draggables': ['1'],
'targets': [
's_left', 's_right', 's_sigma', 's_sigma_star', 'p_pi_1', 'p_pi_2'
],
'rule': 'exact'
}, {
'draggables': ['7'],
'targets': ['p_left_1', 'p_left_2', 'p_right_1','p_right_2'],
'rule': 'exact'
}, {
'draggables': ['11'],
'targets': ['s_sigma_name', 'p_sigma_name'],
'rule': 'exact'
}, {
'draggables': ['13'],
'targets': ['s_sigma_star_name', 'p_sigma_star_name'],
'rule': 'exact'
}, {
'draggables': ['15'],
'targets': ['p_pi_name'],
'rule': 'exact'
}, {
'draggables': ['16'],
'targets': ['p_pi_star_name'],
'rule': 'exact'
}]
if draganddrop.grade(submission[0], correct_answer):
correct = ['correct']
else:
correct = ['incorrect']
]]></answer>
</customresponse>
<customresponse>
<text>
<h4>[Many draggables per target]</h4><br/>
<h4>Move two Stars and three Ants to most left target
and one Label3 and four Label2 to most right target.</h4><br/>
</text>
<drag_and_drop_input img="/static/images/cow.png" target_outline="true" one_per_target="false">
<draggable id="1" label="Label 1" can_reuse="true" />
<draggable id="name_with_icon" label="Ant" icon="/static/images/images_list/ant.jpg" can_reuse="true" />
<draggable id="with_icon" label="Cloud" icon="/static/images/images_list/cloud.jpg" can_reuse="true" />
<draggable id="5" label="Label2" can_reuse="true" />
<draggable id="2" label="Drop" icon="/static/images/images_list/drop.jpg" can_reuse="true" />
<draggable id="name_label_icon3" label="Grass" icon="/static/images/images_list/grass.jpg" can_reuse="true" />
<draggable id="name4" label="Star" icon="/static/images/images_list/star.png" can_reuse="true" />
<draggable id="7" label="Label3" can_reuse="true" />
<target id="t1" x="20" y="20" w="90" h="90"/>
<target id="t2" x="300" y="100" w="90" h="90"/>
<target id="t3" x="150" y="40" w="50" h="50"/>
</drag_and_drop_input>
<answer type="loncapa/python"><![CDATA[
correct_answer = [
{
'draggables': ['name4'],
'targets': [
't1', 't1'
],
'rule': 'exact'
},
{
'draggables': ['name_with_icon'],
'targets': [
't1', 't1', 't1'
],
'rule': 'exact'
},
{
'draggables': ['5'],
'targets': [
't2', 't2', 't2', 't2'
],
'rule': 'exact'
},
{
'draggables': ['7'],
'targets': [
't2'
],
'rule': 'exact'
}
]
if draganddrop.grade(submission[0], correct_answer):
correct = ['correct']
else:
correct = ['incorrect']
]]></answer>
</customresponse>
<customresponse>
<text>
<h4>[Draggables can be placed anywhere on base image]</h4><br/>
<h4>
Place -Grass- in the middle of the image and -Ant- in the
right upper corner.</h4><br/>
</text>
<drag_and_drop_input img="/static/images/cow.png" >
<draggable id="1" label="Label 1" can_reuse="true" />
<draggable id="ant" label="Ant" icon="/static/images/images_list/ant.jpg" can_reuse="true" />
<draggable id="with_icon" label="Cloud" icon="/static/images/images_list/cloud.jpg" can_reuse="true" />
<draggable id="5" label="Label2" can_reuse="true" />
<draggable id="2" label="Drop" icon="/static/images/images_list/drop.jpg" can_reuse="true" />
<draggable id="grass" label="Grass" icon="/static/images/images_list/grass.jpg" can_reuse="true" />
<draggable id="name4" label="Star" icon="/static/images/images_list/star.png" can_reuse="true" />
<draggable id="7" label="Label3" can_reuse="true" />
</drag_and_drop_input>
<answer type="loncapa/python"><![CDATA[
correct_answer = {
'grass': [[300, 200], 200],
'ant': [[500, 0], 200]
}
if draganddrop.grade(submission[0], correct_answer):
correct = ['correct']
else:
correct = ['incorrect']
]]></answer>
</customresponse>
<customresponse>
<text>
<h4>[Another anyof example]</h4><br/>
<h4>Please identify the Carbon and Oxygen atoms in the molecule.</h4><br/>
</text>
<drag_and_drop_input img="/static/images/images_list/ethglycol.jpg" target_outline="true" one_per_target="true">
<draggable id="l1_c" label="Carbon" can_reuse="true" />
<draggable id="l2" label="Methane" can_reuse="true" />
<draggable id="l3_o" label="Oxygen" can_reuse="true" />
<draggable id="l4" label="Calcium" can_reuse="true" />
<draggable id="l7" label="Hydrogen" can_reuse="true" />
<target id="t1_o" x="10" y="67" w="100" h="100"/>
<target id="t2" x="133" y="3" w="70" h="70"/>
<target id="t3" x="2" y="384" w="70" h="70"/>
<target id="t4" x="95" y="386" w="70" h="70"/>
<target id="t5_c" x="94" y="293" w="91" h="91"/>
<target id="t6_c" x="328" y="294" w="91" h="91"/>
<target id="t7" x="393" y="463" w="70" h="70"/>
<target id="t8" x="344" y="214" w="70" h="70"/>
<target id="t9_o" x="445" y="162" w="100" h="100"/>
<target id="t10" x="591" y="132" w="70" h="70"/>
</drag_and_drop_input>
<answer type="loncapa/python"><![CDATA[
correct_answer = [
{
'draggables': ['l3_o'],
'targets': ['t1_o', 't9_o'],
'rule': 'exact'
},
{
'draggables': ['l1_c'],
'targets': ['t5_c', 't6_c'],
'rule': 'exact'
}
]
if draganddrop.grade(submission[0], correct_answer):
correct = ['correct']
else:
correct = ['incorrect']
]]></answer>
</customresponse>
<customresponse>
<text>
<h4>[Exact number of draggables for a set of targets.]</h4><br/>
<h4>Drag two Grass and one Star to first or second positions, and three Cloud to any of the three positions.</h4>
<br/>
</text>
<drag_and_drop_input img="/static/images/cow.png" target_outline="true" one_per_target="false">
<draggable id="1" label="Label 1" can_reuse="true" />
<draggable id="name_with_icon" label="Ant" icon="/static/images/images_list/ant.jpg" can_reuse="true" />
<draggable id="with_icon" label="Cloud" icon="/static/images/images_list/cloud.jpg" can_reuse="true" />
<draggable id="5" label="Label2" can_reuse="true" />
<draggable id="2" label="Drop" icon="/static/images/images_list/drop.jpg" can_reuse="true" />
<draggable id="name_label_icon3" label="Grass" icon="/static/images/images_list/grass.jpg" can_reuse="true" />
<draggable id="name4" label="Star" icon="/static/images/images_list/star.png" can_reuse="true" />
<draggable id="7" label="Label3" can_reuse="true" />
<target id="t1" x="20" y="20" w="90" h="90"/>
<target id="t2" x="300" y="100" w="90" h="90"/>
<target id="t3" x="150" y="40" w="50" h="50"/>
</drag_and_drop_input>
<answer type="loncapa/python"><![CDATA[
correct_answer = [
{
'draggables': ['name_label_icon3', 'name_label_icon3'],
'targets': ['t1', 't3'],
'rule': 'unordered_equal+number'
},
{
'draggables': ['name4'],
'targets': ['t1', 't3'],
'rule': 'anyof+number'
},
{
'draggables': ['with_icon', 'with_icon', 'with_icon'],
'targets': ['t1', 't2', 't3'],
'rule': 'anyof+number'
}
]
if draganddrop.grade(submission[0], correct_answer):
correct = ['correct']
else:
correct = ['incorrect']
]]></answer>
</customresponse>
<customresponse>
<text>
<h4>[As many as you like draggables for a set of targets.]</h4><br/>
<h4>Drag some Grass to any of the targets, and some Stars to either first or last target.</h4>
<br/>
</text>
<drag_and_drop_input img="/static/images/cow.png" target_outline="true" one_per_target="false">
<draggable id="1" label="Label 1" can_reuse="true" />
<draggable id="name_with_icon" label="Ant" icon="/static/images/images_list/ant.jpg" can_reuse="true" />
<draggable id="with_icon" label="Cloud" icon="/static/images/images_list/cloud.jpg" can_reuse="true" />
<draggable id="5" label="Label2" can_reuse="true" />
<draggable id="2" label="Drop" icon="/static/images/images_list/drop.jpg" can_reuse="true" />
<draggable id="name_label_icon3" label="Grass" icon="/static/images/images_list/grass.jpg" can_reuse="true" />
<draggable id="name4" label="Star" icon="/static/images/images_list/star.png" can_reuse="true" />
<draggable id="7" label="Label3" can_reuse="true" />
<target id="t1" x="20" y="20" w="90" h="90"/>
<target id="t2" x="300" y="100" w="90" h="90"/>
<target id="t3" x="150" y="40" w="50" h="50"/>
</drag_and_drop_input>
<answer type="loncapa/python"><![CDATA[
correct_answer = [
{
'draggables': ['name_label_icon3'],
'targets': ['t1', 't2', 't3'],
'rule': 'anyof'
},
{
'draggables': ['name4'],
'targets': ['t1', 't2'],
'rule': 'anyof'
}
]
if draganddrop.grade(submission[0], correct_answer):
correct = ['correct']
else:
correct = ['incorrect']
]]></answer>
</customresponse>
</problem>
**********************************************
Xml format of drag and drop input [inputtypes]
**********************************************
.. module:: drag_and_drop_input
Format description
==================
The main tag of Drag and Drop (DnD) input is::
<drag_and_drop_input> ... </drag_and_drop_input>
``drag_and_drop_input`` can include any number of the following 2 tags:
``draggable`` and ``target``.
drag_and_drop_input tag
-----------------------
The main container for a single instance of DnD. The following attributes can
be specified for this tag::
img - Relative path to an image that will be the base image. All draggables
can be dragged onto it.
target_outline - Specify whether an outline (gray dashed line) should be
drawn around targets (if they are specified). It can be either
'true' or 'false'. If not specified, the default value is
'false'.
one_per_target - Specify whether to allow more than one draggable to be
placed onto a single target. It can be either 'true' or 'false'. If
not specified, the default value is 'true'.
no_labels - default is false, in default behaviour if label is not set, label
is obtained from id. If no_labels is true, labels are not automatically
populated from id, and one can not set labels and obtain only icons.
draggable tag
-------------
Draggable tag specifies a single draggable object which has the following
attributes::
id - Unique identifier of the draggable object.
label - Human readable label that will be shown to the user.
icon - Relative path to an image that will be shown to the user.
can_reuse - true or false, default is false. If true, same draggable can be
used multiple times.
A draggable is what the user must drag out of the slider and place onto the
base image. After a drag operation, if the center of the draggable ends up
outside the rectangular dimensions of the image, it will be returned back
to the slider.
In order for the grader to work, it is essential that a unique ID
is provided. Otherwise, there will be no way to tell which draggable is at what
coordinate, or over what target. Label and icon attributes are optional. If
they are provided they will be used, otherwise, you can have an empty
draggable. The path is relative to 'course_folder' folder, for example,
/static/images/img1.png.
target tag
----------
Target tag specifies a single target object which has the following required
attributes::
id - Unique identifier of the target object.
x - X-coordinate on the base image where the top left corner of the target
will be positioned.
y - Y-coordinate on the base image where the top left corner of the target
will be positioned.
w - Width of the target.
h - Height of the target.
A target specifies a place on the base image where a draggable can be
positioned. By design, if the center of a draggable lies within the target
(i.e. in the rectangle defined by [[x, y], [x + w, y + h]], then it is within
the target. Otherwise, it is outside.
If at lest one target is provided, the behavior of the client side logic
changes. If a draggable is not dragged on to a target, it is returned back to
the slider.
If no targets are provided, then a draggable can be dragged and placed anywhere
on the base image.
correct answer format
---------------------
There are two correct answer formats: short and long
If short from correct answer is mapping of 'draggable_id' to 'target_id'::
correct_answer = {'grass': [[300, 200], 200], 'ant': [[500, 0], 200]}
correct_answer = {'name4': 't1', '7': 't2'}
In long form correct answer is list of dicts. Every dict has 3 keys:
draggables, targets and rule. For example::
correct_answer = [
{
'draggables': ['7', '8'],
'targets': ['t5_c', 't6_c'],
'rule': 'anyof'
},
{
'draggables': ['1', '2'],
'targets': ['t2_h', 't3_h', 't4_h', 't7_h', 't8_h', 't10_h'],
'rule': 'anyof'
}]
Draggables is list of draggables id. Target is list of targets id, draggables
must be dragged to with considering rule. Rule is string.
Draggables in dicts inside correct_answer list must not intersect!!!
Wrong (for draggable id 7)::
correct_answer = [
{
'draggables': ['7', '8'],
'targets': ['t5_c', 't6_c'],
'rule': 'anyof'
},
{
'draggables': ['7', '2'],
'targets': ['t2_h', 't3_h', 't4_h', 't7_h', 't8_h', 't10_h'],
'rule': 'anyof'
}]
Rules are: exact, anyof, unordered_equal, anyof+number, unordered_equal+number
.. such long lines are needed for sphinx to display lists correctly
- Exact rule means that targets for draggable id's in user_answer are the same that targets from correct answer. For example, for draggables 7 and 8 user must drag 7 to target1 and 8 to target2 if correct_answer is::
correct_answer = [
{
'draggables': ['7', '8'],
'targets': ['tartget1', 'target2'],
'rule': 'exact'
}]
- unordered_equal rule allows draggables be dragged to targets unordered. If one want to allow for student to drag 7 to target1 or target2 and 8 to target2 or target 1 and 7 and 8 must be in different targets, then correct answer must be::
correct_answer = [
{
'draggables': ['7', '8'],
'targets': ['tartget1', 'target2'],
'rule': 'unordered_equal'
}]
- Anyof rule allows draggables to be dragged to any of targets. If one want to allow for student to drag 7 and 8 to target1 or target2, which means that if 7 is on target1 and 8 is on target1 or 7 on target2 and 8 on target2 or 7 on target1 and 8 on target2. Any of theese are correct which anyof rule::
correct_answer = [
{
'draggables': ['7', '8'],
'targets': ['tartget1', 'target2'],
'rule': 'anyof'
}]
- If you have can_reuse true, then you, for example, have draggables a,b,c and 10 targets. These will allow you to drag 4 'a' draggables to ['target1', 'target4', 'target7', 'target10'] , you do not need to write 'a' four times. Also this will allow you to drag 'b' draggable to target2 or target5 for target5 and target2 etc..::
correct_answer = [
{
'draggables': ['a'],
'targets': ['target1', 'target4', 'target7', 'target10'],
'rule': 'unordered_equal'
},
{
'draggables': ['b'],
'targets': ['target2', 'target5', 'target8'],
'rule': 'anyof'
},
{
'draggables': ['c'],
'targets': ['target3', 'target6', 'target9'],
'rule': 'unordered_equal'
}]
- And sometimes you want to allow drag only two 'b' draggables, in these case you sould use 'anyof+number' of 'unordered_equal+number' rule::
correct_answer = [
{
'draggables': ['a', 'a', 'a'],
'targets': ['target1', 'target4', 'target7'],
'rule': 'unordered_equal+numbers'
},
{
'draggables': ['b', 'b'],
'targets': ['target2', 'target5', 'target8'],
'rule': 'anyof+numbers'
},
{
'draggables': ['c'],
'targets': ['target3', 'target6', 'target9'],
'rule': 'unordered_equal'
}]
In case if we have no multiple draggables per targets (one_per_target="true"),
for same number of draggables, anyof is equal to unordered_equal
If we have can_reuse=true, than one must use only long form of correct answer.
Grading logic
-------------
1. User answer (that comes from browser) and correct answer (from xml) are parsed to the same format::
group_id: group_draggables, group_targets, group_rule
Group_id is ordinal number, for every dict in correct answer incremental
group_id is assigned: 0, 1, 2, ...
Draggables from user answer are added to same group_id where identical draggables
from correct answer are, for example::
If correct_draggables[group_0] = [t1, t2] then
user_draggables[group_0] are all draggables t1 and t2 from user answer:
[t1] or [t1, t2] or [t1, t2, t2] etc..
2. For every group from user answer, for that group draggables, if 'number' is in group rule, set() is applied,
if 'number' is not in rule, set is not applied::
set() : [t1, t2, t3, t3] -> [t1, t2, ,t3]
For every group, at this step, draggables lists are equal.
3. For every group, lists of targets are compared using rule for that group.
Set and '+number' cases
.......................
Set() and '+number' are needed only for case of reusable draggables,
for other cases there are no equal draggables in list, so set() does nothing.
.. such long lines needed for sphinx to display nicely
* Usage of set() operation allows easily create rule for case of "any number of same draggable can be dragged to some targets"::
{
'draggables': ['draggable_1'],
'targets': ['target3', 'target6', 'target9'],
'rule': 'anyof'
}
* 'number' rule is used for the case of reusable draggables, when one want to fix number of draggable to drag. In this example only two instances of draggables_1 are allowed to be dragged::
{
'draggables': ['draggable_1', 'draggable_1'],
'targets': ['target3', 'target6', 'target9'],
'rule': 'anyof+number'
}
* Note, that in using rule 'exact', one does not need 'number', because you can't recognize from user interface which reusable draggable is on which target. Absurd example::
{
'draggables': ['draggable_1', 'draggable_1', 'draggable_2'],
'targets': ['target3', 'target6', 'target9'],
'rule': 'exact'
}
Correct handling of this example is to create different rules for draggable_1 and
draggable_2
* For 'unordered_equal' (or 'exact' too) we don't need 'number' if you have only same draggable in group, as targets length will provide constraint for the number of draggables::
{
'draggables': ['draggable_1'],
'targets': ['target3', 'target6', 'target9'],
'rule': 'unordered_equal'
}
This means that only three draggaggables 'draggable_1' can be dragged.
* But if you have more that one different reusable draggable in list, you may use 'number' rule::
{
'draggables': ['draggable_1', 'draggable_1', 'draggable_2'],
'targets': ['target3', 'target6', 'target9'],
'rule': 'unordered_equal+number'
}
If not use number, draggables list will be setted to ['draggable_1', 'draggable_2']
Logic flow
----------
(Click on image to see full size version.)
.. image:: draganddrop_logic_flow.png
:width: 100%
:target: _images/draganddrop_logic_flow.png
Example
=======
Examples of draggables that can't be reused
-------------------------------------------
.. literalinclude:: drag-n-drop-demo.xml
Draggables can be reused
------------------------
.. literalinclude:: drag-n-drop-demo2.xml
......@@ -5,4 +5,5 @@ Contents:
.. toctree::
:maxdepth: 2
graphical_slider_tool.rst
\ No newline at end of file
graphical_slider_tool.rst
drag_and_drop_input.rst
......@@ -432,10 +432,11 @@ courseware_only_js += [
in glob2.glob(PROJECT_ROOT / 'static/coffee/src/modules/**/*.coffee')
]
# 'js/vendor/RequireJS.js' - Require JS wrapper.
# See https://edx-wiki.atlassian.net/wiki/display/LMS/Integration+of+Require+JS+into+the+system
main_vendor_js = [
'js/vendor/RequireJS.js',
'js/vendor/json2.js',
'js/vendor/RequireJS.js',
'js/vendor/jquery.min.js',
'js/vendor/jquery-ui.min.js',
'js/vendor/jquery.cookie.js',
......
......@@ -154,8 +154,9 @@ def my_sympify(expr, normphase=False, matrix=False, abcsym=False, do_qubit=False
class formula(object):
'''
Representation of a mathematical formula object. Accepts mathml math expression for constructing,
and can produce sympy translation. The formula may or may not include an assignment (=).
Representation of a mathematical formula object. Accepts mathml math expression
for constructing, and can produce sympy translation. The formula may or may not
include an assignment (=).
'''
def __init__(self, expr, asciimath='', options=None):
self.expr = expr.strip()
......@@ -194,8 +195,12 @@ class formula(object):
def preprocess_pmathml(self, xml):
'''
Pre-process presentation MathML from ASCIIMathML to make it more acceptable for SnuggleTeX, and also
to accomodate some sympy conventions (eg hat(i) for \hat{i}).
Pre-process presentation MathML from ASCIIMathML to make it more
acceptable for SnuggleTeX, and also to accomodate some sympy
conventions (eg hat(i) for \hat{i}).
This method would be a good spot to look for an integral and convert
it, if possible...
'''
if type(xml) == str or type(xml) == unicode:
......@@ -266,6 +271,9 @@ class formula(object):
'''
Return sympy expression for the math formula.
The math formula is converted to Content MathML then that is parsed.
This is a recursive function, called on every CMML node. Support for
more functions can be added by modifying opdict, abould halfway down
'''
if self.the_sympy: return self.the_sympy
......
......@@ -157,13 +157,33 @@ def symmath_check(expect, ans, dynamath=None, options=None, debug=None, xml=None
'''
Check a symbolic mathematical expression using sympy.
The input may be presentation MathML. Uses formula.
This is the default Symbolic Response checking function
Desc of args:
expect is a sympy string representing the correct answer. It is interpreted
using my_sympify (from formula.py), which reads strings as sympy input
(e.g. 'integrate(x^2, (x,1,2))' would be valid, and evaluate to give 1.5)
ans is student-typed answer. It is expected to be ascii math, but the code
below would support a sympy string.
dynamath is the PMathML string converted by MathJax. It is used if
evaluation with ans is not sufficient.
options is a string with these possible substrings, set as an xml property
of the problem:
-matrix - make a sympy matrix, rather than a list of lists, if possible
-qubit - passed to my_sympify
-imaginary - used in formla, presumably to signal to use i as sqrt(-1)?
-numerical - force numerical comparison.
'''
msg = ''
# msg += '<p/>abname=%s' % abname
# msg += '<p/>adict=%s' % (repr(adict).replace('<','&lt;'))
threshold = 1.0e-3
threshold = 1.0e-3 # for numerical comparison (also with matrices)
DEBUG = debug
if xml is not None:
......@@ -184,13 +204,17 @@ def symmath_check(expect, ans, dynamath=None, options=None, debug=None, xml=None
msg += '<p>Error %s in parsing OUR expected answer "%s"</p>' % (err, expect)
return {'ok': False, 'msg': make_error_message(msg)}
###### Sympy input #######
# if expected answer is a number, try parsing provided answer as a number also
try:
fans = my_sympify(str(ans), matrix=do_matrix, do_qubit=do_qubit)
except Exception, err:
fans = None
if hasattr(fexpect, 'is_number') and fexpect.is_number and fans and hasattr(fans, 'is_number') and fans.is_number:
# do a numerical comparison if both expected and answer are numbers
if (hasattr(fexpect, 'is_number') and fexpect.is_number and fans
and hasattr(fans, 'is_number') and fans.is_number):
if abs(abs(fans - fexpect) / fexpect) < threshold:
return {'ok': True, 'msg': msg}
else:
......@@ -208,6 +232,8 @@ def symmath_check(expect, ans, dynamath=None, options=None, debug=None, xml=None
msg += '<p>You entered: %s</p>' % to_latex(fans)
return {'ok': True, 'msg': msg}
###### PMathML input ######
# convert mathml answer to formula
try:
if dynamath:
......@@ -216,6 +242,7 @@ def symmath_check(expect, ans, dynamath=None, options=None, debug=None, xml=None
mmlans = None
if not mmlans:
return {'ok': False, 'msg': '[symmath_check] failed to get MathML for input; dynamath=%s' % dynamath}
f = formula(mmlans, options=options)
# get sympy representation of the formula
......@@ -238,7 +265,7 @@ def symmath_check(expect, ans, dynamath=None, options=None, debug=None, xml=None
msg += '<hr>'
return {'ok': False, 'msg': make_error_message(msg)}
# compare with expected
# do numerical comparison with expected
if hasattr(fexpect, 'is_number') and fexpect.is_number:
if hasattr(fsym, 'is_number') and fsym.is_number:
if abs(abs(fsym - fexpect) / fexpect) < threshold:
......@@ -250,6 +277,10 @@ def symmath_check(expect, ans, dynamath=None, options=None, debug=None, xml=None
# msg += "<p>cmathml = <pre>%s</pre></p>" % str(f.cmathml).replace('<','&lt;')
return {'ok': False, 'msg': make_error_message(msg)}
# Here is a good spot for adding calls to X.simplify() or X.expand(),
# allowing equivalence over binomial expansion or trig identities
# exactly the same?
if fexpect == fsym:
return {'ok': True, 'msg': msg}
......
b4d043bb1ca4a8815d4a388a2c9d96038211417b
\ No newline at end of file
......@@ -6,16 +6,25 @@
<link type="text/html" rel="alternate" href="http://blog.edx.org/"/>
##<link type="application/atom+xml" rel="self" href="https://github.com/blog.atom"/>
<title>EdX Blog</title>
<updated>2012-12-19T14:00:12-07:00</updated>
<updated>2013-01-21T14:00:12-07:00</updated>
<entry>
<id>tag:www.edx.org,2012:Post/10</id>
<published>2012-12-19T14:00:00-07:00</published>
<updated>2012-12-19T14:00:00-07:00</updated>
<link type="text/html" rel="alternate" href="${reverse('press/spring-courses')}"/>
<title>edX announces first wave of new courses for Spring 2013</title>
<content type="html">&lt;img src=&quot;${static.url('images/press/releases/edx-logo_240x180.png')}&quot; /&gt;
<id>tag:www.edx.org,2012:Post/11</id>
<published>2013-01-22T10:00:00-07:00</published>
<updated>2013-01-22T10:00:00-07:00</updated>
<link type="text/html" rel="alternate" href="${reverse('press/lewin-course-announcement')}"/>
<title>New course from legendary MIT physics professor Walter Lewin</title>
<content type="html">&lt;img src=&quot;${static.url('images/press/releases/dr-lewin-316_240x180.jpg')}&quot; /&gt;
&lt;p&gt;&lt;/p&gt;</content>
</entry>
<!-- <entry> -->
<!-- <id>tag:www.edx.org,2012:Post/10</id> -->
<!-- <published>2012-12-19T14:00:00-07:00</published> -->
<!-- <updated>2012-12-19T14:00:00-07:00</updated> -->
<!-- <link type="text/html" rel="alternate" href="${reverse('press/spring-courses')}"/> -->
<!-- <title>edX announces first wave of new courses for Spring 2013</title> -->
<!-- <content type="html">&lt;img src=&quot;${static.url('images/press/releases/edx-logo_240x180.png')}&quot; /&gt; -->
<!-- &lt;p&gt;&lt;/p&gt;</content> -->
<!-- </entry> -->
<entry>
<id>tag:www.edx.org,2012:Post/9</id>
<published>2012-12-10T14:00:00-07:00</published>
......
......@@ -89,7 +89,7 @@
<figure>
<a rel="asset" class="action action-download" href="${static.url('images/press-kit/3.091x_high-res.png')}">
<img src="${static.url('images/press-kit/3.091x_x200.jpg')}"/>
<figcaption>Screenshot of 6.00x: Introduction to Computer Science and Programming.</figcaption>
<figcaption>Screenshot of 3.091x: Introduction to Solid State Chemistry.</figcaption>
<span class="note">Download (High Resolution Photo)</span>
</a>
</figure>
......@@ -108,4 +108,4 @@
return false;
});
</script>
</%block>
\ No newline at end of file
</%block>
<%! from django.core.urlresolvers import reverse %>
<%inherit file="../../main.html" />
<%namespace name='static' file='../../static_content.html'/>
<%block name="title"><title>New Course from legendary MIT physics professor Walter Lewin</title></%block>
<div id="fb-root"></div>
<script>(function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/en_US/all.js#xfbml=1";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));</script>
<section class="pressrelease">
<section class="container">
<h1>Afraid of physics? Do you hate it?<br/>Walter Lewin will make you love physics whether you like it or not</h1>
<hr class="horizontal-divider">
<article>
<h2>MIT physics professor and online web star brings his renowned Electricity and Magnetism course to edX</h2>
<figure>
<a href="${static.url('images/press/releases/dr-lewin-276_2400x1600.jpg')}"><img src="${static.url('images/press/releases/dr-lewin-276_240x180.jpg')}" /></a>
<figcaption>
<p>Walter Lewin, legendary MIT physics professor, demonstrates, in his inimitable fashion, one of the many laws of physics covered in his new course on edX.</p>
<p>Credit: Dominick Reuter</p>
<a href="${static.url('images/press/releases/dr-lewin-276_2400x1600.jpg')}">High Resolution Image</a></p>
</figcaption>
</figure>
<p><strong>CAMBRIDGE, MA &ndash; January 22, 2013 &ndash;</strong> <a href="http://www.edx.org">EdX</a>, the not-for-profit online learning initiative founded by Harvard University and the Massachusetts Institute of Technology (MIT), announced today a new course from the legendary Professor Walter Lewin who, for 47 years, has provided generations of MIT students – and millions watching online – with his inspiring and unconventional lectures. Now, with this edX version of Professor Lewin’s famous course Electricity and Magnetism (Physics), people around the world can experience it just like his students on the MIT campus. MITx <a href="https://www.edx.org/courses/MITx/8.02x/2013_Spring/about">8.02x Electricity and Magnetism</a> is now open for enrollment and classes will begin on February 18, 2013.</p>
<p>“I have taught this course to tens of thousands and many tell me it changed their lives,” said Walter Lewin, Professor of Physics at MIT. “Teaching is my passion: I want to open peoples’ eyes and minds to the beauty of physics so they will begin to see the world in a new way.”</p>
<p>In <a href="https://www.edx.org/courses/MITx/8.02x/2013_Spring/about">8.02x Electricity and Magnetism</a>, Professor Lewin will teach students to “see” the world instead of just “looking at” it. He will make them “see” natural phenomena such as rainbows in a way they never imagined before. Through his dynamic teaching, enthusiasm and great sense of humor, Professor Lewin has an innate ability to make difficult concepts easy. The New York Times has crowned him a “Web Star” and noted how his lectures, with their engaging physics demonstrations, have won him devotees around the world. While this course is MIT level, edX and Professor Lewin encourage even senior high school students from around the world to watch his lectures and take the course.</p>
<p>“Walter Lewin is an international treasure,” said Anant Agarwal, President of edX. “His physics lectures on the MIT campus were already legendary before he put them online and they became an international sensation. We know edX learners will be awestruck by his provocative and enlightening course.”</p>
<p>In addition to the basic concepts of Electromagnetism, a vast variety of interesting topics are covered, including Lightning, Pacemakers, Electric Shock Treatment, Electrocardiograms, Metal Detectors, Musical Instruments, Magnetic Levitation, Bullet Trains, Electric Motors, Radios, TV, Car Coils, Superconductivity, Aurora Borealis, Rainbows, Radio Telescopes, Interferometers, Particle Accelerators such as the Large Hadron Collider, Mass Spectrometers, Red Sunsets, Blue Skies, Haloes around Sun and Moon, Color Perception, Doppler Effect and Big-Bang Cosmology.</p>
<p>Professor Lewin received his PhD in Nuclear Physics at the Technical University in Delft, the Netherlands in 1965. He joined the Physics faculty at MIT in 1966 and became a pioneer in the new field of X-ray Astronomy. His 105 online lectures are world-renowned and are viewed by nearly 2 million people annually. Professor Lewin has received five teaching awards and is the only MIT professor featured in "The Best 300 Professors" by The Princeton Review. He has co-authored with Warren Goldstein the book "For the Love of Physics" (Free Press, Simon & Schuster), which has been translated into 9 languages.</p>
<p>Previously announced new 2013 courses include: <a href="http://www.edx.org/courses/HarvardX/ER22x/2013_Spring/about">Justice from Michael Sandel</a>; <a href="http://www.edx.org/courses/BerkeleyX/Stat2.1x/2013_Spring/about">Introduction to Statistics from Ani Adhikari</a>; <a href="http://www.edx.org/courses/MITx/14.73x/2013_Spring/about">The Challenges of Global Poverty from Esther Duflo</a>; <a href="http://www.edx.org/courses/HarvardX/CB22x/2013_Spring/about">The Ancient Greek Hero from Gregory Nagy</a>; <a href="https://www.edx.org/courses/BerkeleyX/CS191x/2013_Spring/about">Quantum Mechanics and Quantum Computation from Umesh Vazirani</a>; <a href="https://www.edx.org/courses/HarvardX/PH278x/2013_Spring/about">Human Health and Global Environmental Change, from Aaron Bernstein and Jack Spengler</a>.</p>
<p>In addition to these new courses, edX is bringing back several courses from the popular fall 2012 semester: <a href="http://www.edx.org/courses/MITx/6.00x/2013_Spring/about">Introduction to Computer Science and Programming</a>; <a href="http://www.edx.org/courses/MITx/3.091x/2013_Spring/about">Introduction to Solid State Chemistry</a>; <a href="http://www.edx.org/courses/BerkeleyX/CS188.1x/2013_Spring/about">Introduction to Artificial Intelligence</a>; <a href="https://www.edx.org/courses/BerkeleyX/CS169.1x/2013_Spring/about">Software as a Service I</a>; <a href="https://www.edx.org/courses/BerkeleyX/CS169.2x/2013_Spring/about">Software as a Service II</a>; <a href="http://www.edx.org/courses/BerkeleyX/CS184.1x/2013_Spring/about">Foundations of Computer Graphics</a>.</p>
<h2>About edX</h2>
<p><a href="https://www.edx.org/">EdX</a> is a not-for-profit enterprise of its founding partners <a href="http://www.harvard.edu">Harvard University</a> and the <a href="http://www.mit.edu">Massachusetts Institute of Technology</a> focused on transforming online and on-campus learning through groundbreaking methodologies, game-like experiences and cutting-edge research. EdX provides inspirational and transformative knowledge to students of all ages, social status, and income who form worldwide communities of learners. EdX uses its open source technology to transcend physical and social borders. We’re focused on people, not profit. EdX is based in Cambridge, Massachusetts in the USA.</p>
<section class="contact">
<p><strong>Contact:</strong></p>
<p>Brad Baker, Weber Shandwick for edX</p>
<p>BBaker@webershandwick.com</p>
<p>(617) 520-7043</p>
</section>
<section class="footer">
<hr class="horizontal-divider">
<div class="logo"></div><h3 class="date">12 - 22 - 2013</h3>
<div class="social-sharing">
<hr class="horizontal-divider">
<p>Share with friends and family:</p>
<a href="http://twitter.com/intent/tweet?text=:MIT+physics+professor+and+online+web+star+brings+his+renowned+Electricity+and+Magnetism+course+to+edX+http://www.edx.org/press/lewin-course-announcement" class="share">
<img src="${static.url('images/social/twitter-sharing.png')}">
</a>
</a>
<a href="mailto:?subject=MIT%20physics%20professor%20and%20online%20web%20star%20brings%20his%20renowned%20Electricity%20and%20Magnetism%20course%20to%20edX&body=Afraid%20of%20physics?%20Do%20you%20hate%20it?%20Walter%20Lewin%20will%20make%20you%20love%20physics%20whether%20you%20like%20it%20or%20not…http://edx.org/press/lewin-course-announcement" class="share">
<img src="${static.url('images/social/email-sharing.png')}">
</a>
<div class="fb-like" data-href="http://edx.org/press/lewin-course-announcement" data-send="true" data-width="450" data-show-faces="true"></div>
</div>
</section>
</article>
</section>
</section>
......@@ -118,9 +118,11 @@ urlpatterns = ('',
{'template': 'press_releases/Georgetown_joins_edX.html'}, name="press/georgetown-joins-edx"),
url(r'^press/spring-courses$', 'static_template_view.views.render',
{'template': 'press_releases/Spring_2013_course_announcements.html'}, name="press/spring-courses"),
url(r'^press/lewin-course-announcement$', 'static_template_view.views.render',
{'template': 'press_releases/Lewin_course_announcement.html'}, name="press/lewin-course-announcement"),
# Should this always update to point to the latest press release?
(r'^pressrelease$', 'django.views.generic.simple.redirect_to', {'url': '/press/spring-courses'}),
(r'^pressrelease$', 'django.views.generic.simple.redirect_to', {'url': '/press/lewin-course-announcement'}),
(r'^favicon\.ico$', 'django.views.generic.simple.redirect_to', {'url': '/static/images/favicon.ico'}),
......
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