Commit 7af36eb6 by Victor Shnayder

Merge pull request #1598 from MITx/diana/capa-input-ajax

Give InputTypes the ability to handle their own AJAX
parents 69601047 4b7d1deb
...@@ -146,6 +146,13 @@ class LoncapaProblem(object): ...@@ -146,6 +146,13 @@ class LoncapaProblem(object):
if not self.student_answers: # True when student_answers is an empty dict if not self.student_answers: # True when student_answers is an empty dict
self.set_initial_display() self.set_initial_display()
# dictionary of InputType objects associated with this problem
# input_id string -> InputType object
self.inputs = {}
self.extracted_tree = self._extract_html(self.tree)
def do_reset(self): def do_reset(self):
''' '''
Reset internal state to unfinished, with no answers Reset internal state to unfinished, with no answers
...@@ -324,7 +331,27 @@ class LoncapaProblem(object): ...@@ -324,7 +331,27 @@ class LoncapaProblem(object):
''' '''
Main method called externally to get the HTML to be rendered for this capa Problem. Main method called externally to get the HTML to be rendered for this capa Problem.
''' '''
return contextualize_text(etree.tostring(self._extract_html(self.tree)), self.context) html = contextualize_text(etree.tostring(self._extract_html(self.tree)), self.context)
return html
def handle_input_ajax(self, get):
'''
InputTypes can support specialized AJAX calls. Find the correct input and pass along the correct data
Also, parse out the dispatch from the get so that it can be passed onto the input type nicely
'''
# pull out the id
input_id = get['input_id']
if self.inputs[input_id]:
dispatch = get['dispatch']
return self.inputs[input_id].handle_ajax(dispatch, get)
else:
log.warning("Could not find matching input for id: %s" % problem_id)
return {}
# ======= Private Methods Below ======== # ======= Private Methods Below ========
...@@ -458,6 +485,8 @@ class LoncapaProblem(object): ...@@ -458,6 +485,8 @@ class LoncapaProblem(object):
finally: finally:
sys.path = original_path sys.path = original_path
def _extract_html(self, problemtree): # private def _extract_html(self, problemtree): # private
''' '''
Main (private) function which converts Problem XML tree to HTML. Main (private) function which converts Problem XML tree to HTML.
...@@ -468,6 +497,7 @@ class LoncapaProblem(object): ...@@ -468,6 +497,7 @@ class LoncapaProblem(object):
Used by get_html. Used by get_html.
''' '''
if (problemtree.tag == 'script' and problemtree.get('type') if (problemtree.tag == 'script' and problemtree.get('type')
and 'javascript' in problemtree.get('type')): and 'javascript' in problemtree.get('type')):
# leave javascript intact. # leave javascript intact.
...@@ -484,8 +514,9 @@ class LoncapaProblem(object): ...@@ -484,8 +514,9 @@ class LoncapaProblem(object):
msg = '' msg = ''
hint = '' hint = ''
hintmode = None hintmode = None
input_id = problemtree.get('id')
if problemid in self.correct_map: if problemid in self.correct_map:
pid = problemtree.get('id') pid = input_id
status = self.correct_map.get_correctness(pid) status = self.correct_map.get_correctness(pid)
msg = self.correct_map.get_msg(pid) msg = self.correct_map.get_msg(pid)
hint = self.correct_map.get_hint(pid) hint = self.correct_map.get_hint(pid)
...@@ -496,17 +527,17 @@ class LoncapaProblem(object): ...@@ -496,17 +527,17 @@ class LoncapaProblem(object):
value = self.student_answers[problemid] value = self.student_answers[problemid]
# do the rendering # do the rendering
state = {'value': value, state = {'value': value,
'status': status, 'status': status,
'id': problemtree.get('id'), 'id': input_id,
'feedback': {'message': msg, 'feedback': {'message': msg,
'hint': hint, 'hint': hint,
'hintmode': hintmode, }} 'hintmode': hintmode, }}
input_type_cls = inputtypes.registry.get_class_for_tag(problemtree.tag) input_type_cls = inputtypes.registry.get_class_for_tag(problemtree.tag)
the_input = input_type_cls(self.system, problemtree, state) # save the input type so that we can make ajax calls on it if we need to
return the_input.get_html() self.inputs[input_id] = input_type_cls(self.system, problemtree, state)
return self.inputs[input_id].get_html()
# let each Response render itself # let each Response render itself
if problemtree in self.responders: if problemtree in self.responders:
......
...@@ -215,6 +215,18 @@ class InputTypeBase(object): ...@@ -215,6 +215,18 @@ class InputTypeBase(object):
""" """
pass pass
def handle_ajax(self, dispatch, get):
"""
InputTypes that need to handle specialized AJAX should override this.
Input:
dispatch: a string that can be used to determine how to handle the data passed in
get: a dictionary containing the data that was sent with the ajax call
Output:
a dictionary object that can be serialized into JSON. This will be sent back to the Javascript.
"""
pass
def _get_render_context(self): def _get_render_context(self):
""" """
......
...@@ -125,6 +125,8 @@ class CapaHtmlRenderTest(unittest.TestCase): ...@@ -125,6 +125,8 @@ class CapaHtmlRenderTest(unittest.TestCase):
expected_solution_context = {'id': '1_solution_1'} expected_solution_context = {'id': '1_solution_1'}
expected_calls = [mock.call('textline.html', expected_textline_context), expected_calls = [mock.call('textline.html', expected_textline_context),
mock.call('solutionspan.html', expected_solution_context),
mock.call('textline.html', expected_textline_context),
mock.call('solutionspan.html', expected_solution_context)] mock.call('solutionspan.html', expected_solution_context)]
self.assertEqual(test_system.render_template.call_args_list, self.assertEqual(test_system.render_template.call_args_list,
......
...@@ -412,6 +412,7 @@ class CapaModule(XModule): ...@@ -412,6 +412,7 @@ class CapaModule(XModule):
'weight': self.descriptor.weight, 'weight': self.descriptor.weight,
} }
context = {'problem': content, context = {'problem': content,
'id': self.id, 'id': self.id,
'check_button': check_button, 'check_button': check_button,
...@@ -449,6 +450,7 @@ class CapaModule(XModule): ...@@ -449,6 +450,7 @@ class CapaModule(XModule):
'problem_save': self.save_problem, 'problem_save': self.save_problem,
'problem_show': self.get_answer, 'problem_show': self.get_answer,
'score_update': self.update_score, 'score_update': self.update_score,
'input_ajax': self.lcp.handle_input_ajax
} }
if dispatch not in handlers: if dispatch not in handlers:
......
...@@ -76,6 +76,24 @@ class @Problem ...@@ -76,6 +76,24 @@ class @Problem
# TODO: Some logic to dynamically adjust polling rate based on queuelen # TODO: Some logic to dynamically adjust polling rate based on queuelen
window.queuePollerID = window.setTimeout(@poll, 1000) window.queuePollerID = window.setTimeout(@poll, 1000)
# Use this if you want to make an ajax call on the input type object
# static method so you don't have to instantiate a Problem in order to use it
# Input:
# url: the AJAX url of the problem
# input_id: the input_id of the input you would like to make the call on
# NOTE: the id is the ${id} part of "input_${id}" during rendering
# If this function is passed the entire prefixed id, the backend may have trouble
# finding the correct input
# dispatch: string that indicates how this data should be handled by the inputtype
# callback: the function that will be called once the AJAX call has been completed.
# It will be passed a response object
@inputAjax: (url, input_id, dispatch, data, callback) ->
data['dispatch'] = dispatch
data['input_id'] = input_id
$.postWithPrefix "#{url}/input_ajax", data, callback
render: (content) -> render: (content) ->
if content if content
@el.html(content) @el.html(content)
......
...@@ -108,7 +108,9 @@ class CapaFactory(object): ...@@ -108,7 +108,9 @@ class CapaFactory(object):
else: else:
instance_state = None instance_state = None
module = CapaModule(test_system(), location, system = test_system()
system.render_template = Mock(return_value="<div>Test Template HTML</div>")
module = CapaModule(system, location,
definition, descriptor, definition, descriptor,
instance_state, None, metadata=metadata) instance_state, None, metadata=metadata)
...@@ -185,6 +187,7 @@ class CapaModuleTest(unittest.TestCase): ...@@ -185,6 +187,7 @@ class CapaModuleTest(unittest.TestCase):
max_attempts="1", max_attempts="1",
attempts="0", attempts="0",
due=self.yesterday_str) due=self.yesterday_str)
self.assertTrue(after_due_date.answer_available()) self.assertTrue(after_due_date.answer_available())
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment