merge feature/alex/drag_and_drop-mitx into feature/alex/poll-merged
This commit is contained in:
@@ -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:
|
||||
|
||||
|
||||
<editagene width="800" hight="500" dna_sequence="ETAAGGCTATAACCGA" />
|
||||
"""
|
||||
|
||||
|
||||
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)
|
||||
|
||||
@@ -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"},
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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'))
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
131
common/static/js/capa/drag_and_drop/draggable_events.js
Normal file
131
common/static/js/capa/drag_and_drop/draggable_events.js
Normal file
@@ -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) {
|
||||
379
common/static/js/capa/drag_and_drop/draggable_logic.js
Normal file
379
common/static/js/capa/drag_and_drop/draggable_logic.js
Normal file
@@ -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) {
|
||||
@@ -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 = $('<img />');
|
||||
draggableObj.iconEl.attr('src', draggableObj.originalConfigObj.icon);
|
||||
draggableObj.iconEl.load(function () {
|
||||
|
||||
draggableObj.iconEl = $('<div></div>');
|
||||
draggableObj.iconImgEl = $('<img />');
|
||||
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 = $('<img />');
|
||||
draggableObj.iconEl.attr('src', obj.icon);
|
||||
draggableObj.iconEl.load(function () {
|
||||
draggableObj.iconEl = $('<div></div>');
|
||||
|
||||
draggableObj.iconImgEl = $('<img />');
|
||||
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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
'" ' +
|
||||
'></div>'
|
||||
);
|
||||
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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user