diff --git a/common/static/js/capa/jsinput.js b/common/static/js/capa/jsinput.js index 5eb1d3e360..6b7cac2429 100644 --- a/common/static/js/capa/jsinput.js +++ b/common/static/js/capa/jsinput.js @@ -4,6 +4,29 @@ // most relevantly, jquery). Keep in mind what happens in which context // when modifying this file. + + // _deepKey and _ctxCall are helper functions used to ensure that gradefn + // etc. can be nested objects (e.g., "firepad.getText") and that when + // called they receive the appropriate objects as "this" (e.g., "firepad"). + + // Take a string and find the nested object that corresponds to it. E.g.: + // deepKey(obj, "an.example") -> obj["an"]["example"] + var _deepKey = function(obj, path){ + for (var i = 0, path=path.split('.'), len = path.length; i < len; i++){ + obj = obj[path[i]]; + } + return obj; + }; + + var _ctxCall = function(obj, fn) { + var func = _deepKey(obj, fn); + var oldthis = fn.split('.'); + oldthis.pop(); + oldthis = oldthis.join(); + var newthis = _deepKey(obj, oldthis); + return func.apply(newthis); + }; + // First time this function was called? var isFirst = typeof(jsinput.jsinputarr) != 'undefined'; @@ -70,12 +93,12 @@ var update = function (answer) { var ans; - ans = cWindow[gradefn](); + ans = _ctxCall(cWindow, gradefn); // setstate presumes getstate, so don't getstate unless setstate is // defined. if (getgetstate() && getsetstate()) { var state, store; - state = cWindow[getgetstate()](); + state = _ctxCall(cWindow, getgetstate()); store = { answer: ans, state: state @@ -131,7 +154,7 @@ function whileloop(n) { if (n < 10){ try { - cWindow[getsetstate()](sval); + _ctxCall(cWindow, getsetstate(), sval); } catch (err) { setTimeout(whileloop(n+1), 200); } diff --git a/doc/public/course_data_formats/jsinput.rst b/doc/public/course_data_formats/jsinput.rst index 008940e3b7..5cf043a3ce 100644 --- a/doc/public/course_data_formats/jsinput.rst +++ b/doc/public/course_data_formats/jsinput.rst @@ -66,11 +66,14 @@ should be located in the content directory. The iframe is created using the sandbox attribute; while popups, scripts, and pointer locks are allowed, the iframe cannot access its parent's attributes. -The html file should contain a top-level function for the gradefn function. To -check whether the gradefn will be accessible to JSInput, check that, in the -console,:: - window["`gradefn`"] -Returns the right thing. +The html file should contain an accesible gradefn function. To check whether +the gradefn will be accessible to JSInput, check that, in the console,:: + "`gradefn" +Returns the right thing. When used by JSInput, `gradefn` is called with:: + `gradefn`.call(`obj`) +Where `obj` is the object-part of `gradefn`. For example, if `gradefn` is +`myprog.myfn`, JSInput will call `myprog.myfun.call(myprog)`. (This is to +ensure "`this`" continues to refer to what `gradefn` expects.) Aside from that, more or less anything goes. Note that currently there is no support for inheriting css or javascript from the parent (aside from the