From fd6abc88e2eff689e612b949b3c0da691951f76a Mon Sep 17 00:00:00 2001 From: Julian Arni Date: Mon, 8 Jul 2013 14:32:02 -0400 Subject: [PATCH] Incorporate review comments --- common/lib/capa/capa/inputtypes.py | 2 +- common/static/js/capa/spec/jsinput/jsinput.js | 67 +++--- .../js/capa/spec/jsinput/mainfixture.html | 211 ++++++++++-------- common/static/js/capa/{ => src}/jsinput.js | 120 ++++------ 4 files changed, 189 insertions(+), 211 deletions(-) rename common/static/js/capa/{ => src}/jsinput.js (59%) diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index d7204e516c..9bb72ad4e1 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -504,7 +504,7 @@ class JSInput(InputTypeBase): def _extra_context(self): context = { - 'applet_loader': '/static/js/capa/jsinput.js', + 'applet_loader': '/static/js/capa/src/jsinput.js', 'saved_state': self.value } diff --git a/common/static/js/capa/spec/jsinput/jsinput.js b/common/static/js/capa/spec/jsinput/jsinput.js index 85b33d9b4e..252bc4df54 100644 --- a/common/static/js/capa/spec/jsinput/jsinput.js +++ b/common/static/js/capa/spec/jsinput/jsinput.js @@ -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]; + var testJsElem = jsinputConstructor({ - id: 3781, - elem: "
a div
", + id: 1, + elem: iframe1, passive: false }); @@ -51,7 +28,7 @@ describe("A jsinput has:", 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() { @@ -60,12 +37,34 @@ describe("A jsinput has:", function () { expect(testJsElem.update).toBeDefined(); }); - it("Changes the parent's inputfield", function() { - - }) + 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() { + 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); + } + }); }); -} - ) +}) diff --git a/common/static/js/capa/spec/jsinput/mainfixture.html b/common/static/js/capa/spec/jsinput/mainfixture.html index accf4997e1..c43b027c70 100644 --- a/common/static/js/capa/spec/jsinput/mainfixture.html +++ b/common/static/js/capa/spec/jsinput/mainfixture.html @@ -2,102 +2,117 @@ JSinput jasmine test - -
- -
- - - -
- -

- -

-

-

- - - -
-
-
- -
- - - -
- -

- -

-

-

- - - -
-
- + +
+ +
+ + + +
+ +

+ +

+

+

+ +
+ +
+
+ +
+ + + +
+ +

+ +

+

+

+ +
+ +
+ diff --git a/common/static/js/capa/jsinput.js b/common/static/js/capa/src/jsinput.js similarity index 59% rename from common/static/js/capa/jsinput.js rename to common/static/js/capa/src/jsinput.js index b39ddc6183..9d3fde32fc 100644 --- a/common/static/js/capa/jsinput.js +++ b/common/static/js/capa/src/jsinput.js @@ -12,7 +12,6 @@ // submitted), the constructor is called again. if (!jsinput) { - console.log("hi"); jsinput = { runs : 1, arr : [], @@ -27,29 +26,9 @@ jsinput.runs++; - if ($(document).find('section[class="jsinput"]').length > jsinput.runs) { - return; - } - - /* 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.: // deepKey(obj, "an.example") -> obj["an"]["example"] var _deepKey = function(obj, path){ @@ -76,65 +55,51 @@ /* Private methods */ 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). find('iframe[name^="iframe_"]'). get(0); var cWindow = thisIFrame.contentWindow; // Get the hidden input field to pass to customresponse - function _inputfield() { + function _inputField() { var parent = $(spec.elem).parent(); return parent.find('input[id^="input_"]'); } - var inputfield = _inputfield(); + var inputField = _inputField(); // Get the grade function name - var getgradefn = sectattr("data"); + var getGradeFn = sectAttr("data"); // Get state getter - var getgetstate = sectattr("data-getstate"); + var getStateGetter = sectAttr("data-getstate"); // Get state setter - var getsetstate = sectattr("data-setstate"); + var getStateSetter = sectAttr("data-setstate"); // 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; - ans = _deepKey(cWindow, gradefn); + + ans = _deepKey(cWindow, gradeFn)(); // setstate presumes getstate, so don't getstate unless setstate is // defined. - if (getgetstate && getsetstate) { + if (getStateGetter && getStateSetter) { var state, store; - state = _deepKey(cWindow, getgetstate); + state = unescape(_deepKey(cWindow, getStateGetter)()); store = { answer: ans, state: state }; - - debuglog("Store: " + store); - inputfield.val(JSON.stringify(store)); + inputField.val(JSON.stringify(store)); } else { - inputfield.val(ans); - debuglog("Answer: " + ans); + inputField.val(ans); } 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 */ that.update = update; @@ -145,53 +110,53 @@ 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. function bindCheck() { - debuglog("Update function: " + that.update); - inputfield.data('waitfor', that.update); + inputField.data('waitfor', that.update); return; } - var gradefn = getgradefn; - debuglog("Gradefn: " + gradefn); + var gradeFn = getGradeFn; - 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(); // 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 // loading. - if (getsetstate && getstoredstate) { - var sval; - if (typeof(getstoredstate) === "object") { - sval = getstoredstate["state"]; - } else { - sval = getstoredstate; + if (getStateSetter && getStoredState) { + var sval, jsonVal; + + try { + jsonVal = JSON.parse(getStoredState); + } catch (err) { + jsonVal = getStoredState; } - debuglog("Stored state: "+ sval); - debuglog("Set_statefn: " + getsetstate); + if (typeof(jsonVal) === "object") { + sval = jsonVal["state"]; + } else { + sval = jsonVal; + } + + // 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) { - if (n < 10){ + if (n < 5){ try { - _deepKey(cWindow, getsetstate)(sval); + _deepKey(cWindow, getStateSetter)(sval); } catch (err) { setTimeout(whileloop(n+1), 200); } } else { - debuglog("Error: could not set state"); - _deepKey(cWindow, getsetstate)(sval); + console.debug("Error: could not set state"); } } whileloop(0); @@ -218,7 +183,6 @@ var newJsElem = jsinputConstructor({ id: newid, elem: value, - passive: true }); } }); @@ -227,9 +191,9 @@ // This is ugly, but without a timeout pages with multiple/heavy jsinputs // don't load properly. if ($.isReady) { - setTimeout(walkDOM, 1000); + setTimeout(walkDOM, 300); } else { - $(document).ready(setTimeout(walkDOM, 1000)); + $(document).ready(setTimeout(walkDOM, 300)); } })(window.jsinput = window.jsinput || false);