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,102 +2,117 @@ ...@@ -2,102 +2,117 @@
<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"
<div class="script_placeholder" data-src="${applet_loader}"/> data-setstate="setinput"
<iframe name="iframe_1" class="jsinput">
sandbox="allow-scripts allow-popups allow-same-origin allow-forms allow-pointer-lock"
seamless="seamless" <div class="script_placeholder" />
height="500" <iframe name="iframe_1"
width="500" >
<html> sandbox="allow-scripts
<head> allow-popups
<title> allow-same-origin
JS input test allow-forms
</title> allow-pointer-lock"
<script type="text/javascript"> seamless="seamless"
function gradefn () { height="500"
var ans = document.getElementById("one").value; width="500">
console.log("I've been called!"); <html>
return ans <head>
} <title>
</script> JS input test 1
</head> </title>
<body> <script type="text/javascript">
function gradefn () {
<p>Simple js input test. Defines a js function that returns the value in the var ans = document.getElementById("one").value;
input field below when called. </p> console.log("I've been called!");
return ans
<form> }
<input id='one' type="TEXT"/>
</form> function setinput(val) {
document.getElementById("one").value(val);
</body> return;
</html> }
</script>
</iframe> </head>
<input type="hidden" name="input_1" id="input_1" value="${value|h}"/> <body>
<br/> <p>Simple js input test. Defines a js function that returns the value in
<button id="update_1" class="update">Update</button> the input field below when called. </p>
<p id="answer_1" class="answer"></p>
<form>
<p class="status"> <input id='one' type="TEXT"/>
</p> </form>
<br/> <br/>
</body>
<div class="error_message" style="padding: 5px 5px 5px 5px; background-color:#FA6666; height:60px;width:400px; display: none"></div> </html>
</div> </iframe>
</section> <input type="hidden" name="input_1" id="input_1" value="${value|h}"/>
<section id="inputtype_2" data="gradefn" class="jsinput">
<br/>
<div class="script_placeholder" data-src="${applet_loader}"/> <button id="update_1" class="update">Update</button>
<iframe name="iframe_2" <p id="answer_1" class="answer"></p>
sandbox="allow-scripts allow-popups allow-same-origin allow-forms allow-pointer-lock"
seamless="seamless" <p class="status">
height="500" </p>
width="500" > <br/> <br/>
<html>
<head> <div class="error_message" ></div>
<title>
JS input test </section>
</title> <section id="inputtype_2" data="gradefn" class="jsinput">
<script type="text/javascript">
function gradefn () { <div class="script_placeholder" />
var ans = document.getElementById("one").value; <iframe name="iframe_2"
console.log("I've been called!"); sandbox="allow-scripts
return ans allow-popups
} allow-same-origin
</script> allow-forms
</head> allow-pointer-lock"
<body> seamless="seamless"
height="500"
<p>Simple js input test. Defines a js function that returns the value in the width="500" >
input field below when called. </p> <html>
<head>
<form> <title>
<input id='one' type="TEXT"/> JS input test
</form> </title>
<script type="text/javascript">
</body> function gradefn () {
</html> var ans = document.getElementById("one").value;
console.log("I've been called!");
</iframe> return ans
<input type="hidden" name="input_2" id="input_2" value="${value|h}"/> }
</script>
<br/> </head>
<button id="update_2" class="update">Update</button> <body>
<p id="answer_2" class="answer"></p>
<p>Simple js input test. Defines a js function that returns the value in
<p class="status"> the input field below when called. </p>
</p>
<br/> <br/> <form>
<input id='two' type="TEXT"/>
<div class="error_message" style="padding: 5px 5px 5px 5px; background-color:#FA6666; height:60px;width:400px; display: none"></div> </form>
</div> </body>
</section> </html>
</body>
</iframe>
<input type="hidden" name="input_2" id="input_2" value="${value|h}"/>
<br/>
<button id="update_2" class="update">Update</button>
<p id="answer_2" class="answer"></p>
<p class="status">
</p>
<br/> <br/>
<div class="error_message"></div>
</section>
</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. // Put the return value of gradeFn in the hidden inputField.
// If passed an argument, does not call gradefn, and instead directly var update = function () {
// updates the inputfield with the passed value.
var update = function (answer) {
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