Commit 232e758c by Isaac Chuang Committed by Piotr Mitros

Integrating some of Ike's courseware changes

parent 756dc978
#
# File: courseware/capa/capa_problem.py
#
'''
Main module which shows problems (of "capa" type).
This is used by capa_module.
'''
import copy
import logging
import math
......@@ -10,12 +19,13 @@ import struct
from lxml import etree
from lxml.etree import Element
from xml.sax.saxutils import escape, unescape
from mako.template import Template
from util import contextualize_text
import inputtypes
from responsetypes import NumericalResponse, FormulaResponse, CustomResponse, SchematicResponse, MultipleChoiceResponse, StudentInputError, TrueFalseResponse
from responsetypes import NumericalResponse, FormulaResponse, CustomResponse, SchematicResponse, MultipleChoiceResponse, StudentInputError, TrueFalseResponse, ExternalResponse,ImageResponse
import calc
import eia
......@@ -26,19 +36,27 @@ response_types = {'numericalresponse':NumericalResponse,
'formularesponse':FormulaResponse,
'customresponse':CustomResponse,
'schematicresponse':SchematicResponse,
'externalresponse':ExternalResponse,
'multiplechoiceresponse':MultipleChoiceResponse,
'truefalseresponse':TrueFalseResponse}
entry_types = ['textline', 'schematic', 'choicegroup']
response_properties = ["responseparam", "answer"]
'truefalseresponse':TrueFalseResponse,
'imageresponse':ImageResponse,
}
entry_types = ['textline', 'schematic', 'choicegroup','textbox','imageinput']
solution_types = ['solution'] # extra things displayed after "show answers" is pressed
response_properties = ["responseparam", "answer"] # these get captured as student responses
# How to convert from original XML to HTML
# We should do this with xlst later
html_transforms = {'problem': {'tag':'div'},
"numericalresponse": {'tag':'span'},
"customresponse": {'tag':'span'},
"externalresponse": {'tag':'span'},
"schematicresponse": {'tag':'span'},
"formularesponse": {'tag':'span'},
"multiplechoiceresponse": {'tag':'span'},
"text": {'tag':'span'}}
"text": {'tag':'span'},
"math": {'tag':'span'},
}
global_context={'random':random,
'numpy':numpy,
......@@ -50,7 +68,15 @@ global_context={'random':random,
# These should be removed from HTML output, including all subelements
html_problem_semantics = ["responseparam", "answer", "script"]
# These should be removed from HTML output, but keeping subelements
html_skip = ["numericalresponse", "customresponse", "schematicresponse", "formularesponse", "text"]
html_skip = ["numericalresponse", "customresponse", "schematicresponse", "formularesponse", "text","externalresponse"]
# removed in MC
## These should be transformed
#html_special_response = {"textline":textline.render,
# "schematic":schematic.render,
# "textbox":textbox.render,
# "solution":solution.render,
# }
class LoncapaProblem(object):
def __init__(self, fileobject, id, state=None, seed=None):
......@@ -80,6 +106,7 @@ class LoncapaProblem(object):
## Parse XML file
file_text = fileobject.read()
self.fileobject = fileobject # save it, so we can use for debugging information later
# Convert startouttext and endouttext to proper <text></text>
# TODO: Do with XML operations
file_text = re.sub("startouttext\s*/","text",file_text)
......@@ -102,6 +129,9 @@ class LoncapaProblem(object):
'done':self.done}
def get_max_score(self):
'''
TODO: multiple points for programming problems.
'''
sum = 0
for et in entry_types:
sum = sum + self.tree.xpath('count(//'+et+')')
......@@ -120,27 +150,39 @@ class LoncapaProblem(object):
'total':self.get_max_score()}
def grade_answers(self, answers):
'''
Grade student responses. Called by capa_module.check_problem.
answers is a dict of all the entries from request.POST, but with the first part
of each key removed (the string before the first "_").
Thus, for example, input_ID123 -> ID123, and input_fromjs_ID123 -> fromjs_ID123
'''
self.student_answers = answers
context=self.extract_context(self.tree)
self.correct_map = dict()
problems_simple = self.extract_problems(self.tree)
for response in problems_simple:
grader = response_types[response.tag](response, self.context)
results = grader.grade(answers)
results = grader.grade(answers) # call the responsetype instance to do the actual grading
self.correct_map.update(results)
return self.correct_map
def get_question_answers(self):
'''
Make a dict of (id,correct_answer) entries, for all the problems.
Called by "show answers" button JSON request (see capa_module)
'''
context=self.extract_context(self.tree)
answer_map = dict()
problems_simple = self.extract_problems(self.tree)
problems_simple = self.extract_problems(self.tree) # purified (flat) XML tree of just response queries
for response in problems_simple:
responder = response_types[response.tag](response, self.context)
responder = response_types[response.tag](response, self.context) # instance of numericalresponse, customresponse,...
results = responder.get_answers()
answer_map.update(results)
answer_map.update(results) # dict of (id,correct_answer)
# example for the following: <textline size="5" correct_answer="saturated" />
for entry in problems_simple.xpath("//"+"|//".join(response_properties+entry_types)):
answer = entry.get('correct_answer')
answer = entry.get('correct_answer') # correct answer, when specified elsewhere, eg in a textline
if answer:
answer_map[entry.get('id')] = contextualize_text(answer, self.context)
......@@ -149,11 +191,28 @@ class LoncapaProblem(object):
# ======= Private ========
def extract_context(self, tree, seed = struct.unpack('i', os.urandom(4))[0]): # private
''' Problem XML goes to Python execution context. Runs everything in script tags '''
'''
Extract content of <script>...</script> from the problem.xml file, and exec it in the
context of this problem. Provides ability to randomize problems, and also set
variables for problem answer checking.
Problem XML goes to Python execution context. Runs everything in script tags
'''
random.seed(self.seed)
context = dict()
for script in tree.xpath('/problem/script'):
exec script.text in global_context, context
### IKE: Why do we need these two lines?
context = {'global_context':global_context} # save global context in here also
global_context['context'] = context # and put link to local context in the global one
#for script in tree.xpath('/problem/script'):
for script in tree.findall('.//script'):
code = script.text
XMLESC = {"&apos;": "'", "&quot;": '"'}
code = unescape(code,XMLESC)
try:
exec code in global_context, context
except Exception,err:
print "[courseware.capa.capa_problem.extract_context] error %s" % err
print "in doing exec of this code:",code
return context
def get_html(self):
......@@ -165,14 +224,22 @@ class LoncapaProblem(object):
if problemtree.tag in html_problem_semantics:
return
problemid = problemtree.get('id') # my ID
# used to be
# if problemtree.tag in html_special_response:
if hasattr(inputtypes, problemtree.tag):
# status is currently the answer for the problem ID for the input element,
# but it will turn into a dict containing both the answer and any associated message
# for the problem ID for the input element.
status = "unsubmitted"
if problemtree.get('id') in self.correct_map:
if problemid in self.correct_map:
status = self.correct_map[problemtree.get('id')]
value = ""
if self.student_answers and problemtree.get('id') in self.student_answers:
value = self.student_answers[problemtree.get('id')]
if self.student_answers and problemid in self.student_answers:
value = self.student_answers[problemid]
return getattr(inputtypes, problemtree.tag)(problemtree, value, status) #TODO
......@@ -203,7 +270,8 @@ class LoncapaProblem(object):
return [tree]
def preprocess_problem(self, tree, correct_map=dict(), answer_map=dict()): # private
''' Assign IDs to all the responses
'''
Assign IDs to all the responses
Assign sub-IDs to all entries (textline, schematic, etc.)
Annoted correctness and value
In-place transformation
......@@ -217,13 +285,20 @@ class LoncapaProblem(object):
response.attrib['state'] = correct
response_id = response_id + 1
answer_id = 1
for entry in tree.xpath("|".join(['//'+response.tag+'[@id=$id]//'+x for x in entry_types]),
for entry in tree.xpath("|".join(['//'+response.tag+'[@id=$id]//'+x for x in (entry_types + solution_types)]),
id=response_id_str):
entry.attrib['response_id'] = str(response_id)
entry.attrib['answer_id'] = str(answer_id)
entry.attrib['id'] = "%s_%i_%i"%(self.problem_id, response_id, answer_id)
answer_id=answer_id+1
# <solution>...</solution> may not be associated with any specific response; give IDs for those separately
# TODO: We should make the namespaces consistent and unique (e.g. %s_problem_%i).
solution_id = 1
for solution in tree.findall('.//solution'):
solution.attrib['id'] = "%s_solution_%i"%(self.problem_id, solution_id)
solution_id += 1
def extract_problems(self, problem_tree):
''' Remove layout from the problem, and give a purified XML tree of just the problems '''
problem_tree=copy.deepcopy(problem_tree)
......
#
# File: courseware/capa/responsetypes.py
#
'''
Problem response evaluation. Handles checking of student responses, of a variety of types.
Used by capa_problem.py
'''
# standard library imports
import json
import math
import numbers
import numpy
import random
import re
import requests
import scipy
import traceback
import copy
import abc
# specific library imports
from calc import evaluator, UndefinedVariable
from django.conf import settings
from util import contextualize_text
from lxml import etree
from lxml.etree import Element
from lxml.html.soupparser import fromstring as fromstring_bs # uses Beautiful Soup!!! FIXME?
# local imports
import calc
import eia
# TODO: Should be the same object as in capa_problem
global_context={'random':random,
'numpy':numpy,
'math':math,
'scipy':scipy,
'calc':calc,
'eia':eia}
def compare_with_tolerance(v1, v2, tol):
''' Compare v1 to v2 with maximum tolerance tol
......@@ -56,6 +63,21 @@ class GenericResponse(object):
#Every response type needs methods "grade" and "get_answers"
class MultipleChoiceResponse(GenericResponse):
'''
Example:
<multiplechoiceresponse direction="vertical" randomize="yes">
<choicegroup type="MultipleChoice">
<choice location="random" name="1" correct="false"><span>`a+b`<br/></span></choice>
<choice location="random" name="2" correct="true"><span><math>a+b^2</math><br/></span></choice>
<choice location="random" name="3" correct="false"><math>a+b+c</math></choice>
<choice location="bottom" name="4" correct="false"><math>a+b+d</math></choice>
</choicegroup>
</multiplechoiceresponse>
TODO: handle direction and randomize
'''
def __init__(self, xml, context):
self.xml = xml
self.correct_choices = xml.xpath('//*[@id=$id]//choice[@correct="true"]',
......@@ -115,11 +137,17 @@ class NumericalResponse(GenericResponse):
def __init__(self, xml, context):
self.xml = xml
self.correct_answer = contextualize_text(xml.get('answer'), context)
self.tolerance_xml = xml.xpath('//*[@id=$id]//responseparam[@type="tolerance"]/@default',
id=xml.get('id'))[0]
self.tolerance = contextualize_text(self.tolerance_xml, context)
self.answer_id = xml.xpath('//*[@id=$id]//textline/@id',
id=xml.get('id'))[0]
try:
self.tolerance_xml = xml.xpath('//*[@id=$id]//responseparam[@type="tolerance"]/@default',
id=xml.get('id'))[0]
self.tolerance = contextualize_text(self.tolerance_xml, context)
except Exception,err:
self.tolerance = 0
try:
self.answer_id = xml.xpath('//*[@id=$id]//textline/@id',
id=xml.get('id'))[0]
except Exception, err:
self.answer_id = None
def grade(self, student_answers):
''' Display HTML for a numeric response '''
......@@ -140,7 +168,50 @@ class NumericalResponse(GenericResponse):
def get_answers(self):
return {self.answer_id:self.correct_answer}
#-----------------------------------------------------------------------------
class CustomResponse(GenericResponse):
'''
Custom response. The python code to be run should be in <answer>...</answer>. Example:
<customresponse>
<startouttext/>
<br/>
Suppose that \(I(t)\) rises from \(0\) to \(I_S\) at a time \(t_0 \neq 0\)
In the space provided below write an algebraic expression for \(I(t)\).
<br/>
<textline size="5" correct_answer="IS*u(t-t0)" />
<endouttext/>
<answer type="loncapa/python">
correct=['correct']
try:
r = str(submission[0])
except ValueError:
correct[0] ='incorrect'
r = '0'
if not(r=="IS*u(t-t0)"):
correct[0] ='incorrect'
</answer>
</customresponse>
Alternatively, the check function can be defined in <script>...</script> Example:
<script type="loncapa/python"><![CDATA[
def sympy_check2():
messages[0] = '%s:%s' % (submission[0],fromjs[0].replace('<','&lt;'))
#messages[0] = str(answers)
correct[0] = 'correct'
]]>
</script>
<customresponse cfn="sympy_check2" type="cs" expect="2.27E-39" dojs="math" size="30" answer="2.27E-39">
<textline size="40" dojs="math" />
<responseparam description="Numerical Tolerance" type="tolerance" default="0.00001" name="tol"/>
</customresponse>
'''
def __init__(self, xml, context):
self.xml = xml
## CRITICAL TODO: Should cover all entrytypes
......@@ -163,6 +234,10 @@ class CustomResponse(GenericResponse):
self.code = answer.text
def grade(self, student_answers):
'''
student_answers is a dict with everything from request.POST, but with the first part
of each key removed (the string before the first "_").
'''
submission = [student_answers[k] for k in sorted(self.answer_ids)]
self.context.update({'submission':submission})
exec self.code in global_context, self.context
......@@ -173,19 +248,92 @@ class CustomResponse(GenericResponse):
# be handled by capa_problem
return {}
#-----------------------------------------------------------------------------
class ExternalResponse(GenericResponse):
'''
Grade the student's input using an external server.
Typically used by coding problems.
'''
def __init__(self, xml, context):
self.xml = xml
self.answer_ids = xml.xpath('//*[@id=$id]//textbox/@id|//*[@id=$id]//textline/@id',
id=xml.get('id'))
self.context = context
answer = xml.xpath('//*[@id=$id]//answer',
id=xml.get('id'))[0]
answer_src = answer.get('src')
if answer_src != None:
self.code = open(settings.DATA_DIR+'src/'+answer_src).read()
else:
self.code = answer.text
self.tests = xml.get('answer')
def grade(self, student_answers):
submission = [student_answers[k] for k in sorted(self.answer_ids)]
self.context.update({'submission':submission})
xmlstr = etree.tostring(self.xml, pretty_print=True)
payload = {'xml': xmlstr,
### Question: Is this correct/what we want? Shouldn't this be a json.dumps?
'LONCAPA_student_response': ''.join(submission),
'LONCAPA_correct_answer': self.tests,
'processor' : self.code,
}
# call external server; TODO: get URL from settings.py
r = requests.post("http://eecs1.mit.edu:8889/pyloncapa",data=payload)
rxml = etree.fromstring(r.text) # response is XML; prase it
ad = rxml.find('awarddetail').text
admap = {'EXACT_ANS':'correct', # TODO: handle other loncapa responses
'WRONG_FORMAT': 'incorrect',
}
self.context['correct'] = ['correct']
if ad in admap:
self.context['correct'][0] = admap[ad]
# self.context['correct'] = ['correct','correct']
correct_map = dict(zip(sorted(self.answer_ids), self.context['correct']))
# TODO: separate message for each answer_id?
correct_map['msg'] = rxml.find('message').text.replace('&nbsp;','&#160;') # store message in correct_map
return correct_map
def get_answers(self):
# Since this is explicitly specified in the problem, this will
# be handled by capa_problem
return {}
class StudentInputError(Exception):
pass
#-----------------------------------------------------------------------------
class FormulaResponse(GenericResponse):
def __init__(self, xml, context):
self.xml = xml
self.correct_answer = contextualize_text(xml.get('answer'), context)
self.samples = contextualize_text(xml.get('samples'), context)
self.tolerance_xml = xml.xpath('//*[@id=$id]//responseparam[@type="tolerance"]/@default',
id=xml.get('id'))[0]
self.tolerance = contextualize_text(self.tolerance_xml, context)
self.answer_id = xml.xpath('//*[@id=$id]//textline/@id',
id=xml.get('id'))[0]
try:
self.tolerance_xml = xml.xpath('//*[@id=$id]//responseparam[@type="tolerance"]/@default',
id=xml.get('id'))[0]
self.tolerance = contextualize_text(self.tolerance_xml, context)
except Exception,err:
self.tolerance = 0
try:
self.answer_id = xml.xpath('//*[@id=$id]//textline/@id',
id=xml.get('id'))[0]
except Exception, err:
self.answer_id = None
raise Exception, "[courseware.capa.responsetypes.FormulaResponse] Error: missing answer_id!!"
self.context = context
ts = xml.get('type')
if ts == None:
......@@ -211,7 +359,7 @@ class FormulaResponse(GenericResponse):
for i in range(numsamples):
instructor_variables = self.strip_dict(dict(self.context))
student_variables = dict()
for var in ranges:
for var in ranges: # ranges give numerical ranges for testing
value = random.uniform(*ranges[var])
instructor_variables[str(var)] = value
student_variables[str(var)] = value
......@@ -246,6 +394,8 @@ class FormulaResponse(GenericResponse):
def get_answers(self):
return {self.answer_id:self.correct_answer}
#-----------------------------------------------------------------------------
class SchematicResponse(GenericResponse):
def __init__(self, xml, context):
self.xml = xml
......@@ -270,3 +420,68 @@ class SchematicResponse(GenericResponse):
# Since this is explicitly specified in the problem, this will
# be handled by capa_problem
return {}
#-----------------------------------------------------------------------------
class ImageResponse(GenericResponse):
"""
Handle student response for image input: the input is a click on an image,
which produces an [x,y] coordinate pair. The click is correct if it falls
within a region specified. This region is nominally a rectangle.
Lon-CAPA requires that each <imageresponse> has a <foilgroup> inside it. That
doesn't make sense to me (Ike). Instead, let's have it such that <imageresponse>
should contain one or more <imageinput> stanzas. Each <imageinput> should specify
a rectangle, given as an attribute, defining the correct answer.
Example:
<imageresponse>
<imageinput src="image1.jpg" width="200" height="100" rectangle="(10,10)-(20,30)" />
<imageinput src="image2.jpg" width="210" height="130" rectangle="(12,12)-(40,60)" />
</imageresponse>
"""
def __init__(self, xml, context):
self.xml = xml
self.context = context
self.ielements = xml.findall('imageinput')
self.answer_ids = [ie.get('id') for ie in self.ielements]
def grade(self, student_answers):
correct_map = {}
expectedset = self.get_answers()
for aid in self.answer_ids: # loop through IDs of <imageinput> fields in our stanza
given = student_answers[aid] # this should be a string of the form '[x,y]'
# parse expected answer
# TODO: Compile regexp on file load
m = re.match('[\(\[]([0-9]+),([0-9]+)[\)\]]-[\(\[]([0-9]+),([0-9]+)[\)\]]',expectedset[aid].strip().replace(' ',''))
if not m:
msg = 'Error in problem specification! cannot parse rectangle in %s' % (etree.tostring(self.ielements[aid],
pretty_print=True))
raise Exception,'[capamodule.capa.responsetypes.imageinput] '+msg
(llx,lly,urx,ury) = [int(x) for x in m.groups()]
# parse given answer
m = re.match('\[([0-9]+),([0-9]+)]',given.strip().replace(' ',''))
if not m:
raise Exception,'[capamodule.capa.responsetypes.imageinput] error grading %s (input=%s)' % (err,aid,given)
(gx,gy) = [int(x) for x in m.groups()]
if settings.DEBUG:
print "[capamodule.capa.responsetypes.imageinput] llx,lly,urx,ury=",(llx,lly,urx,ury)
print "[capamodule.capa.responsetypes.imageinput] gx,gy=",(gx,gy)
# answer is correct if (x,y) is within the specified rectangle
if (llx <= gx <= urx) and (lly <= gy <= ury):
correct_map[aid] = 'correct'
else:
correct_map[aid] = 'incorrect'
if settings.DEBUG:
print "[capamodule.capa.responsetypes.imageinput] correct_map=",correct_map
return correct_map
def get_answers(self):
return dict([(ie.get('id'),ie.get('rectangle')) for ie in self.ielements])
'''
courseware/content_parser.py
This file interfaces between all courseware modules and the top-level course.xml file for a course.
Does some caching (to be explained).
'''
import hashlib
import logging
import os
......
<problem>
<text><p>
Two skiers are on frictionless black diamond ski slopes.
Hello</p></text>
<imageresponse max="1" loncapaid="11">
<imageinput src="/static/Physics801/Figures/Skier-conservation of energy.jpg" width="560" height="388" rectangle="(490,11)-(556,98)"/>
<text>Click on the image where the top skier will stop momentarily if the top skier starts from rest.</text>
<imageinput src="/static/Physics801/Figures/Skier-conservation of energy.jpg" width="560" height="388" rectangle="(242,202)-(296,276)"/>
<text>Click on the image where the lower skier will stop momentarily if the lower skier starts from rest.</text>
<hintgroup showoncorrect="no">
<text><p>Use conservation of energy.</p></text>
</hintgroup>
</imageresponse>
</problem>
\ No newline at end of file
.CodeMirror {
line-height: 1em;
font-family: monospace;
}
.CodeMirror-scroll {
overflow: auto;
height: 300px;
/* This is needed to prevent an IE[67] bug where the scrolled content
is visible outside of the scrolling box. */
position: relative;
outline: none;
}
.CodeMirror-gutter {
position: absolute; left: 0; top: 0;
z-index: 10;
background-color: #f7f7f7;
border-right: 1px solid #eee;
min-width: 2em;
height: 100%;
}
.CodeMirror-gutter-text {
color: #aaa;
text-align: right;
padding: .4em .2em .4em .4em;
white-space: pre !important;
}
.CodeMirror-lines {
padding: .4em;
white-space: pre;
}
.CodeMirror pre {
-moz-border-radius: 0;
-webkit-border-radius: 0;
-o-border-radius: 0;
border-radius: 0;
border-width: 0; margin: 0; padding: 0; background: transparent;
font-family: inherit;
font-size: inherit;
padding: 0; margin: 0;
white-space: pre;
word-wrap: normal;
}
.CodeMirror-wrap pre {
word-wrap: break-word;
white-space: pre-wrap;
}
.CodeMirror-wrap .CodeMirror-scroll {
overflow-x: hidden;
}
.CodeMirror textarea {
outline: none !important;
}
.CodeMirror pre.CodeMirror-cursor {
z-index: 10;
position: absolute;
visibility: hidden;
border-left: 1px solid black;
border-right:none;
width:0;
}
.CodeMirror pre.CodeMirror-cursor.CodeMirror-overwrite {}
.CodeMirror-focused pre.CodeMirror-cursor {
visibility: visible;
}
div.CodeMirror-selected { background: #d9d9d9; }
.CodeMirror-focused div.CodeMirror-selected { background: #d7d4f0; }
.CodeMirror-searching {
background: #ffa;
background: rgba(255, 255, 0, .4);
}
/* Default theme */
.cm-s-default span.cm-keyword {color: #708;}
.cm-s-default span.cm-atom {color: #219;}
.cm-s-default span.cm-number {color: #164;}
.cm-s-default span.cm-def {color: #00f;}
.cm-s-default span.cm-variable {color: black;}
.cm-s-default span.cm-variable-2 {color: #05a;}
.cm-s-default span.cm-variable-3 {color: #085;}
.cm-s-default span.cm-property {color: black;}
.cm-s-default span.cm-operator {color: black;}
.cm-s-default span.cm-comment {color: #a50;}
.cm-s-default span.cm-string {color: #a11;}
.cm-s-default span.cm-string-2 {color: #f50;}
.cm-s-default span.cm-meta {color: #555;}
.cm-s-default span.cm-error {color: #f00;}
.cm-s-default span.cm-qualifier {color: #555;}
.cm-s-default span.cm-builtin {color: #30a;}
.cm-s-default span.cm-bracket {color: #cc7;}
.cm-s-default span.cm-tag {color: #170;}
.cm-s-default span.cm-attribute {color: #00c;}
.cm-s-default span.cm-header {color: #a0a;}
.cm-s-default span.cm-quote {color: #090;}
.cm-s-default span.cm-hr {color: #999;}
.cm-s-default span.cm-link {color: #00c;}
span.cm-header, span.cm-strong {font-weight: bold;}
span.cm-em {font-style: italic;}
span.cm-emstrong {font-style: italic; font-weight: bold;}
span.cm-link {text-decoration: underline;}
div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
This source diff could not be displayed because it is too large. You can view the blob instead.
/////////////////////////////////////////////////////////////////////////////
//
// Simple image input
//
////////////////////////////////////////////////////////////////////////////////
// click on image, return coordinates
// put a dot at location of click, on imag
// window.image_input_click = function(id,event){
function image_input_click(id,event){
iidiv = document.getElementById("imageinput_"+id);
pos_x = event.offsetX?(event.offsetX):event.pageX-document.iidiv.offsetLeft;
pos_y = event.offsetY?(event.offsetY):event.pageY-document.iidiv.offsetTop;
result = "[" + pos_x + "," + pos_y + "]";
cx = (pos_x-15) +"px";
cy = (pos_y-15) +"px" ;
// alert(result);
document.getElementById("cross_"+id).style.left = cx;
document.getElementById("cross_"+id).style.top = cy;
document.getElementById("cross_"+id).style.visibility = "visible" ;
document.getElementById("input_"+id).value =result;
}
<section class="solution-span">
<span id="solution_${id}"></span>
</section>
<section class="text-input">
<textarea rows="30" cols="80" name="input_${id}" id="input_${id}">${value|h}</textarea>
<span id="answer_${id}"></span>
% if state == 'unsubmitted':
<span class="unanswered" style="display:inline-block;" id="status_${id}"></span>
% elif state == 'correct':
<span class="correct" id="status_${id}"></span>
% elif state == 'incorrect':
<span class="incorrect" id="status_${id}"></span>
% elif state == 'incomplete':
<span class="incorrect" id="status_${id}"></span>
% endif
<br/>
<span class="debug">(${state})</span>
<br/>
<span class="debug">${msg|n}</span>
<br/>
<br/>
<script>
// Note: We need to make the area follow the CodeMirror for this to
// work.
$(function(){
var cm = CodeMirror.fromTextArea(document.getElementById("input_${id}"),
{'mode':"python"});
});
</script>
<style type="text/css">
.CodeMirror {border-style: solid;
border-width: 1px;}
</style>
</section>
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