Commit e1766854 by ichuang

dynamic math - changes to javascript, responsetypes, and inputtypes

main.html now includes mathjax_include for all mathjax stuff
problem.js refreshes mathjax formulas on change
all previous *_fromjs renamed to *_dynamath, eg textline_dynamath
parent 41996fae
...@@ -209,6 +209,11 @@ def choicegroup(element, value, status, msg=''): ...@@ -209,6 +209,11 @@ def choicegroup(element, value, status, msg=''):
@register_render_function @register_render_function
def textline(element, value, state, msg=""): def textline(element, value, state, msg=""):
'''
Simple text line input, with optional size specification.
'''
if element.get('math') or element.get('dojs'): # 'dojs' flag is temporary, for backwards compatibility with 8.02x
return SimpleInput.xml_tags['textline_dynamath'](element,value,state,msg)
eid=element.get('id') eid=element.get('id')
count = int(eid.split('_')[-2])-1 # HACK count = int(eid.split('_')[-2])-1 # HACK
size = element.get('size') size = element.get('size')
...@@ -219,27 +224,25 @@ def textline(element, value, state, msg=""): ...@@ -219,27 +224,25 @@ def textline(element, value, state, msg=""):
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
@register_render_function @register_render_function
def js_textline(element, value, status, msg=''): def textline_dynamath(element, value, status, msg=''):
''' '''
Plan: We will inspect element to figure out type Text line input with dynamic math display (equation rendered on client in real time during input).
''' '''
# TODO: Make a wrapper for <formulainput> # TODO: Make a wrapper for <formulainput>
# TODO: Make an AJAX loop to confirm equation is okay in real-time as user types # TODO: Make an AJAX loop to confirm equation is okay in real-time as user types
## TODO: Code should follow PEP8 (4 spaces per indentation level) ## TODO: Code should follow PEP8 (4 spaces per indentation level)
''' '''
textline is used for simple one-line inputs, like formularesponse and symbolicresponse. textline is used for simple one-line inputs, like formularesponse and symbolicresponse.
uses a <span id=display_eid>`{::}`</span>
and a hidden textarea with id=input_eid_fromjs for the mathjax rendering and return.
''' '''
eid=element.get('id') eid=element.get('id')
count = int(eid.split('_')[-2])-1 # HACK count = int(eid.split('_')[-2])-1 # HACK
size = element.get('size') size = element.get('size')
dojs = element.get('dojs') # dojs is used for client-side javascript display & return
# when dojs=='math', a <span id=display_eid>`{::}`</span>
# and a hidden textarea with id=input_eid_fromjs will be output
context = {'id':eid, 'value':value, 'state':status, 'count':count, 'size': size, context = {'id':eid, 'value':value, 'state':status, 'count':count, 'size': size,
'dojs':dojs,
'msg':msg, 'msg':msg,
} }
html=render_to_string("jstext.html", context) html=render_to_string("textinput_dynamath.html", context)
return etree.XML(html) return etree.XML(html)
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
......
...@@ -8,7 +8,9 @@ Used by capa_problem.py ...@@ -8,7 +8,9 @@ Used by capa_problem.py
''' '''
# standard library imports # standard library imports
import inspect
import json import json
import logging
import math import math
import numbers import numbers
import numpy import numpy
...@@ -34,6 +36,8 @@ import eia ...@@ -34,6 +36,8 @@ import eia
from util import contextualize_text from util import contextualize_text
log = logging.getLogger("mitx.courseware")
def compare_with_tolerance(v1, v2, tol): def compare_with_tolerance(v1, v2, tol):
''' Compare v1 to v2 with maximum tolerance tol ''' Compare v1 to v2 with maximum tolerance tol
tol is relative if it ends in %; otherwise, it is absolute tol is relative if it ends in %; otherwise, it is absolute
...@@ -271,6 +275,9 @@ def sympy_check2(): ...@@ -271,6 +275,9 @@ def sympy_check2():
self.expect = xml.get('expect') self.expect = xml.get('expect')
self.myid = xml.get('id') self.myid = xml.get('id')
if settings.DEBUG:
log.info('answer_ids=%s' % self.answer_ids)
# the <answer>...</answer> stanza should be local to the current <customresponse>. So try looking there first. # the <answer>...</answer> stanza should be local to the current <customresponse>. So try looking there first.
self.code = None self.code = None
answer = None answer = None
...@@ -283,7 +290,7 @@ def sympy_check2(): ...@@ -283,7 +290,7 @@ def sympy_check2():
# ie the comparison function is defined in the <script>...</script> stanza instead # ie the comparison function is defined in the <script>...</script> stanza instead
cfn = xml.get('cfn') cfn = xml.get('cfn')
if cfn: if cfn:
if settings.DEBUG: print "[courseware.capa.responsetypes] cfn = ",cfn if settings.DEBUG: log.info("[courseware.capa.responsetypes] cfn = %s" % cfn)
if cfn in context: if cfn in context:
self.code = context[cfn] self.code = context[cfn]
else: else:
...@@ -321,7 +328,8 @@ def sympy_check2(): ...@@ -321,7 +328,8 @@ def sympy_check2():
msg += '\n idset = %s, error = %s' % (idset,err) msg += '\n idset = %s, error = %s' % (idset,err)
raise Exception,msg raise Exception,msg
fromjs = [ getkey2(student_answers,k+'_fromjs',None) for k in idset ] # ordered list of fromjs_XXX responses (if exists) # global variable in context which holds the Presentation MathML from dynamic math input
dynamath = [ student_answers.get(k+'_dynamath',None) for k in idset ] # ordered list of dynamath responses
# if there is only one box, and it's empty, then don't evaluate # if there is only one box, and it's empty, then don't evaluate
if len(idset)==1 and not submission[0]: if len(idset)==1 and not submission[0]:
...@@ -339,7 +347,7 @@ def sympy_check2(): ...@@ -339,7 +347,7 @@ def sympy_check2():
'expect': self.expect, # expected answer (if given as attribute) 'expect': self.expect, # expected answer (if given as attribute)
'submission':submission, # ordered list of student answers from entry boxes in our subtree 'submission':submission, # ordered list of student answers from entry boxes in our subtree
'idset':idset, # ordered list of ID's of all entry boxes in our subtree 'idset':idset, # ordered list of ID's of all entry boxes in our subtree
'fromjs':fromjs, # ordered list of all javascript inputs in our subtree 'dynamath':dynamath, # ordered list of all javascript inputs in our subtree
'answers':student_answers, # dict of student's responses, with keys being entry box IDs 'answers':student_answers, # dict of student's responses, with keys being entry box IDs
'correct':correct, # the list to be filled in by the check function 'correct':correct, # the list to be filled in by the check function
'messages':messages, # the list of messages to be filled in by the check function 'messages':messages, # the list of messages to be filled in by the check function
...@@ -359,30 +367,41 @@ def sympy_check2(): ...@@ -359,30 +367,41 @@ def sympy_check2():
# this is an interface to the Tutor2 check functions # this is an interface to the Tutor2 check functions
fn = self.code fn = self.code
ret = None ret = None
if settings.DEBUG: log.info(" submission = %s" % submission)
try: try:
answer_given = submission[0] if (len(idset)==1) else submission answer_given = submission[0] if (len(idset)==1) else submission
if fn.func_code.co_argcount>=4: # does it want four arguments (the answers dict, myname)? # handle variable number of arguments in check function, for backwards compatibility
ret = fn(self.expect,answer_given,student_answers,self.answer_ids[0]) # with various Tutor2 check functions
elif fn.func_code.co_argcount>=3: # does it want a third argument (the answers dict)? args = [self.expect,answer_given,student_answers,self.answer_ids[0]]
ret = fn(self.expect,answer_given,student_answers) argspec = inspect.getargspec(fn)
else: nargs = len(argspec.args)-len(argspec.defaults or [])
ret = fn(self.expect,answer_given) kwargs = {}
for argname in argspec.args[nargs:]:
kwargs[argname] = self.context[argname] if argname in self.context else None
if settings.DEBUG: if settings.DEBUG:
print '[courseware.capa.responsetypes.customresponse] answer_given=%s' % answer_given log.debug('[courseware.capa.responsetypes.customresponse] answer_given=%s' % answer_given)
# log.info('nargs=%d, args=%s' % (nargs,args))
ret = fn(*args[:nargs],**kwargs)
except Exception,err: except Exception,err:
print "oops in customresponse (cfn) error %s" % err log.error("oops in customresponse (cfn) error %s" % err)
# print "context = ",self.context # print "context = ",self.context
print traceback.format_exc() log.error(traceback.format_exc())
raise Exception,"oops in customresponse (cfn) error %s" % err raise Exception,"oops in customresponse (cfn) error %s" % err
if settings.DEBUG: print "[courseware.capa.responsetypes.customresponse.get_score] ret = ",ret if settings.DEBUG: log.info("[courseware.capa.responsetypes.customresponse.get_score] ret = %s" % ret)
if type(ret)==dict: if type(ret)==dict:
correct = ['correct']*len(idset) if ret['ok'] else ['incorrect']*len(idset) correct = ['correct']*len(idset) if ret['ok'] else ['incorrect']*len(idset)
msg = ret['msg'] msg = ret['msg']
if 1: if 1:
# try to clean up message html # try to clean up message html
log.info('unicode2html(msg) = %s' % msg)
msg = '<html>'+msg+'</html>' msg = '<html>'+msg+'</html>'
msg = etree.tostring(fromstring_bs(msg),pretty_print=True) msg = msg.replace('&#60;','&lt;')
#msg = msg.replace('&lt;','<')
msg = etree.tostring(fromstring_bs(msg,convertEntities=None),pretty_print=True)
#msg = etree.tostring(fromstring_bs(msg),pretty_print=True)
msg = msg.replace('&#13;','') msg = msg.replace('&#13;','')
#msg = re.sub('<html>(.*)</html>','\\1',msg,flags=re.M|re.DOTALL) # python 2.7 #msg = re.sub('<html>(.*)</html>','\\1',msg,flags=re.M|re.DOTALL) # python 2.7
msg = re.sub('(?ms)<html>(.*)</html>','\\1',msg) msg = re.sub('(?ms)<html>(.*)</html>','\\1',msg)
......
...@@ -28,6 +28,12 @@ def check_problem_code(ans,the_lcp,correct_answers,false_answers): ...@@ -28,6 +28,12 @@ def check_problem_code(ans,the_lcp,correct_answers,false_answers):
msg += '<hr width="100%"/>' msg += '<hr width="100%"/>'
is_ok = True is_ok = True
if (not correct_answers) or (not false_answers):
ret = {'ok':is_ok,
'msg': msg,
}
return ret
try: try:
# check correctness # check correctness
fp = the_lcp.system.filestore.open('problems/%s.xml' % pfn) fp = the_lcp.system.filestore.open('problems/%s.xml' % pfn)
......
from formula import *
from sympy_check2 import *
...@@ -234,7 +234,10 @@ class formula(object): ...@@ -234,7 +234,10 @@ class formula(object):
if self.the_cmathml: return self.the_cmathml if self.the_cmathml: return self.the_cmathml
# pre-process the presentation mathml before sending it to snuggletex to convert to content mathml # pre-process the presentation mathml before sending it to snuggletex to convert to content mathml
xml = self.preprocess_pmathml(self.expr) try:
xml = self.preprocess_pmathml(self.expr)
except Exception,err:
return "<html>Error! Cannot process pmathml</html>"
pmathml = etree.tostring(xml,pretty_print=True) pmathml = etree.tostring(xml,pretty_print=True)
self.the_pmathml = pmathml self.the_pmathml = pmathml
......
...@@ -16,7 +16,11 @@ from formula import * ...@@ -16,7 +16,11 @@ from formula import *
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
# check function interface # check function interface
def sympy_check(expect,ans,adict={},symtab=None,extra_options=None): def sympy_check_simple(expect,ans,adict={},symtab=None,extra_options=None):
'''
Check a symbolic mathematical expression using sympy.
The input is an ascii string (not MathML)
'''
options = {'__MATRIX__':False,'__ABC__':False,'__LOWER__':False} options = {'__MATRIX__':False,'__ABC__':False,'__LOWER__':False}
if extra_options: options.update(extra_options) if extra_options: options.update(extra_options)
...@@ -130,7 +134,11 @@ def check(expect,given,numerical=False,matrix=False,normphase=False,abcsym=False ...@@ -130,7 +134,11 @@ def check(expect,given,numerical=False,matrix=False,normphase=False,abcsym=False
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
# Check function interface, which takes pmathml input # Check function interface, which takes pmathml input
def sympy_check2(expect,ans,adict={},abname=''): def sympy_check(expect,ans,adict={},abname=''):
'''
Check a symbolic mathematical expression using sympy.
The input may be presentation MathML
'''
msg = '' msg = ''
# msg += '<p/>abname=%s' % abname # msg += '<p/>abname=%s' % abname
......
...@@ -36,18 +36,12 @@ ...@@ -36,18 +36,12 @@
<script src="${static.url('js/html5shiv.js')}"></script> <script src="${static.url('js/html5shiv.js')}"></script>
<![endif]--> <![endif]-->
<script type="text/x-mathjax-config"> <%block name="headextra"/>
MathJax.Hub.Config({
tex2jax: {inlineMath: [["\\(","\\)"]],
displayMath: [["\\[","\\]"]]}
});
</script>
<%block name="headextra"/>
<!-- This must appear after all mathjax-config blocks, so it is after the imports from the other templates. <!-- This must appear after all mathjax-config blocks, so it is after the imports from the other templates.
It can't be run through static.url because MathJax uses crazy url introspection to do lazy loading of It can't be run through static.url because MathJax uses crazy url introspection to do lazy loading of
MathJax extension libraries --> MathJax extension libraries -->
<script type="text/javascript" src="/static/js/mathjax-MathJax-c9db6ac/MathJax.js?config=TeX-AMS_HTML-full"></script> <%include file="mathjax_include.html" />
</head> </head>
<body class="<%block name='bodyclass'/>"> <body class="<%block name='bodyclass'/>">
......
...@@ -28,8 +28,8 @@ ...@@ -28,8 +28,8 @@
// function to queue in MathJax to get put the MathML expression in in the right document element // function to queue in MathJax to get put the MathML expression in in the right document element
function UpdateMathML(jax,id) { function UpdateMathML(jax,id) {
toMathML(jax,function (mml) { toMathML(jax,function (mml) {
// document.getElementById(id+'_fromjs').value=math.originalText+ "\n\n=>\n\n"+ mml; // document.getElementById(id+'_dynamath').value=math.originalText+ "\n\n=>\n\n"+ mml;
delem = document.getElementById("input_" + id + "_fromjs"); delem = document.getElementById("input_" + id + "_dynamath");
if (delem) { delem.value=mml; }; if (delem) { delem.value=mml; };
mmlset[id] = mml; mmlset[id] = mml;
}) })
...@@ -83,7 +83,8 @@ function DoUpdateMath(inputId) { ...@@ -83,7 +83,8 @@ function DoUpdateMath(inputId) {
<!-- This must appear after all mathjax-config blocks, so it is after the imports from the other templates --> <!-- This must appear after all mathjax-config blocks, so it is after the imports from the other templates -->
<!-- TODO: move to settings --> <!-- TODO: move to settings -->
<script type="text/javascript" ## <script type="text/javascript" src="http://cdn.mathjax.org/mathjax/2.0-latest/MathJax.js?config=TeX-MML-AM_HTMLorMML-full">
src="http://cdn.mathjax.org/mathjax/2.0-latest/MathJax.js?config=TeX-MML-AM_HTMLorMML-full"> ## <script type="text/javascript" src="/static/js/mathjax-MathJax-c9db6ac/MathJax.js?config=TeX-AMS_HTML-full"></script>
<script type="text/javascript" src="/static/js/mathjax-MathJax-c9db6ac/MathJax.js?config=TeX-MML-AM_HTMLorMML-full"></script>
</script> </script>
...@@ -2,6 +2,23 @@ function ${ id }_content_updated() { ...@@ -2,6 +2,23 @@ function ${ id }_content_updated() {
MathJax.Hub.Queue(["Typeset",MathJax.Hub]); MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
update_schematics(); update_schematics();
// dynamic math display: add to jaxset for automatic rendering
$.each($("[id^=input_${ id }_]"), function(index,value){
theid = value.id.replace("input_",""); // ID of the response
if (document.getElementById("display_" + theid)){
MathJax.Hub.queue.Push(function () {
math = MathJax.Hub.getAllJax("display_" + theid)[0];
if (math){
jaxset[theid] = math;
math.Text(document.getElementById(value.id).defaultValue);
x = document.getElementById("input_" + theid + "_dynamath");
UpdateMathML(math,theid);
}
});
};
});
$('#check_${ id }').unbind('click').click(function() { $('#check_${ id }').unbind('click').click(function() {
$("input.schematic").each(function(index,element){ element.schematic.update_value(); }); $("input.schematic").each(function(index,element){ element.schematic.update_value(); });
$(".CodeMirror").each(function(index,element){ if (element.CodeMirror.save) element.CodeMirror.save(); }); $(".CodeMirror").each(function(index,element){ if (element.CodeMirror.save) element.CodeMirror.save(); });
......
...@@ -24,20 +24,13 @@ ...@@ -24,20 +24,13 @@
<script type="text/javascript" src="/static/coffee/src/calculator.js"></script> <script type="text/javascript" src="/static/coffee/src/calculator.js"></script>
<script type="text/javascript" src="/static/coffee/src/main.js"></script> <script type="text/javascript" src="/static/coffee/src/main.js"></script>
## <%include file="mathjax_include.html" /> <%block name="headextra"/>
<script type="text/x-mathjax-config">
MathJax.Hub.Config({
tex2jax: {inlineMath: [["\\(","\\)"]],
displayMath: [["\\[","\\]"]]}
});
</script>
<%block name="headextra"/>
<!-- This must appear after all mathjax-config blocks, so it is after the imports from the other templates. <!-- This must appear after all mathjax-config blocks, so it is after the imports from the other templates.
It can't be run through static.url because MathJax uses crazy url introspection to do lazy loading of It can't be run through static.url because MathJax uses crazy url introspection to do lazy loading of
MathJax extension libraries --> MathJax extension libraries -->
<script type="text/javascript" src="/static/js/mathjax-MathJax-c9db6ac/MathJax.js?config=TeX-AMS_HTML-full"></script> <%include file="mathjax_include.html" />
## <script type="text/javascript" src="/static/js/mathjax-MathJax-c9db6ac/MathJax.js?config=TeX-AMS_HTML-full"></script>
</head> </head>
<body> <body>
......
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