diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py
index 4287dba604..af4a447a84 100644
--- a/common/lib/capa/capa/inputtypes.py
+++ b/common/lib/capa/capa/inputtypes.py
@@ -798,6 +798,10 @@ class DragAndDropInput(InputTypeBase):
if tag_type == 'draggable' and not self.no_labels:
dic['label'] = dic['label'] or dic['id']
+ if tag_type == 'draggable':
+ dic['target_fields'] = [parse(target, 'target') for target in
+ tag.iterchildren('target')]
+
return dic
# add labels to images?:
@@ -909,15 +913,15 @@ registry.register(DesignProtein2dInput)
class EditAGeneInput(InputTypeBase):
"""
An input type for editing a gene. Integrates with the genex java applet.
-
+
Example:
-
+
"""
-
+
template = "editageneinput.html"
tags = ['editageneinput']
-
+
@classmethod
def get_attributes(cls):
"""
@@ -927,14 +931,14 @@ class EditAGeneInput(InputTypeBase):
Attribute('height'),
Attribute('dna_sequence')
]
-
+
def _extra_context(self):
"""
"""
context = {
'applet_loader': '/static/js/capa/edit-a-gene.js',
}
-
+
return context
registry.register(EditAGeneInput)
diff --git a/common/lib/capa/capa/tests/test_inputtypes.py b/common/lib/capa/capa/tests/test_inputtypes.py
index 4a5ea5c429..8286f16b95 100644
--- a/common/lib/capa/capa/tests/test_inputtypes.py
+++ b/common/lib/capa/capa/tests/test_inputtypes.py
@@ -539,14 +539,14 @@ class DragAndDropTest(unittest.TestCase):
"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": ""}],
+{"can_reuse": "", "label": "Label 1", "id": "1", "icon": "", "target_fields": []},
+{"can_reuse": "", "label": "cc", "id": "name_with_icon", "icon": "/static/images/cc.jpg", "target_fields": []},
+{"can_reuse": "", "label": "arrow-left", "id": "with_icon", "icon": "/static/images/arrow-left.png", "can_reuse": "", "target_fields": []},
+{"can_reuse": "", "label": "Label2", "id": "5", "icon": "", "can_reuse": "", "target_fields": []},
+{"can_reuse": "", "label": "Mute", "id": "2", "icon": "/static/images/mute.png", "can_reuse": "", "target_fields": []},
+{"can_reuse": "", "label": "spinner", "id": "name_label_icon3", "icon": "/static/images/spinner.gif", "can_reuse": "", "target_fields": []},
+{"can_reuse": "", "label": "Star", "id": "name4", "icon": "/static/images/volume.png", "can_reuse": "", "target_fields": []},
+{"can_reuse": "", "label": "Label3", "id": "7", "icon": "", "can_reuse": "", "target_fields": []}],
"one_per_target": "True",
"targets": [
{"y": "90", "x": "210", "id": "t1", "w": "90", "h": "90"},
diff --git a/common/lib/capa/capa/verifiers/draganddrop.py b/common/lib/capa/capa/verifiers/draganddrop.py
index 239ff2b9a4..5d7e4770cf 100644
--- a/common/lib/capa/capa/verifiers/draganddrop.py
+++ b/common/lib/capa/capa/verifiers/draganddrop.py
@@ -27,6 +27,49 @@ values are (x,y) coordinates of centers of dragged images.
import json
+def flat_user_answer(user_answer):
+ """
+ Convert nested `user_answer` to flat format.
+
+ {'up': {'first': {'p': 'p_l'}}}
+
+ to
+
+ {'up': 'p_l[p][first]'}
+ """
+
+ def parse_user_answer(answer):
+ key = answer.keys()[0]
+ value = answer.values()[0]
+ if isinstance(value, dict):
+
+ # Make complex value:
+ # Example:
+ # Create like 'p_l[p][first]' from {'first': {'p': 'p_l'}
+ complex_value_list = []
+ v_value = value
+ while isinstance(v_value, dict):
+ v_key = v_value.keys()[0]
+ v_value = v_value.values()[0]
+ complex_value_list.append(v_key)
+
+ complex_value = '{0}'.format(v_value)
+ for i in reversed(complex_value_list):
+ complex_value = '{0}[{1}]'.format(complex_value, i)
+
+ res = {key: complex_value}
+ return res
+ else:
+ return answer
+
+ result = []
+ for answer in user_answer:
+ parse_answer = parse_user_answer(answer)
+ result.append(parse_answer)
+
+ return result
+
+
class PositionsCompare(list):
""" Class for comparing positions.
@@ -116,37 +159,36 @@ class DragAndDrop(object):
# 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():
-
+ for index, draggable_ids in enumerate(self.correct_groups):
# '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]
+ current_rule = self.correct_positions[index].keys()[0]
if 'number' in current_rule:
- rule_values = self.correct_positions[groupname][current_rule]
+ rule_values = self.correct_positions[index][current_rule]
# clean rule, do not do clean duplicate items
- self.correct_positions[groupname].pop(current_rule, None)
+ self.correct_positions[index].pop(current_rule, None)
parsed_rule = current_rule.replace('+', '').replace('number', '')
- self.correct_positions[groupname][parsed_rule] = rule_values
+ self.correct_positions[index][parsed_rule] = rule_values
else: # remove dublicates
- self.user_groups[groupname] = list(set(self.user_groups[groupname]))
+ self.user_groups[index] = list(set(self.user_groups[index]))
- if sorted(draggable_ids) != sorted(self.user_groups[groupname]):
+ if sorted(draggable_ids) != sorted(self.user_groups[index]):
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:
+ for index, _ in enumerate(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):
+ if self.correct_positions[index].get(rule, None):
rules_executed += 1
if not self.compare_positions(
- self.correct_positions[groupname][rule],
- self.user_positions[groupname]['user'], flag=rule):
+ self.correct_positions[index][rule],
+ self.user_positions[index]['user'], flag=rule):
return False
if not rules_executed: # no correct rules for current group
# probably xml content mistake - wrong rules names
@@ -248,7 +290,7 @@ class DragAndDrop(object):
correct_answer = {'name4': 't1',
'name_with_icon': 't1',
'5': 't2',
- '7':'t2'}
+ '7': 't2'}
It is draggable_name: dragable_position mapping.
@@ -284,24 +326,25 @@ class DragAndDrop(object):
Args:
user_answer: json
- correct_answer: dict or list
+ 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
+ self.correct_groups = [] # Correct groups from xml.
+ self.correct_positions = [] # Correct positions for comparing.
+ self.user_groups = [] # Will be populated from user answer.
+ self.user_positions = [] # Will be populated from user answer.
- # convert from dict answer format to list format
+ # 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)
+ tmp.append({
+ 'draggables': [key],
+ 'targets': [value],
+ 'rule': 'exact'})
correct_answer = tmp
+ # Convert string `user_answer` to object.
user_answer = json.loads(user_answer)
# This dictionary will hold a key for each draggable the user placed on
@@ -312,24 +355,29 @@ class DragAndDrop(object):
self.excess_draggables = dict((users_draggable.keys()[0],True)
for users_draggable in user_answer['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}
+ # Convert nested `user_answer` to flat format.
+ user_answer = flat_user_answer(user_answer)
+
+ # Create identical data structures from user answer and correct answer.
+ for answer in correct_answer:
+ user_groups_data = []
+ user_positions_data = []
+ for draggable_dict in user_answer:
+ # 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(
+ if draggable_name in answer['draggables']:
+ user_groups_data.append(draggable_name)
+ user_positions_data.append(
draggable_dict[draggable_name])
# proved that this is not excess
self.excess_draggables[draggable_name] = False
+ self.correct_groups.append(answer['draggables'])
+ self.correct_positions.append({answer['rule']: answer['targets']})
+ self.user_groups.append(user_groups_data)
+ self.user_positions.append({'user': user_positions_data})
+
+
def grade(user_input, correct_answer):
""" Creates DragAndDrop instance from user_input and correct_answer and
calls DragAndDrop.grade for grading.
diff --git a/common/lib/capa/capa/verifiers/tests_draganddrop.py b/common/lib/capa/capa/verifiers/tests_draganddrop.py
index bcd024fa89..b70c6f1553 100644
--- a/common/lib/capa/capa/verifiers/tests_draganddrop.py
+++ b/common/lib/capa/capa/verifiers/tests_draganddrop.py
@@ -2,6 +2,7 @@ import unittest
import draganddrop
from draganddrop import PositionsCompare
+import json
class Test_PositionsCompare(unittest.TestCase):
@@ -40,10 +41,242 @@ class Test_PositionsCompare(unittest.TestCase):
class Test_DragAndDrop_Grade(unittest.TestCase):
+ def test_targets_are_draggable_1(self):
+ user_input = json.dumps([
+ {'p': 'p_l'},
+ {'up': {'first': {'p': 'p_l'}}}
+ ])
+
+ correct_answer = [
+ {
+ 'draggables': ['p'],
+ 'targets': [
+ 'p_l', 'p_r'
+ ],
+ 'rule': 'anyof'
+ },
+ {
+ 'draggables': ['up'],
+ 'targets': [
+ 'p_l[p][first]'
+ ],
+ 'rule': 'anyof'
+ }
+ ]
+ self.assertTrue(draganddrop.grade(user_input, correct_answer))
+
+ def test_targets_are_draggable_2(self):
+ user_input = json.dumps([
+ {'p': 'p_l'},
+ {'p': 'p_r'},
+ {'s': 's_l'},
+ {'s': 's_r'},
+ {'up': {'1': {'p': 'p_l'}}},
+ {'up': {'3': {'p': 'p_l'}}},
+ {'up': {'1': {'p': 'p_r'}}},
+ {'up': {'3': {'p': 'p_r'}}},
+ {'up_and_down': {'1': {'s': 's_l'}}},
+ {'up_and_down': {'1': {'s': 's_r'}}}
+ ])
+
+ correct_answer = [
+ {
+ 'draggables': ['p'],
+ 'targets': ['p_l', 'p_r'],
+ 'rule': 'unordered_equal'
+ },
+ {
+ 'draggables': ['s'],
+ 'targets': ['s_l', 's_r'],
+ 'rule': 'unordered_equal'
+ },
+ {
+ 'draggables': ['up_and_down'],
+ 'targets': [
+ 's_l[s][1]', 's_r[s][1]'
+ ],
+ 'rule': 'unordered_equal'
+ },
+ {
+ 'draggables': ['up'],
+ 'targets': [
+ 'p_l[p][1]', 'p_l[p][3]', 'p_r[p][1]', 'p_r[p][3]'
+ ],
+ 'rule': 'unordered_equal'
+ }
+ ]
+ self.assertTrue(draganddrop.grade(user_input, correct_answer))
+
+ def test_targets_are_draggable_2_manual_parsing(self):
+ user_input = json.dumps([
+ {'up': 'p_l[p][1]'},
+ {'p': 'p_l'},
+ {'up': 'p_l[p][3]'},
+ {'up': 'p_r[p][1]'},
+ {'p': 'p_r'},
+ {'up': 'p_r[p][3]'},
+ {'up_and_down': 's_l[s][1]'},
+ {'s': 's_l'},
+ {'up_and_down': 's_r[s][1]'},
+ {'s': 's_r'}
+ ])
+
+ correct_answer = [
+ {
+ 'draggables': ['p'],
+ 'targets': ['p_l', 'p_r'],
+ 'rule': 'unordered_equal'
+ },
+ {
+ 'draggables': ['s'],
+ 'targets': ['s_l', 's_r'],
+ 'rule': 'unordered_equal'
+ },
+ {
+ 'draggables': ['up_and_down'],
+ 'targets': [
+ 's_l[s][1]', 's_r[s][1]'
+ ],
+ 'rule': 'unordered_equal'
+ },
+ {
+ 'draggables': ['up'],
+ 'targets': [
+ 'p_l[p][1]', 'p_l[p][3]', 'p_r[p][1]', 'p_r[p][3]'
+ ],
+ 'rule': 'unordered_equal'
+ }
+ ]
+ self.assertTrue(draganddrop.grade(user_input, correct_answer))
+
+ def test_targets_are_draggable_3_nested(self):
+ user_input = json.dumps([
+ {'molecule': 'left_side_tagret'},
+ {'molecule': 'right_side_tagret'},
+ {'p': {'p_target': {'molecule': 'left_side_tagret'}}},
+ {'p': {'p_target': {'molecule': 'right_side_tagret'}}},
+ {'s': {'s_target': {'molecule': 'left_side_tagret'}}},
+ {'s': {'s_target': {'molecule': 'right_side_tagret'}}},
+ {'up': {'1': {'p': {'p_target': {'molecule': 'left_side_tagret'}}}}},
+ {'up': {'3': {'p': {'p_target': {'molecule': 'left_side_tagret'}}}}},
+ {'up': {'1': {'p': {'p_target': {'molecule': 'right_side_tagret'}}}}},
+ {'up': {'3': {'p': {'p_target': {'molecule': 'right_side_tagret'}}}}},
+ {'up_and_down': {'1': {'s': {'s_target': {'molecule': 'left_side_tagret'}}}}},
+ {'up_and_down': {'1': {'s': {'s_target': {'molecule': 'right_side_tagret'}}}}}
+ ])
+
+ correct_answer = [
+ {
+ 'draggables': ['molecule'],
+ 'targets': ['left_side_tagret', 'right_side_tagret'],
+ 'rule': 'unordered_equal'
+ },
+ {
+ 'draggables': ['p'],
+ 'targets': [
+ 'left_side_tagret[molecule][p_target]',
+ 'right_side_tagret[molecule][p_target]'
+ ],
+ 'rule': 'unordered_equal'
+ },
+ {
+ 'draggables': ['s'],
+ 'targets': [
+ 'left_side_tagret[molecule][s_target]',
+ 'right_side_tagret[molecule][s_target]'
+ ],
+ 'rule': 'unordered_equal'
+ },
+ {
+ 'draggables': ['up_and_down'],
+ 'targets': [
+ 'left_side_tagret[molecule][s_target][s][1]',
+ 'right_side_tagret[molecule][s_target][s][1]'
+ ],
+ 'rule': 'unordered_equal'
+ },
+ {
+ 'draggables': ['up'],
+ 'targets': [
+ 'left_side_tagret[molecule][p_target][p][1]',
+ 'left_side_tagret[molecule][p_target][p][3]',
+ 'right_side_tagret[molecule][p_target][p][1]',
+ 'right_side_tagret[molecule][p_target][p][3]'
+ ],
+ 'rule': 'unordered_equal'
+ }
+ ]
+ self.assertTrue(draganddrop.grade(user_input, correct_answer))
+
+ def test_targets_are_draggable_4_real_example(self):
+ user_input = json.dumps([
+ {'single_draggable': 's_l'},
+ {'single_draggable': 's_r'},
+ {'single_draggable': 'p_sigma'},
+ {'single_draggable': 'p_sigma*'},
+ {'single_draggable': 's_sigma'},
+ {'single_draggable': 's_sigma*'},
+ {'double_draggable': 'p_pi*'},
+ {'double_draggable': 'p_pi'},
+ {'triple_draggable': 'p_l'},
+ {'triple_draggable': 'p_r'},
+ {'up': {'1': {'triple_draggable': 'p_l'}}},
+ {'up': {'2': {'triple_draggable': 'p_l'}}},
+ {'up': {'2': {'triple_draggable': 'p_r'}}},
+ {'up': {'3': {'triple_draggable': 'p_r'}}},
+ {'up_and_down': {'1': {'single_draggable': 's_l'}}},
+ {'up_and_down': {'1': {'single_draggable': 's_r'}}},
+ {'up_and_down': {'1': {'single_draggable': 's_sigma'}}},
+ {'up_and_down': {'1': {'single_draggable': 's_sigma*'}}},
+ {'up_and_down': {'1': {'double_draggable': 'p_pi'}}},
+ {'up_and_down': {'2': {'double_draggable': 'p_pi'}}}
+ ])
+
+ # 10 targets:
+ # s_l, s_r, p_l, p_r, s_sigma, s_sigma*, p_pi, p_sigma, p_pi*, p_sigma*
+ #
+ # 3 draggable objects, which have targets (internal target ids - 1, 2, 3):
+ # single_draggable, double_draggable, triple_draggable
+ #
+ # 2 draggable objects:
+ # up, up_and_down
+ correct_answer = [
+ {
+ 'draggables': ['triple_draggable'],
+ 'targets': ['p_l', 'p_r'],
+ 'rule': 'unordered_equal'
+ },
+ {
+ 'draggables': ['double_draggable'],
+ 'targets': ['p_pi', 'p_pi*'],
+ 'rule': 'unordered_equal'
+ },
+ {
+ 'draggables': ['single_draggable'],
+ 'targets': ['s_l', 's_r', 's_sigma', 's_sigma*', 'p_sigma', 'p_sigma*'],
+ 'rule': 'unordered_equal'
+ },
+ {
+ 'draggables': ['up'],
+ 'targets': ['p_l[triple_draggable][1]', 'p_l[triple_draggable][2]',
+ 'p_r[triple_draggable][2]', 'p_r[triple_draggable][3]'],
+ 'rule': 'unordered_equal'
+ },
+ {
+ 'draggables': ['up_and_down'],
+ 'targets': ['s_l[single_draggable][1]', 's_r[single_draggable][1]',
+ 's_sigma[single_draggable][1]', 's_sigma*[single_draggable][1]',
+ 'p_pi[double_draggable][1]', 'p_pi[double_draggable][2]'],
+ 'rule': 'unordered_equal'
+ },
+
+ ]
+ self.assertTrue(draganddrop.grade(user_input, correct_answer))
+
def test_targets_true(self):
- user_input = '{"draggables": [{"1": "t1"}, \
- {"name_with_icon": "t2"}]}'
- correct_answer = {'1': 't1', 'name_with_icon': 't2'}
+ user_input = '[{"1": "t1"}, \
+ {"name_with_icon": "t2"}]'
+ correct_answer = {'1': 't1', 'name_with_icon': 't2'}
self.assertTrue(draganddrop.grade(user_input, correct_answer))
def test_expect_no_actions_wrong(self):
@@ -59,71 +292,63 @@ class Test_DragAndDrop_Grade(unittest.TestCase):
def test_targets_false(self):
- user_input = '{"draggables": [{"1": "t1"}, \
- {"name_with_icon": "t2"}]}'
- correct_answer = {'1': 't3', 'name_with_icon': 't2'}
+ user_input = '[{"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',
+ user_input = '[{"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',
+ user_input = '[{"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]}]}'
+ user_input = '[{"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"}]}'
+ user_input = '[{"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]}]}'
+ user_input = '[{"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]}]}'
+ user_input = '[{"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]}]}'
+ user_input = '[{"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]}]}'
+ user_input = '[{"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]}]}'
+ user_input = '[{"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"}]}'
+ user_input = '[{"1": "t1"}, {"name_with_icon": "t2"}]'
correct_answer = {'3': 't3', 'name_with_icon': 't2'}
self.assertFalse(draganddrop.grade(user_input, correct_answer))
@@ -131,20 +356,20 @@ class Test_DragAndDrop_Grade(unittest.TestCase):
"""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]}]}'
+ user_input = '[{"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"}, \
+ user_input = '[{"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"}]}'
+ {"12":"p_sigma_name"},{"14":"p_sigma_star_name"}]'
correct_answer = [{
'draggables': ['1', '2', '3', '4', '5', '6'],
@@ -178,12 +403,12 @@ class Test_DragAndDrop_Grade(unittest.TestCase):
def test_lcao_extra_element_incorrect(self):
"""Describe carbon molecule in LCAO-MO"""
- user_input = '{"draggables":[{"1":"s_left"}, \
+ user_input = '[{"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"}]}'
+ {"12":"p_sigma_name"},{"14":"p_sigma_star_name"}]'
correct_answer = [{
'draggables': ['1', '2', '3', '4', '5', '6'],
@@ -217,9 +442,9 @@ class Test_DragAndDrop_Grade(unittest.TestCase):
def test_reuse_draggable_no_mupliples(self):
"""Test reusable draggables (no mupltiple draggables per target)"""
- user_input = '{"draggables":[{"1":"target1"}, \
+ user_input = '[{"1":"target1"}, \
{"2":"target2"},{"1":"target3"},{"2":"target4"},{"2":"target5"}, \
- {"3":"target6"}]}'
+ {"3":"target6"}]'
correct_answer = [
{
'draggables': ['1'],
@@ -240,9 +465,9 @@ class Test_DragAndDrop_Grade(unittest.TestCase):
def test_reuse_draggable_with_mupliples(self):
"""Test reusable draggables with mupltiple draggables per target"""
- user_input = '{"draggables":[{"1":"target1"}, \
+ user_input = '[{"1":"target1"}, \
{"2":"target2"},{"1":"target1"},{"2":"target4"},{"2":"target4"}, \
- {"3":"target6"}]}'
+ {"3":"target6"}]'
correct_answer = [
{
'draggables': ['1'],
@@ -263,10 +488,10 @@ class Test_DragAndDrop_Grade(unittest.TestCase):
def test_reuse_many_draggable_with_mupliples(self):
"""Test reusable draggables with mupltiple draggables per target"""
- user_input = '{"draggables":[{"1":"target1"}, \
+ user_input = '[{"1":"target1"}, \
{"2":"target2"},{"1":"target1"},{"2":"target4"},{"2":"target4"}, \
{"3":"target6"}, {"4": "target3"}, {"5": "target4"}, \
- {"5": "target5"}, {"6": "target2"}]}'
+ {"5": "target5"}, {"6": "target2"}]'
correct_answer = [
{
'draggables': ['1', '4'],
@@ -292,12 +517,12 @@ class Test_DragAndDrop_Grade(unittest.TestCase):
def test_reuse_many_draggable_with_mupliples_wrong(self):
"""Test reusable draggables with mupltiple draggables per target"""
- user_input = '{"draggables":[{"1":"target1"}, \
+ user_input = '[{"1":"target1"}, \
{"2":"target2"},{"1":"target1"}, \
{"2":"target3"}, \
{"2":"target4"}, \
{"3":"target6"}, {"4": "target3"}, {"5": "target4"}, \
- {"5": "target5"}, {"6": "target2"}]}'
+ {"5": "target5"}, {"6": "target2"}]'
correct_answer = [
{
'draggables': ['1', '4'],
@@ -323,10 +548,10 @@ class Test_DragAndDrop_Grade(unittest.TestCase):
def test_label_10_targets_with_a_b_c_false(self):
"""Test reusable draggables (no mupltiple draggables per target)"""
- user_input = '{"draggables":[{"a":"target1"}, \
+ user_input = '[{"a":"target1"}, \
{"b":"target2"},{"c":"target3"},{"a":"target4"},{"b":"target5"}, \
{"c":"target6"}, {"a":"target7"},{"b":"target8"},{"c":"target9"}, \
- {"a":"target1"}]}'
+ {"a":"target1"}]'
correct_answer = [
{
'draggables': ['a'],
@@ -347,10 +572,10 @@ class Test_DragAndDrop_Grade(unittest.TestCase):
def test_label_10_targets_with_a_b_c_(self):
"""Test reusable draggables (no mupltiple draggables per target)"""
- user_input = '{"draggables":[{"a":"target1"}, \
+ user_input = '[{"a":"target1"}, \
{"b":"target2"},{"c":"target3"},{"a":"target4"},{"b":"target5"}, \
{"c":"target6"}, {"a":"target7"},{"b":"target8"},{"c":"target9"}, \
- {"a":"target10"}]}'
+ {"a":"target10"}]'
correct_answer = [
{
'draggables': ['a'],
@@ -371,10 +596,10 @@ class Test_DragAndDrop_Grade(unittest.TestCase):
def test_label_10_targets_with_a_b_c_multiple(self):
"""Test reusable draggables (mupltiple draggables per target)"""
- user_input = '{"draggables":[{"a":"target1"}, \
+ user_input = '[{"a":"target1"}, \
{"b":"target2"},{"c":"target3"},{"b":"target5"}, \
{"c":"target6"}, {"a":"target7"},{"b":"target8"},{"c":"target9"}, \
- {"a":"target1"}]}'
+ {"a":"target1"}]'
correct_answer = [
{
'draggables': ['a', 'a', 'a'],
@@ -395,10 +620,10 @@ class Test_DragAndDrop_Grade(unittest.TestCase):
def test_label_10_targets_with_a_b_c_multiple_false(self):
"""Test reusable draggables (mupltiple draggables per target)"""
- user_input = '{"draggables":[{"a":"target1"}, \
+ user_input = '[{"a":"target1"}, \
{"b":"target2"},{"c":"target3"},{"a":"target4"},{"b":"target5"}, \
{"c":"target6"}, {"a":"target7"},{"b":"target8"},{"c":"target9"}, \
- {"a":"target1"}]}'
+ {"a":"target1"}]'
correct_answer = [
{
'draggables': ['a', 'a', 'a'],
@@ -419,10 +644,10 @@ class Test_DragAndDrop_Grade(unittest.TestCase):
def test_label_10_targets_with_a_b_c_reused(self):
"""Test a b c in 10 labels reused"""
- user_input = '{"draggables":[{"a":"target1"}, \
+ user_input = '[{"a":"target1"}, \
{"b":"target2"},{"c":"target3"},{"b":"target5"}, \
{"c":"target6"}, {"b":"target8"},{"c":"target9"}, \
- {"a":"target10"}]}'
+ {"a":"target10"}]'
correct_answer = [
{
'draggables': ['a', 'a'],
@@ -443,10 +668,10 @@ class Test_DragAndDrop_Grade(unittest.TestCase):
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"}, \
+ user_input = '[{"a":"target1"}, \
{"b":"target2"},{"c":"target3"},{"b":"target5"}, {"a":"target8"},\
{"c":"target6"}, {"b":"target8"},{"c":"target9"}, \
- {"a":"target10"}]}'
+ {"a":"target10"}]'
correct_answer = [
{
'draggables': ['a', 'a'],
@@ -467,9 +692,9 @@ class Test_DragAndDrop_Grade(unittest.TestCase):
def test_mixed_reuse_and_not_reuse(self):
"""Test reusable draggables """
- user_input = '{"draggables":[{"a":"target1"}, \
+ user_input = '[{"a":"target1"}, \
{"b":"target2"},{"c":"target3"}, {"a":"target4"},\
- {"a":"target5"}]}'
+ {"a":"target5"}]'
correct_answer = [
{
'draggables': ['a', 'b'],
@@ -485,8 +710,8 @@ class Test_DragAndDrop_Grade(unittest.TestCase):
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"}]}'
+ user_input = '[{"a":"target1"}, \
+ {"b":"target2"},{"c":"target3"}, {"a":"target4"}]'
correct_answer = [
{
'draggables': ['a', 'a', 'b'],
@@ -502,8 +727,8 @@ class Test_DragAndDrop_Grade(unittest.TestCase):
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"}]}'
+ user_input = '[{"a":"target1"}, \
+ {"b":"target2"},{"c":"target3"}, {"a":"target4"}, {"a":"target10"}]'
correct_answer = [
{
'draggables': ['a', 'a', 'b'],
@@ -518,9 +743,9 @@ class Test_DragAndDrop_Grade(unittest.TestCase):
self.assertFalse(draganddrop.grade(user_input, correct_answer))
def test_alternative_correct_answer(self):
- user_input = '{"draggables":[{"name_with_icon":"t1"},\
+ user_input = '[{"name_with_icon":"t1"},\
{"name_with_icon":"t1"},{"name_with_icon":"t1"},{"name4":"t1"}, \
- {"name4":"t1"}]}'
+ {"name4":"t1"}]'
correct_answer = [
{'draggables': ['name4'], 'targets': ['t1', 't1'], 'rule': 'exact'},
{'draggables': ['name_with_icon'], 'targets': ['t1', 't1', 't1'],
@@ -533,14 +758,13 @@ 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]}]}'
+ user_input = '[{"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]]}}
+ correct_groups = [['1'], ['name_with_icon']]
+ correct_positions = [{'exact': [[[40, 10], 29]]}, {'exact': [[20, 20]]}]
+ user_groups = [['1'], ['name_with_icon']]
+ user_positions = [{'user': [[10, 10]]}, {'user': [[20, 20]]}]
self.assertEqual(correct_groups, dnd.correct_groups)
self.assertEqual(correct_positions, dnd.correct_positions)
@@ -551,49 +775,49 @@ class Test_DragAndDrop_Populate(unittest.TestCase):
class Test_DraAndDrop_Compare_Positions(unittest.TestCase):
def test_1(self):
- dnd = draganddrop.DragAndDrop({'1': 't1'}, '{"draggables": [{"1": "t1"}]}')
+ dnd = draganddrop.DragAndDrop({'1': 't1'}, '[{"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"}]}')
+ dnd = draganddrop.DragAndDrop({'1': 't1'}, '[{"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"}]}')
+ dnd = draganddrop.DragAndDrop({'1': 't1'}, '[{"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"}]}')
+ dnd = draganddrop.DragAndDrop({'1': 't1'}, '[{"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"}]}')
+ dnd = draganddrop.DragAndDrop({'1': 't1'}, '[{"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"}]}')
+ dnd = draganddrop.DragAndDrop({'1': 't1'}, '[{"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"}]}')
+ dnd = draganddrop.DragAndDrop({'1': 't1'}, '[{"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"}]}')
+ dnd = draganddrop.DragAndDrop({'1': 't1'}, '[{"1": "t1"}]')
self.assertFalse(dnd.compare_positions(correct=["a", "b", "b"],
user=["a", "c", "b"],
flag='anyof'))
diff --git a/common/static/js/capa/drag_and_drop/base_image.js b/common/static/js/capa/drag_and_drop/base_image.js
index da875c4329..ad3da20e94 100644
--- a/common/static/js/capa/drag_and_drop/base_image.js
+++ b/common/static/js/capa/drag_and_drop/base_image.js
@@ -1,9 +1,4 @@
-// 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;
@@ -50,10 +45,5 @@ define(['logme'], function (logme) {
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)
+}); // End-of: define(['logme'], function (logme) {
+}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); // End-of: (function (requirejs, require, define) {
diff --git a/common/static/js/capa/drag_and_drop/config_parser.js b/common/static/js/capa/drag_and_drop/config_parser.js
index e6c1e4d3c1..d84a8da913 100644
--- a/common/static/js/capa/drag_and_drop/config_parser.js
+++ b/common/static/js/capa/drag_and_drop/config_parser.js
@@ -1,9 +1,4 @@
-// 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;
@@ -16,7 +11,7 @@ define(['logme'], function (logme) {
'targetOutline': true,
'labelBgColor': '#d6d6d6',
'individualTargets': null, // Depends on 'targets'.
- 'errors': 0 // Number of errors found while parsing config.
+ 'foundErrors': false // Whether or not we find errors while processing the config.
};
getDraggables(state, config);
@@ -28,7 +23,7 @@ define(['logme'], function (logme) {
setIndividualTargets(state);
- if (state.config.errors !== 0) {
+ if (state.config.foundErrors !== false) {
return false;
}
@@ -38,35 +33,34 @@ define(['logme'], function (logme) {
function getDraggables(state, config) {
if (config.hasOwnProperty('draggables') === false) {
logme('ERROR: "config" does not have a property "draggables".');
- state.config.errors += 1;
+ state.config.foundErrors = true;
} 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;
+ config.draggables.every(function (draggable) {
+ if (processDraggable(state, draggable) !== true) {
+ state.config.foundErrors = true;
+
+ // Exit immediately from .every() call.
+ return false;
}
- }(0));
- } else if ($.isPlainObject(config.draggables) === true) {
- if (processDraggable(state, config.draggables) !== true) {
- state.config.errors += 1;
- }
+
+ // Continue to next .every() call.
+ return true;
+ });
} else {
logme('ERROR: The type of config.draggables is no supported.');
- state.config.errors += 1;
+ state.config.foundErrors = true;
}
}
function getBaseImage(state, config) {
if (config.hasOwnProperty('base_image') === false) {
logme('ERROR: "config" does not have a property "base_image".');
- state.config.errors += 1;
+ state.config.foundErrors = true;
} 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;
+ state.config.foundErrors = true;
}
}
@@ -77,28 +71,27 @@ define(['logme'], function (logme) {
// 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;
+ config.targets.every(function (target) {
+ if (processTarget(state, target) !== true) {
+ state.config.foundErrors = true;
+
+ // Exit immediately from .every() call.
+ return false;
}
- }(0));
- } else if ($.isPlainObject(config.targets) === true) {
- if (processTarget(state, config.targets) !== true) {
- state.config.errors += 1;
- }
+
+ // Continue to next .every() call.
+ return true;
+ });
} else {
logme('ERROR: Property config.targets is not of a supported type.');
- state.config.errors += 1;
+ state.config.foundErrors = true;
}
}
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;
+ state.config.foundErrors = true;
} else if (typeof config.one_per_target === 'string') {
if (config.one_per_target.toLowerCase() === 'true') {
state.config.onePerTarget = true;
@@ -106,42 +99,45 @@ define(['logme'], function (logme) {
state.config.onePerTarget = false;
} else {
logme('ERROR: Property config.one_per_target can either be "true", or "false".');
- state.config.errors += 1;
+ state.config.foundErrors = true;
}
} else {
logme('ERROR: Property config.one_per_target is not of a supported type.');
- state.config.errors += 1;
+ state.config.foundErrors = true;
}
}
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;
+ // 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.
+
+ if (config.hasOwnProperty('target_outline') === true) {
+ 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.foundErrors = true;
+ }
} else {
- logme('ERROR: Property config.target_outline can either be "true", or "false".');
- state.config.errors += 1;
+ logme('ERROR: Property config.target_outline is not of a supported type.');
+ state.config.foundErrors = true;
}
- } 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;
+ // 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.
+
+ if (config.hasOwnProperty('label_bg_color') === true) {
+ 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.');
+ }
}
}
@@ -159,17 +155,36 @@ define(['logme'], function (logme) {
(attrIsString(obj, 'icon') === false) ||
(attrIsString(obj, 'label') === false) ||
- (attrIsBoolean(obj, 'can_reuse', false) === false)
+ (attrIsBoolean(obj, 'can_reuse', false) === false) ||
+
+ (obj.hasOwnProperty('target_fields') === false)
) {
return false;
}
+ // Check that all targets in the 'target_fields' property are proper target objects.
+ // We will be testing the return value from .every() call (it can be 'true' or 'false').
+ if (obj.target_fields.every(
+ function (targetObj) {
+ return processTarget(state, targetObj, false);
+ }
+ ) === false) {
+ return false;
+ }
+
state.config.draggables.push(obj);
return true;
}
- function processTarget(state, obj) {
+ // We need 'pushToState' parameter in order to simply test an object for the fact that it is a
+ // proper target (without pushing it to the 'state' object). When
+ //
+ // pushToState === false
+ //
+ // the object being tested is not going to be pushed to 'state'. The function will onyl return
+ // 'true' or 'false.
+ function processTarget(state, obj, pushToState) {
if (
(attrIsString(obj, 'id') === false) ||
@@ -182,7 +197,9 @@ define(['logme'], function (logme) {
return false;
}
- state.config.targets.push(obj);
+ if (pushToState !== false) {
+ state.config.targets.push(obj);
+ }
return true;
}
@@ -250,10 +267,5 @@ define(['logme'], function (logme) {
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)
+}); // End-of: define(['logme'], function (logme) {
+}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); // End-of: (function (requirejs, require, define) {
diff --git a/common/static/js/capa/drag_and_drop/container.js b/common/static/js/capa/drag_and_drop/container.js
index e5a7de447f..0c627f12d3 100644
--- a/common/static/js/capa/drag_and_drop/container.js
+++ b/common/static/js/capa/drag_and_drop/container.js
@@ -1,9 +1,4 @@
-// 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;
@@ -21,10 +16,5 @@ define(['logme'], function (logme) {
$('#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)
+}); // End-of: define(['logme'], function (logme) {
+}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); // End-of: (function (requirejs, require, define) {
diff --git a/common/static/js/capa/drag_and_drop/draggable_events.js b/common/static/js/capa/drag_and_drop/draggable_events.js
new file mode 100644
index 0000000000..73d03b3cfd
--- /dev/null
+++ b/common/static/js/capa/drag_and_drop/draggable_events.js
@@ -0,0 +1,131 @@
+(function (requirejs, require, define) {
+define(['logme'], function (logme) {
+return {
+ 'attachMouseEventsTo': function (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);
+ });
+ },
+
+ 'mouseDown': function (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.numDraggablesOnMe > 0) {
+ return;
+ }
+
+ // 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();
+ }
+
+ if (this.iconImgEl !== null) {
+ this.iconImgEl.css({
+ 'width': this.iconWidth,
+ 'height': this.iconHeight
+ });
+ }
+ 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;
+ }
+ },
+
+ 'mouseUp': function () {
+ if (this.mousePressed === true) {
+ this.state.currentMovingDraggable = null;
+
+ this.checkLandingElement();
+ }
+ },
+
+ 'mouseMove': function (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
+ });
+ }
+ }
+ }
+}; // End-of: return {
+}); // End-of: define(['logme'], function (logme) {
+}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); // End-of: (function (requirejs, require, define) {
diff --git a/common/static/js/capa/drag_and_drop/draggable_logic.js b/common/static/js/capa/drag_and_drop/draggable_logic.js
new file mode 100644
index 0000000000..91c70ccbaf
--- /dev/null
+++ b/common/static/js/capa/drag_and_drop/draggable_logic.js
@@ -0,0 +1,379 @@
+(function (requirejs, require, define) {
+define(['logme', 'update_input', 'targets'], function (logme, updateInput, Targets) {
+return {
+ 'moveDraggableTo': function (moveType, target, funcCallback) {
+ var self, offset;
+
+ if (this.hasLoaded === false) {
+ self = this;
+
+ setTimeout(function () {
+ self.moveDraggableTo(moveType, target, funcCallback);
+ }, 50);
+
+ return;
+ }
+
+ if ((this.isReusable === true) && (this.isOriginal === true)) {
+ this.makeDraggableCopy(function (draggableCopy) {
+ draggableCopy.moveDraggableTo(moveType, target, funcCallback);
+ });
+
+ return;
+ }
+
+ offset = 0;
+ if (this.state.config.targetOutline === true) {
+ offset = 1;
+ }
+
+ this.inContainer = false;
+
+ if (this.isOriginal === true) {
+ this.containerEl.hide();
+ this.iconEl.detach();
+ }
+
+ if (this.iconImgEl !== null) {
+ this.iconImgEl.css({
+ 'width': this.iconWidth,
+ 'height': this.iconHeight
+ });
+ }
+
+ 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();
+
+ Targets.initializeTargetField(this);
+
+ if (this.isOriginal === true) {
+ this.state.numDraggablesInSlider -= 1;
+ this.state.updateArrowOpacity();
+ }
+
+ if ($.isFunction(funcCallback) === true) {
+ funcCallback();
+ }
+ },
+
+ // 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.
+ 'checkLandingElement': function () {
+ var positionIE;
+
+ this.mousePressed = false;
+ positionIE = this.iconEl.position();
+
+ if (this.state.config.individualTargets === true) {
+ if (this.checkIfOnTarget(positionIE) === true) {
+ this.correctZIndexes();
+
+ Targets.initializeTargetField(this);
+ } 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;
+
+ Targets.initializeTargetField(this);
+ }
+ }
+
+ 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()
+ 'checkIfOnTarget': function (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;
+ }
+
+ // If the target is on a draggable (from target field), we must make sure that
+ // this draggable is not the same as "this" one.
+ if ((target.type === 'on_drag') && (target.draggableObj.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.uniqueId !== target.uniqueId)) {
+ 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;
+ },
+
+ 'snapToTarget': function (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.
+ 'correctZIndexes': function () {
+ 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.
+ 'moveBackToSlider': function () {
+ var c1;
+
+ Targets.destroyTargetField(this);
+
+ 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.length; 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();
+ if (this.iconImgEl !== null) {
+ this.iconImgEl.css({
+ 'width': this.iconWidthSmall,
+ 'height': this.iconHeightSmall
+ });
+ }
+ 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: return {
+}); // End-of: define(['logme', 'update_input', 'targets'], function (logme, updateInput, Targets) {
+}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); // End-of: (function (requirejs, require, define) {
diff --git a/common/static/js/capa/drag_and_drop/draggables.js b/common/static/js/capa/drag_and_drop/draggables.js
index a867cf73fe..5c4fc87c9d 100644
--- a/common/static/js/capa/drag_and_drop/draggables.js
+++ b/common/static/js/capa/drag_and_drop/draggables.js
@@ -1,21 +1,15 @@
-// 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) {
+define(['logme', 'draggable_events', 'draggable_logic'], function (logme, draggableEvents, draggableLogic) {
return {
'init': init
};
function init(state) {
- (function (c1) {
- while (c1 < state.config.draggables.length) {
- processDraggable(state, state.config.draggables[c1]);
- c1 += 1
- }
- }(0));
+ state.config.draggables.every(function (draggable) {
+ processDraggable(state, draggable);
+
+ return true;
+ });
}
function makeDraggableCopy(callbackFunc) {
@@ -34,13 +28,18 @@ define(['logme', 'update_input'], function (logme, updateInput) {
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.iconImgEl = null; // Will be created.
draggableObj.labelEl = null; // Will be created.
+ draggableObj.targetField = []; // Will be populated.
// Create DOM elements and attach events.
if (draggableObj.originalConfigObj.icon.length > 0) {
- draggableObj.iconEl = $('
');
- draggableObj.iconEl.attr('src', draggableObj.originalConfigObj.icon);
- draggableObj.iconEl.load(function () {
+
+ draggableObj.iconEl = $('
');
+ draggableObj.iconImgEl = $('
');
+ draggableObj.iconImgEl.attr('src', draggableObj.originalConfigObj.icon);
+ draggableObj.iconImgEl.load(function () {
+
draggableObj.iconEl.css({
'position': 'absolute',
'width': draggableObj.iconWidthSmall,
@@ -48,6 +47,14 @@ define(['logme', 'update_input'], function (logme, updateInput) {
'left': 50 - draggableObj.iconWidthSmall * 0.5,
'top': ((draggableObj.originalConfigObj.label.length > 0) ? 5 : 50 - draggableObj.iconHeightSmall * 0.5)
});
+ draggableObj.iconImgEl.css({
+ 'position': 'absolute',
+ 'width': draggableObj.iconWidthSmall,
+ 'height': draggableObj.iconHeightSmall,
+ 'left': 0,
+ 'top': 0
+ });
+ draggableObj.iconImgEl.appendTo(draggableObj.iconEl);
if (draggableObj.originalConfigObj.label.length > 0) {
draggableObj.labelEl = $(
@@ -71,7 +78,7 @@ define(['logme', 'update_input'], function (logme, updateInput) {
draggableObj.attachMouseEventsTo('iconEl');
- draggableObj.stateDraggablesIndex = draggableObj.state.draggables.push(draggableObj);
+ draggableObj.stateDraggablesIndex = draggableObj.state.draggables.push(draggableObj) - 1;
setTimeout(function () {
callbackFunc(draggableObj);
@@ -99,7 +106,7 @@ define(['logme', 'update_input'], function (logme, updateInput) {
draggableObj.attachMouseEventsTo('iconEl');
- draggableObj.stateDraggablesIndex = draggableObj.state.draggables.push(draggableObj);
+ draggableObj.stateDraggablesIndex = draggableObj.state.draggables.push(draggableObj) - 1;
setTimeout(function () {
callbackFunc(draggableObj);
@@ -110,115 +117,6 @@ define(['logme', 'update_input'], function (logme, updateInput) {
}
}
- 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;
@@ -234,6 +132,7 @@ define(['logme', 'update_input'], function (logme, updateInput) {
'zIndex': 1,
'containerEl': null,
'iconEl': null,
+ 'iconImgEl': null,
'iconElBGColor': null,
'iconElPadding': null,
'iconElBorder': null,
@@ -251,17 +150,23 @@ define(['logme', 'update_input'], function (logme, updateInput) {
'onTargetIndex': null,
'state': state,
- 'mouseDown': mouseDown,
- 'mouseUp': mouseUp,
- 'mouseMove': mouseMove,
- 'checkLandingElement': checkLandingElement,
- 'checkIfOnTarget': checkIfOnTarget,
- 'snapToTarget': snapToTarget,
- 'correctZIndexes': correctZIndexes,
- 'moveBackToSlider': moveBackToSlider,
- 'moveDraggableTo': moveDraggableTo,
+ 'mouseDown': draggableEvents.mouseDown,
+ 'mouseUp': draggableEvents.mouseUp,
+ 'mouseMove': draggableEvents.mouseMove,
+
+ 'checkLandingElement': draggableLogic.checkLandingElement,
+ 'checkIfOnTarget': draggableLogic.checkIfOnTarget,
+ 'snapToTarget': draggableLogic.snapToTarget,
+ 'correctZIndexes': draggableLogic.correctZIndexes,
+ 'moveBackToSlider': draggableLogic.moveBackToSlider,
+ 'moveDraggableTo': draggableLogic.moveDraggableTo,
+
'makeDraggableCopy': makeDraggableCopy,
- 'attachMouseEventsTo': attachMouseEventsTo
+
+ 'attachMouseEventsTo': draggableEvents.attachMouseEventsTo,
+
+ 'targetField': [],
+ 'numDraggablesOnMe': 0
};
draggableObj.containerEl = $(
@@ -288,9 +193,11 @@ define(['logme', 'update_input'], function (logme, updateInput) {
draggableObj.iconElBorder = 'none';
draggableObj.iconElLeftOffset = 0;
- draggableObj.iconEl = $('
');
- draggableObj.iconEl.attr('src', obj.icon);
- draggableObj.iconEl.load(function () {
+ draggableObj.iconEl = $('');
+
+ draggableObj.iconImgEl = $('
');
+ draggableObj.iconImgEl.attr('src', obj.icon);
+ draggableObj.iconImgEl.load(function () {
draggableObj.iconWidth = this.width;
draggableObj.iconHeight = this.height;
@@ -309,6 +216,14 @@ define(['logme', 'update_input'], function (logme, updateInput) {
'left': 50 - draggableObj.iconWidthSmall * 0.5,
'top': ((obj.label.length > 0) ? 5 : 50 - draggableObj.iconHeightSmall * 0.5)
});
+ draggableObj.iconImgEl.css({
+ 'position': 'absolute',
+ 'width': draggableObj.iconWidthSmall,
+ 'height': draggableObj.iconHeightSmall,
+ 'left': 0,
+ 'top': 0
+ });
+ draggableObj.iconImgEl.appendTo(draggableObj.iconEl);
draggableObj.iconEl.appendTo(draggableObj.containerEl);
if (obj.label.length > 0) {
@@ -384,357 +299,5 @@ define(['logme', 'update_input'], function (logme, updateInput) {
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)
+}); // End-of: define(['logme', 'draggable_events', 'draggable_logic'], function (logme, draggableEvents, draggableLogic) {
+}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); // End-of: (function (requirejs, require, define) {
diff --git a/common/static/js/capa/drag_and_drop/logme.js b/common/static/js/capa/drag_and_drop/logme.js
index 21f73bf2a5..5a6c5385a6 100644
--- a/common/static/js/capa/drag_and_drop/logme.js
+++ b/common/static/js/capa/drag_and_drop/logme.js
@@ -1,9 +1,4 @@
-// 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;
@@ -27,10 +22,5 @@ define([], function () {
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)
+}); // End-of: define([], function () {
+}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); // End-of: (function (requirejs, require, define) {
diff --git a/common/static/js/capa/drag_and_drop/main.js b/common/static/js/capa/drag_and_drop/main.js
index 89cf08001d..92c71e008b 100644
--- a/common/static/js/capa/drag_and_drop/main.js
+++ b/common/static/js/capa/drag_and_drop/main.js
@@ -1,15 +1,41 @@
-// 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() {
+
+ // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/every
+ //
+ // Array.prototype.every is a recent addition to the ECMA-262 standard; as such it may not be present in
+ // other implementations of the standard.
+ if (!Array.prototype.every) {
+ Array.prototype.every = function(fun /*, thisp */) {
+ var thisp, t, len, i;
+
+ if (this == null) {
+ throw new TypeError();
+ }
+
+ t = Object(this);
+ len = t.length >>> 0;
+ if (typeof fun != 'function') {
+ throw new TypeError();
+ }
+
+ thisp = arguments[1];
+
+ for (i = 0; i < len; i++) {
+ if (i in t && !fun.call(thisp, t[i], i, t)) {
+ return false;
+ }
+ }
+
+ return true;
+ };
+ }
+
$('.drag_and_drop_problem_div').each(processProblem);
}
@@ -59,7 +85,7 @@ define(
return;
}
- Targets(state);
+ Targets.initializeBaseTargets(state);
Scroller(state);
Draggables.init(state);
@@ -72,10 +98,5 @@ define(
}
}());
}
-});
-
-// 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)
+}); // End-of: define(
+}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); // End-of: (function (requirejs, require, define) {
diff --git a/common/static/js/capa/drag_and_drop/scroller.js b/common/static/js/capa/drag_and_drop/scroller.js
index c1fe867006..7aa1ff4108 100644
--- a/common/static/js/capa/drag_and_drop/scroller.js
+++ b/common/static/js/capa/drag_and_drop/scroller.js
@@ -1,9 +1,4 @@
-// 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;
@@ -206,10 +201,5 @@ define(['logme'], function (logme) {
}
}
} // 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)
+}); // End-of: define(['logme'], function (logme) {
+}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); // End-of: (function (requirejs, require, define) {
diff --git a/common/static/js/capa/drag_and_drop/state.js b/common/static/js/capa/drag_and_drop/state.js
index 4565acd842..0f83aa7092 100644
--- a/common/static/js/capa/drag_and_drop/state.js
+++ b/common/static/js/capa/drag_and_drop/state.js
@@ -1,9 +1,4 @@
-// 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;
@@ -96,10 +91,5 @@ define([], function () {
}
}
}
-});
-
-// 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)
+}); // End-of: define([], function () {
+}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); // End-of: (function (requirejs, require, define) {
diff --git a/common/static/js/capa/drag_and_drop/targets.js b/common/static/js/capa/drag_and_drop/targets.js
index e56020aac6..3a8e2c4b2d 100644
--- a/common/static/js/capa/drag_and_drop/targets.js
+++ b/common/static/js/capa/drag_and_drop/targets.js
@@ -1,13 +1,12 @@
-// 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;
+ return {
+ 'initializeBaseTargets': initializeBaseTargets,
+ 'initializeTargetField': initializeTargetField,
+ 'destroyTargetField': destroyTargetField
+ };
- function Targets(state) {
+ function initializeBaseTargets(state) {
(function (c1) {
while (c1 < state.config.targets.length) {
processTarget(state, state.config.targets[c1]);
@@ -17,7 +16,58 @@ define(['logme'], function (logme) {
}(0));
}
- function processTarget(state, obj) {
+ function initializeTargetField(draggableObj) {
+ var iconElOffset;
+
+ if (draggableObj.targetField.length === 0) {
+ draggableObj.originalConfigObj.target_fields.every(function (targetObj) {
+ processTarget(draggableObj.state, targetObj, true, draggableObj);
+
+ return true;
+ });
+ } else {
+ iconElOffset = draggableObj.iconEl.position();
+
+ draggableObj.targetField.every(function (targetObj) {
+ targetObj.offset.top = iconElOffset.top + targetObj.y;
+ targetObj.offset.left = iconElOffset.left + targetObj.x;
+
+ return true;
+ });
+ }
+ }
+
+ function destroyTargetField(draggableObj) {
+ var indexOffset, lowestRemovedIndex;
+
+ indexOffset = 0;
+ lowestRemovedIndex = draggableObj.state.targets.length + 1;
+
+ draggableObj.targetField.every(function (target) {
+ target.el.remove();
+
+ if (lowestRemovedIndex > target.indexInStateArray) {
+ lowestRemovedIndex = target.indexInStateArray;
+ }
+
+ draggableObj.state.targets.splice(target.indexInStateArray - indexOffset, 1);
+ indexOffset += 1;
+
+ return true;
+ });
+
+ draggableObj.state.targets.every(function (target) {
+ if (target.indexInStateArray > lowestRemovedIndex) {
+ target.indexInStateArray -= indexOffset;
+ }
+
+ return true;
+ });
+
+ draggableObj.targetField = [];
+ }
+
+ function processTarget(state, obj, fromTargetField, draggableObj) {
var targetEl, borderCss, numTextEl, targetObj;
borderCss = '';
@@ -38,7 +88,13 @@ define(['logme'], function (logme) {
'" ' +
'>'
);
- targetEl.appendTo(state.baseImageEl.parent());
+
+ if (fromTargetField === true) {
+ targetEl.appendTo(draggableObj.iconEl);
+ } else {
+ targetEl.appendTo(state.baseImageEl.parent());
+ }
+
targetEl.mousedown(function (event) {
event.preventDefault();
});
@@ -68,8 +124,13 @@ define(['logme'], function (logme) {
}
targetObj = {
+ 'uniqueId': state.getUniqueId(),
+
'id': obj.id,
+ 'x': obj.x,
+ 'y': obj.y,
+
'w': obj.w,
'h': obj.h,
@@ -86,9 +147,21 @@ define(['logme'], function (logme) {
'updateNumTextEl': updateNumTextEl,
'removeDraggable': removeDraggable,
- 'addDraggable': addDraggable
+ 'addDraggable': addDraggable,
+
+ 'type': 'base',
+ 'draggableObj': null
};
+ if (fromTargetField === true) {
+ targetObj.offset = draggableObj.iconEl.position();
+ targetObj.offset.top += obj.y;
+ targetObj.offset.left += obj.x;
+
+ targetObj.type = 'on_drag';
+ targetObj.draggableObj = draggableObj;
+ }
+
if (state.config.onePerTarget === false) {
numTextEl.appendTo(state.baseImageEl.parent());
numTextEl.mousedown(function (event) {
@@ -99,7 +172,11 @@ define(['logme'], function (logme) {
});
}
- state.targets.push(targetObj);
+ targetObj.indexInStateArray = state.targets.push(targetObj) - 1;
+
+ if (fromTargetField === true) {
+ draggableObj.targetField.push(targetObj);
+ }
}
function removeDraggable(draggable) {
@@ -121,6 +198,10 @@ define(['logme'], function (logme) {
draggable.onTarget = null;
draggable.onTargetIndex = null;
+ if (this.type === 'on_drag') {
+ this.draggableObj.numDraggablesOnMe -= 1;
+ }
+
this.updateNumTextEl();
}
@@ -128,6 +209,10 @@ define(['logme'], function (logme) {
draggable.onTarget = this;
draggable.onTargetIndex = this.draggableList.push(draggable) - 1;
+ if (this.type === 'on_drag') {
+ this.draggableObj.numDraggablesOnMe += 1;
+ }
+
this.updateNumTextEl();
}
@@ -183,10 +268,5 @@ define(['logme'], function (logme) {
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)
+}); // End-of: define(['logme'], function (logme) {
+}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); // End-of: (function (requirejs, require, define) {
diff --git a/common/static/js/capa/drag_and_drop/update_input.js b/common/static/js/capa/drag_and_drop/update_input.js
index 04715a3ecf..804b0bed97 100644
--- a/common/static/js/capa/drag_and_drop/update_input.js
+++ b/common/static/js/capa/drag_and_drop/update_input.js
@@ -1,9 +1,4 @@
-// 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,
@@ -37,7 +32,12 @@ define(['logme'], function (logme) {
(function (c2) {
while (c2 < state.targets[c1].draggableList.length) {
tempObj = {};
- tempObj[state.targets[c1].draggableList[c2].id] = state.targets[c1].id;
+
+ if (state.targets[c1].type === 'base') {
+ tempObj[state.targets[c1].draggableList[c2].id] = state.targets[c1].id;
+ } else {
+ addTargetRecursively(tempObj, state.targets[c1].draggableList[c2], state.targets[c1]);
+ }
draggables.push(tempObj);
tempObj = null;
@@ -50,7 +50,18 @@ define(['logme'], function (logme) {
}(0));
}
- $('#input_' + state.problemId).val(JSON.stringify({'draggables': draggables}));
+ $('#input_' + state.problemId).val(JSON.stringify(draggables));
+ }
+
+ function addTargetRecursively(tempObj, draggable, target) {
+ if (target.type === 'base') {
+ tempObj[draggable.id] = target.id;
+ } else {
+ tempObj[draggable.id] = {};
+ tempObj[draggable.id][target.id] = {};
+
+ addTargetRecursively(tempObj[draggable.id][target.id], target.draggableObj, target.draggableObj.onTarget);
+ }
}
// Check if input has an answer from server. If yes, then position
@@ -59,6 +70,7 @@ define(['logme'], function (logme) {
var inputElVal;
inputElVal = $('#input_' + state.problemId).val();
+
if (inputElVal.length === 0) {
return false;
}
@@ -68,95 +80,147 @@ define(['logme'], function (logme) {
return true;
}
- function getUseTargets(answer) {
- if ($.isArray(answer.draggables) === false) {
- logme('ERROR: answer.draggables is not an array.');
+ function processAnswerTargets(state, answerSortedByDepth, minDepth, maxDepth, depth, i) {
+ var baseDraggableId, baseDraggable, baseTargetId, baseTarget,
+ layeredDraggableId, layeredDraggable, layeredTargetId, layeredTarget,
+ chain;
- return;
- } else if (answer.draggables.length === 0) {
- return;
- }
-
- if ($.isPlainObject(answer.draggables[0]) === false) {
- logme('ERROR: answer.draggables array does not contain objects.');
+ if (depth === 0) {
+ // We are at the lowest depth? The end.
return;
}
- for (c1 in answer.draggables[0]) {
- if (answer.draggables[0].hasOwnProperty(c1) === false) {
- continue;
- }
+ if (answerSortedByDepth.hasOwnProperty(depth) === false) {
+ // We have a depth that ts not valid, we decrease the depth by one.
+ processAnswerTargets(state, answerSortedByDepth, minDepth, maxDepth, depth - 1, 0);
- 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;
- }
+ return;
}
- logme('ERROR: answer.draggables[0] is an empty object.');
+ if (answerSortedByDepth[depth].length <= i) {
+ // We ran out of answers at this depth, go to the next depth down.
+ processAnswerTargets(state, answerSortedByDepth, minDepth, maxDepth, depth - 1, 0);
+
+ return;
+ }
+
+ chain = answerSortedByDepth[depth][i];
+
+ baseDraggableId = Object.keys(chain)[0];
+
+ // This is a hack. For now we will work with depths 1 and 3.
+ if (depth === 1) {
+ baseTargetId = chain[baseDraggableId];
+
+ layeredTargetId = null;
+ layeredDraggableId = null;
+
+ // createBaseDraggableOnTarget(state, baseDraggableId, baseTargetId);
+ } else if (depth === 3) {
+ layeredDraggableId = baseDraggableId;
+
+ layeredTargetId = Object.keys(chain[layeredDraggableId])[0];
+
+ baseDraggableId = Object.keys(chain[layeredDraggableId][layeredTargetId])[0];
+
+ baseTargetId = chain[layeredDraggableId][layeredTargetId][baseDraggableId];
+ }
+
+ checkBaseDraggable();
return;
+
+ function checkBaseDraggable() {
+ if ((baseDraggable = getById(state, 'draggables', baseDraggableId, null, false, baseTargetId)) === null) {
+ createBaseDraggableOnTarget(state, baseDraggableId, baseTargetId, true, function () {
+ if ((baseDraggable = getById(state, 'draggables', baseDraggableId, null, false, baseTargetId)) === null) {
+ console.log('ERROR: Could not successfully create a base draggable on a base target.');
+ } else {
+ baseTarget = baseDraggable.onTarget;
+
+ if ((layeredTargetId === null) || (layeredDraggableId === null)) {
+ processAnswerTargets(state, answerSortedByDepth, minDepth, maxDepth, depth, i + 1);
+ } else {
+ checklayeredDraggable();
+ }
+ }
+ });
+ } else {
+ baseTarget = baseDraggable.onTarget;
+
+ if ((layeredTargetId === null) || (layeredDraggableId === null)) {
+ processAnswerTargets(state, answerSortedByDepth, minDepth, maxDepth, depth, i + 1);
+ } else {
+ checklayeredDraggable();
+ }
+ }
+ }
+
+ function checklayeredDraggable() {
+ if ((layeredDraggable = getById(state, 'draggables', layeredDraggableId, null, false, layeredTargetId, baseDraggableId, baseTargetId)) === null) {
+ layeredDraggable = getById(state, 'draggables', layeredDraggableId);
+ layeredTarget = null;
+ baseDraggable.targetField.every(function (target) {
+ if (target.id === layeredTargetId) {
+ layeredTarget = target;
+ }
+
+ return true;
+ });
+
+ if ((layeredDraggable !== null) && (layeredTarget !== null)) {
+ layeredDraggable.moveDraggableTo('target', layeredTarget, function () {
+ processAnswerTargets(state, answerSortedByDepth, minDepth, maxDepth, depth, i + 1);
+ });
+ } else {
+ processAnswerTargets(state, answerSortedByDepth, minDepth, maxDepth, depth, i + 1);
+ }
+ } else {
+ processAnswerTargets(state, answerSortedByDepth, minDepth, maxDepth, depth, i + 1);
+ }
+ }
}
- function processAnswerTargets(state, answer) {
- var draggableId, draggable, targetId, target;
+ function createBaseDraggableOnTarget(state, draggableId, targetId, reportError, funcCallback) {
+ var draggable, 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;
+ if ((draggable = getById(state, 'draggables', draggableId)) === null) {
+ if (reportError !== false) {
+ logme(
+ 'ERROR: In answer there exists a ' +
+ 'draggable ID "' + draggableId + '". No ' +
+ 'draggable with this ID could be found.'
+ );
}
- }(0));
+
+ return false;
+ }
+
+ if ((target = getById(state, 'targets', targetId)) === null) {
+ if (reportError !== false) {
+ logme(
+ 'ERROR: In answer there exists a target ' +
+ 'ID "' + targetId + '". No target with this ' +
+ 'ID could be found.'
+ );
+ }
+
+ return false;
+ }
+
+ draggable.moveDraggableTo('target', target, funcCallback);
+
+ return true;
}
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) {
+ while (c1 < answer.length) {
+ for (draggableId in answer[c1]) {
+ if (answer[c1].hasOwnProperty(draggableId) === false) {
continue;
}
@@ -171,8 +235,8 @@ define(['logme'], function (logme) {
}
draggable.moveDraggableTo('XY', {
- 'x': answer.draggables[c1][draggableId][0],
- 'y': answer.draggables[c1][draggableId][1]
+ 'x': answer[c1][draggableId][0],
+ 'y': answer[c1][draggableId][1]
});
}
@@ -182,33 +246,110 @@ define(['logme'], function (logme) {
}
function repositionDraggables(state, answer) {
- if (answer.draggables.length === 0) {
+ var answerSortedByDepth, minDepth, maxDepth;
+
+ answerSortedByDepth = {};
+ minDepth = 1000;
+ maxDepth = 0;
+
+ answer.every(function (chain) {
+ var depth;
+
+ depth = findDepth(chain, 0);
+
+ if (depth < minDepth) {
+ minDepth = depth;
+ }
+ if (depth > maxDepth) {
+ maxDepth = depth;
+ }
+
+ if (answerSortedByDepth.hasOwnProperty(depth) === false) {
+ answerSortedByDepth[depth] = [];
+ }
+
+ answerSortedByDepth[depth].push(chain);
+
+ return true;
+ });
+
+ if (answer.length === 0) {
return;
}
- if (state.config.individualTargets !== getUseTargets(answer)) {
- logme('ERROR: JSON config is not consistent with server response.');
-
+ // For now we support only one case.
+ if ((minDepth < 1) || (maxDepth > 3)) {
return;
}
if (state.config.individualTargets === true) {
- processAnswerTargets(state, answer);
+ processAnswerTargets(state, answerSortedByDepth, minDepth, maxDepth, maxDepth, 0);
} else if (state.config.individualTargets === false) {
processAnswerPositions(state, answer);
}
}
- function getById(state, type, id) {
+ function findDepth(tempObj, depth) {
+ var i;
+
+ if ($.isPlainObject(tempObj) === false) {
+ return depth;
+ }
+
+ depth += 1;
+
+ for (i in tempObj) {
+ if (tempObj.hasOwnProperty(i) === true) {
+ depth = findDepth(tempObj[i], depth);
+ }
+ }
+
+ return depth;
+ }
+
+ function getById(state, type, id, fromTargetField, inContainer, targetId, baseDraggableId, baseTargetId) {
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];
+ if ((targetId !== undefined) && (inContainer === false) && (baseDraggableId !== undefined) && (baseTargetId !== undefined)) {
+ if (
+ (state[type][c1].id === id) &&
+ (state[type][c1].inContainer === false) &&
+ (state[type][c1].onTarget.id === targetId) &&
+ (state[type][c1].onTarget.type === 'on_drag') &&
+ (state[type][c1].onTarget.draggableObj.id === baseDraggableId) &&
+ (state[type][c1].onTarget.draggableObj.onTarget.id === baseTargetId)
+ ) {
+ return state[type][c1];
+ }
+ } else if ((targetId !== undefined) && (inContainer === false)) {
+ if (
+ (state[type][c1].id === id) &&
+ (state[type][c1].inContainer === false) &&
+ (state[type][c1].onTarget.id === targetId)
+ ) {
+ return state[type][c1];
+ }
+ } else {
+ if (inContainer === false) {
+ if ((state[type][c1].id === id) && (state[type][c1].inContainer === false)) {
+ return state[type][c1];
+ }
+ } else {
+ if ((state[type][c1].id === id) && (state[type][c1].inContainer === true)) {
+ return state[type][c1];
+ }
+ }
}
} else { // 'targets'
- if (state[type][c1].id === id) {
- return state[type][c1];
+ if (fromTargetField === true) {
+ if ((state[type][c1].id === id) && (state[type][c1].type === 'on_drag')) {
+ return state[type][c1];
+ }
+ } else {
+ if ((state[type][c1].id === id) && (state[type][c1].type === 'base')) {
+ return state[type][c1];
+ }
}
}
@@ -218,10 +359,5 @@ define(['logme'], function (logme) {
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)
+}); // End-of: define(['logme'], function (logme) {
+}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); // End-of: (function (requirejs, require, define) {