Merge pull request #1598 from MITx/diana/capa-input-ajax
Give InputTypes the ability to handle their own AJAX
This commit is contained in:
@@ -146,6 +146,13 @@ class LoncapaProblem(object):
|
||||
if not self.student_answers: # True when student_answers is an empty dict
|
||||
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):
|
||||
'''
|
||||
Reset internal state to unfinished, with no answers
|
||||
@@ -324,7 +331,27 @@ class LoncapaProblem(object):
|
||||
'''
|
||||
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 ========
|
||||
|
||||
@@ -458,6 +485,8 @@ class LoncapaProblem(object):
|
||||
finally:
|
||||
sys.path = original_path
|
||||
|
||||
|
||||
|
||||
def _extract_html(self, problemtree): # private
|
||||
'''
|
||||
Main (private) function which converts Problem XML tree to HTML.
|
||||
@@ -468,6 +497,7 @@ class LoncapaProblem(object):
|
||||
|
||||
Used by get_html.
|
||||
'''
|
||||
|
||||
if (problemtree.tag == 'script' and problemtree.get('type')
|
||||
and 'javascript' in problemtree.get('type')):
|
||||
# leave javascript intact.
|
||||
@@ -484,8 +514,9 @@ class LoncapaProblem(object):
|
||||
msg = ''
|
||||
hint = ''
|
||||
hintmode = None
|
||||
input_id = problemtree.get('id')
|
||||
if problemid in self.correct_map:
|
||||
pid = problemtree.get('id')
|
||||
pid = input_id
|
||||
status = self.correct_map.get_correctness(pid)
|
||||
msg = self.correct_map.get_msg(pid)
|
||||
hint = self.correct_map.get_hint(pid)
|
||||
@@ -496,17 +527,17 @@ class LoncapaProblem(object):
|
||||
value = self.student_answers[problemid]
|
||||
|
||||
# do the rendering
|
||||
|
||||
state = {'value': value,
|
||||
'status': status,
|
||||
'id': problemtree.get('id'),
|
||||
'id': input_id,
|
||||
'feedback': {'message': msg,
|
||||
'hint': hint,
|
||||
'hintmode': hintmode, }}
|
||||
|
||||
input_type_cls = inputtypes.registry.get_class_for_tag(problemtree.tag)
|
||||
the_input = input_type_cls(self.system, problemtree, state)
|
||||
return the_input.get_html()
|
||||
# save the input type so that we can make ajax calls on it if we need to
|
||||
self.inputs[input_id] = input_type_cls(self.system, problemtree, state)
|
||||
return self.inputs[input_id].get_html()
|
||||
|
||||
# let each Response render itself
|
||||
if problemtree in self.responders:
|
||||
|
||||
@@ -215,6 +215,18 @@ class InputTypeBase(object):
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
|
||||
@@ -125,6 +125,8 @@ class CapaHtmlRenderTest(unittest.TestCase):
|
||||
expected_solution_context = {'id': '1_solution_1'}
|
||||
|
||||
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)]
|
||||
|
||||
self.assertEqual(test_system.render_template.call_args_list,
|
||||
|
||||
@@ -412,6 +412,7 @@ class CapaModule(XModule):
|
||||
'weight': self.descriptor.weight,
|
||||
}
|
||||
|
||||
|
||||
context = {'problem': content,
|
||||
'id': self.id,
|
||||
'check_button': check_button,
|
||||
@@ -449,6 +450,7 @@ class CapaModule(XModule):
|
||||
'problem_save': self.save_problem,
|
||||
'problem_show': self.get_answer,
|
||||
'score_update': self.update_score,
|
||||
'input_ajax': self.lcp.handle_input_ajax
|
||||
}
|
||||
|
||||
if dispatch not in handlers:
|
||||
|
||||
@@ -76,6 +76,24 @@ class @Problem
|
||||
# TODO: Some logic to dynamically adjust polling rate based on queuelen
|
||||
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) ->
|
||||
if content
|
||||
@el.html(content)
|
||||
|
||||
@@ -108,7 +108,9 @@ class CapaFactory(object):
|
||||
else:
|
||||
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,
|
||||
instance_state, None, metadata=metadata)
|
||||
|
||||
@@ -185,6 +187,7 @@ class CapaModuleTest(unittest.TestCase):
|
||||
max_attempts="1",
|
||||
attempts="0",
|
||||
due=self.yesterday_str)
|
||||
|
||||
self.assertTrue(after_due_date.answer_available())
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user