Commit fd6abc88 by Julian Arni

Incorporate review comments

parent 7fbd1a72
...@@ -504,7 +504,7 @@ class JSInput(InputTypeBase): ...@@ -504,7 +504,7 @@ class JSInput(InputTypeBase):
def _extra_context(self): def _extra_context(self):
context = { context = {
'applet_loader': '/static/js/capa/jsinput.js', 'applet_loader': '/static/js/capa/src/jsinput.js',
'saved_state': self.value 'saved_state': self.value
} }
......
...@@ -11,38 +11,15 @@ describe("A jsinput has:", function () { ...@@ -11,38 +11,15 @@ describe("A jsinput has:", function () {
}); });
}); });
describe("The ctxCall function", function() {
it("Evaluatates nested-object functions", function() {
var ctxTest = {
ctxFn : function () {
return this.name ;
}
};
var fnString = "nest.ctxFn";
var holder = {};
holder.nest = ctxTest;
var fn = _ctxCall(holder, fnString);
expect(fnString).toBe(holder.nest.ctxFn());
});
it("Throws an exception when the object does not exits", function () {
var notObj = _ctxCall("twas", "brilling");
expect(notObj).toThrow();
});
it("Throws an exception when the function does not exist", function () {
var anobj = {};
var notFn = _ctxCall("anobj", "brillig");
expect(notFn).toThrow();
});
describe("The jsinput constructor", function(){
}); var iframe1 = $(document).find('iframe')[0];
describe("The jsinput constructor", function(){
var testJsElem = jsinputConstructor({ var testJsElem = jsinputConstructor({
id: 3781, id: 1,
elem: "<div id='abc'> a div </div>", elem: iframe1,
passive: false passive: false
}); });
...@@ -51,7 +28,7 @@ describe("A jsinput has:", function () { ...@@ -51,7 +28,7 @@ describe("A jsinput has:", function () {
}); });
it("Adds the object to the jsinput array", function() { it("Adds the object to the jsinput array", function() {
expect(jsinput.jsinputarr.exists(3781)).toBe(true); expect(jsinput.exists(1)).toBe(true);
}); });
describe("The returned object", function() { describe("The returned object", function() {
...@@ -60,12 +37,34 @@ describe("A jsinput has:", function () { ...@@ -60,12 +37,34 @@ describe("A jsinput has:", function () {
expect(testJsElem.update).toBeDefined(); expect(testJsElem.update).toBeDefined();
}); });
it("Returns an 'update' that is idempotent", function(){
var orig = testJsElem.update();
for (var i = 0; i++; i < 5) {
expect(testJsElem.update()).toEqual(orig);
}
});
it("Changes the parent's inputfield", function() { it("Changes the parent's inputfield", function() {
testJsElem.update();
});
});
});
})
describe("The walkDOM functions", function() {
walkDOM();
it("Creates (at least) one object per iframe", function() {
jsinput.arr.length >= 2;
}); });
it("Does not create multiple objects with the same id", function() {
while (jsinput.arr.length > 0) {
var elem = jsinput.arr.pop();
expect(jsinput.exists(elem.id)).toBe(false);
}
});
}); });
} })
)
...@@ -2,32 +2,45 @@ ...@@ -2,32 +2,45 @@
<head> <head>
<title> JSinput jasmine test </title> <title> JSinput jasmine test </title>
</head> </head>
<body> <body>
<section id="inputtype_1" data="gradefn" class="jsinput"> <section id="inputtype_1"
data="gradefn"
data-setstate="setinput"
class="jsinput">
<div class="script_placeholder" data-src="${applet_loader}"/> <div class="script_placeholder" />
<iframe name="iframe_1" <iframe name="iframe_1"
sandbox="allow-scripts allow-popups allow-same-origin allow-forms allow-pointer-lock"
sandbox="allow-scripts
allow-popups
allow-same-origin
allow-forms
allow-pointer-lock"
seamless="seamless" seamless="seamless"
height="500" height="500"
width="500" > width="500">
<html> <html>
<head> <head>
<title> <title>
JS input test JS input test 1
</title> </title>
<script type="text/javascript"> <script type="text/javascript">
function gradefn () { function gradefn () {
var ans = document.getElementById("one").value; var ans = document.getElementById("one").value;
console.log("I've been called!"); console.log("I've been called!");
return ans return ans
} }
function setinput(val) {
document.getElementById("one").value(val);
return;
}
</script> </script>
</head> </head>
<body> <body>
<p>Simple js input test. Defines a js function that returns the value in the <p>Simple js input test. Defines a js function that returns the value in
input field below when called. </p> the input field below when called. </p>
<form> <form>
<input id='one' type="TEXT"/> <input id='one' type="TEXT"/>
...@@ -47,15 +60,18 @@ ...@@ -47,15 +60,18 @@
</p> </p>
<br/> <br/> <br/> <br/>
<div class="error_message" style="padding: 5px 5px 5px 5px; background-color:#FA6666; height:60px;width:400px; display: none"></div> <div class="error_message" ></div>
</div> </section>
</section> <section id="inputtype_2" data="gradefn" class="jsinput">
<section id="inputtype_2" data="gradefn" class="jsinput">
<div class="script_placeholder" data-src="${applet_loader}"/> <div class="script_placeholder" />
<iframe name="iframe_2" <iframe name="iframe_2"
sandbox="allow-scripts allow-popups allow-same-origin allow-forms allow-pointer-lock" sandbox="allow-scripts
allow-popups
allow-same-origin
allow-forms
allow-pointer-lock"
seamless="seamless" seamless="seamless"
height="500" height="500"
width="500" > width="500" >
...@@ -65,20 +81,20 @@ ...@@ -65,20 +81,20 @@
JS input test JS input test
</title> </title>
<script type="text/javascript"> <script type="text/javascript">
function gradefn () { function gradefn () {
var ans = document.getElementById("one").value; var ans = document.getElementById("one").value;
console.log("I've been called!"); console.log("I've been called!");
return ans return ans
} }
</script> </script>
</head> </head>
<body> <body>
<p>Simple js input test. Defines a js function that returns the value in the <p>Simple js input test. Defines a js function that returns the value in
input field below when called. </p> the input field below when called. </p>
<form> <form>
<input id='one' type="TEXT"/> <input id='two' type="TEXT"/>
</form> </form>
</body> </body>
...@@ -95,9 +111,8 @@ ...@@ -95,9 +111,8 @@
</p> </p>
<br/> <br/> <br/> <br/>
<div class="error_message" style="padding: 5px 5px 5px 5px; background-color:#FA6666; height:60px;width:400px; display: none"></div> <div class="error_message"></div>
</div> </section>
</section> </body>
</body>
</html> </html>
...@@ -12,7 +12,6 @@ ...@@ -12,7 +12,6 @@
// submitted), the constructor is called again. // submitted), the constructor is called again.
if (!jsinput) { if (!jsinput) {
console.log("hi");
jsinput = { jsinput = {
runs : 1, runs : 1,
arr : [], arr : [],
...@@ -27,29 +26,9 @@ ...@@ -27,29 +26,9 @@
jsinput.runs++; jsinput.runs++;
if ($(document).find('section[class="jsinput"]').length > jsinput.runs) {
return;
}
/* Utils */ /* Utils */
jsinput._DEBUG = jsinput._DEBUG || true;
var debuglog = function(text) { if (jsinput._DEBUG) { console.log(text);}};
var eqTimeout = function(fn, pred, time, max) {
var i = 0;
while (pred(fn) && i < max) {
setTimeout(fn, time);
}
return fn;
};
var isUndef = function (e) { return (typeof(e) === 'undefined'); };
// Take a string and find the nested object that corresponds to it. E.g.: // Take a string and find the nested object that corresponds to it. E.g.:
// deepKey(obj, "an.example") -> obj["an"]["example"] // deepKey(obj, "an.example") -> obj["an"]["example"]
var _deepKey = function(obj, path){ var _deepKey = function(obj, path){
...@@ -76,65 +55,51 @@ ...@@ -76,65 +55,51 @@
/* Private methods */ /* Private methods */
var sect = $(spec.elem).parent().find('section[class="jsinput"]'); var sect = $(spec.elem).parent().find('section[class="jsinput"]');
var sectattr = function (e) { return $(sect).attr(e); }; var sectAttr = function (e) { return $(sect).attr(e); };
var thisIFrame = $(spec.elem). var thisIFrame = $(spec.elem).
find('iframe[name^="iframe_"]'). find('iframe[name^="iframe_"]').
get(0); get(0);
var cWindow = thisIFrame.contentWindow; var cWindow = thisIFrame.contentWindow;
// Get the hidden input field to pass to customresponse // Get the hidden input field to pass to customresponse
function _inputfield() { function _inputField() {
var parent = $(spec.elem).parent(); var parent = $(spec.elem).parent();
return parent.find('input[id^="input_"]'); return parent.find('input[id^="input_"]');
} }
var inputfield = _inputfield(); var inputField = _inputField();
// Get the grade function name // Get the grade function name
var getgradefn = sectattr("data"); var getGradeFn = sectAttr("data");
// Get state getter // Get state getter
var getgetstate = sectattr("data-getstate"); var getStateGetter = sectAttr("data-getstate");
// Get state setter // Get state setter
var getsetstate = sectattr("data-setstate"); var getStateSetter = sectAttr("data-setstate");
// Get stored state // Get stored state
var getstoredstate = sectattr("data-stored"); var getStoredState = sectAttr("data-stored");
// Put the return value of gradefn in the hidden inputfield.
// If passed an argument, does not call gradefn, and instead directly
// updates the inputfield with the passed value.
var update = function (answer) {
// Put the return value of gradeFn in the hidden inputField.
var update = function () {
var ans; var ans;
ans = _deepKey(cWindow, gradefn);
ans = _deepKey(cWindow, gradeFn)();
// setstate presumes getstate, so don't getstate unless setstate is // setstate presumes getstate, so don't getstate unless setstate is
// defined. // defined.
if (getgetstate && getsetstate) { if (getStateGetter && getStateSetter) {
var state, store; var state, store;
state = _deepKey(cWindow, getgetstate); state = unescape(_deepKey(cWindow, getStateGetter)());
store = { store = {
answer: ans, answer: ans,
state: state state: state
}; };
inputField.val(JSON.stringify(store));
debuglog("Store: " + store);
inputfield.val(JSON.stringify(store));
} else { } else {
inputfield.val(ans); inputField.val(ans);
debuglog("Answer: " + ans);
} }
return; return;
}; };
// Find the update button, and bind the update function to its click
// event.
function bindUpdate() {
var updatebutton = $(spec.elem).
find('button[class="update"]').
get(0);
$(updatebutton).click(update);
}
/* Public methods */ /* Public methods */
that.update = update; that.update = update;
...@@ -145,53 +110,53 @@ ...@@ -145,53 +110,53 @@
jsinput.arr.push(that); jsinput.arr.push(that);
// Put the update function as the value of the inputfield's "waitfor" // Put the update function as the value of the inputField's "waitfor"
// attribute so that it is called when the check button is clicked. // attribute so that it is called when the check button is clicked.
function bindCheck() { function bindCheck() {
debuglog("Update function: " + that.update); inputField.data('waitfor', that.update);
inputfield.data('waitfor', that.update);
return; return;
} }
var gradefn = getgradefn; var gradeFn = getGradeFn;
debuglog("Gradefn: " + gradefn);
if (spec.passive === false) {
// If there is a separate "Update" button, bind update to it.
bindUpdate();
} else {
// Otherwise, bind update to the check button.
bindCheck();
}
bindCheck(); bindCheck();
// Check whether application takes in state and there is a saved // Check whether application takes in state and there is a saved
// state to give it. If getsetstate is specified but calling it // state to give it. If getStateSetter is specified but calling it
// fails, wait and try again, since the iframe might still be // fails, wait and try again, since the iframe might still be
// loading. // loading.
if (getsetstate && getstoredstate) { if (getStateSetter && getStoredState) {
var sval; var sval, jsonVal;
if (typeof(getstoredstate) === "object") {
sval = getstoredstate["state"]; try {
jsonVal = JSON.parse(getStoredState);
} catch (err) {
jsonVal = getStoredState;
}
if (typeof(jsonVal) === "object") {
sval = jsonVal["state"];
} else { } else {
sval = getstoredstate; sval = jsonVal;
} }
debuglog("Stored state: "+ sval);
debuglog("Set_statefn: " + getsetstate);
// Try calling setstate every 200ms while it throws an exception,
// up to five times; give up after that.
// (Functions in the iframe may not be ready when we first try
// calling it, but might just need more time. Give the functions
// more time.)
function whileloop(n) { function whileloop(n) {
if (n < 10){ if (n < 5){
try { try {
_deepKey(cWindow, getsetstate)(sval); _deepKey(cWindow, getStateSetter)(sval);
} catch (err) { } catch (err) {
setTimeout(whileloop(n+1), 200); setTimeout(whileloop(n+1), 200);
} }
} }
else { else {
debuglog("Error: could not set state"); console.debug("Error: could not set state");
_deepKey(cWindow, getsetstate)(sval);
} }
} }
whileloop(0); whileloop(0);
...@@ -218,7 +183,6 @@ ...@@ -218,7 +183,6 @@
var newJsElem = jsinputConstructor({ var newJsElem = jsinputConstructor({
id: newid, id: newid,
elem: value, elem: value,
passive: true
}); });
} }
}); });
...@@ -227,9 +191,9 @@ ...@@ -227,9 +191,9 @@
// This is ugly, but without a timeout pages with multiple/heavy jsinputs // This is ugly, but without a timeout pages with multiple/heavy jsinputs
// don't load properly. // don't load properly.
if ($.isReady) { if ($.isReady) {
setTimeout(walkDOM, 1000); setTimeout(walkDOM, 300);
} else { } else {
$(document).ready(setTimeout(walkDOM, 1000)); $(document).ready(setTimeout(walkDOM, 300));
} }
})(window.jsinput = window.jsinput || false); })(window.jsinput = window.jsinput || false);
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