Files
edx-platform/common/static/js/capa/src/jsinput.js
2013-07-08 14:32:02 -04:00

200 lines
6.0 KiB
JavaScript

(function (jsinput, undefined) {
// Initialize js inputs on current page.
// N.B.: No library assumptions about the iframe can be made (including,
// most relevantly, jquery). Keep in mind what happens in which context
// when modifying this file.
/* Check whether there is anything to be done */
// When all the problems are first loaded, we want to make sure the
// constructor only runs once for each iframe; but we also want to make
// sure that if part of the page is reloaded (e.g., a problem is
// submitted), the constructor is called again.
if (!jsinput) {
jsinput = {
runs : 1,
arr : [],
exists : function(id) {
jsinput.arr.filter(function(e, i, a) {
return e.id = id;
});
}
};
}
jsinput.runs++;
/* Utils */
// 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, p=path.split('.'), len = p.length; i < len; i++){
obj = obj[p[i]];
}
return obj;
};
/* END Utils */
function jsinputConstructor(spec) {
// Define an class that will be instantiated for each jsinput element
// of the DOM
// 'that' is the object returned by the constructor. It has a single
// public method, "update", which updates the hidden input field.
var that = {};
/* Private methods */
var sect = $(spec.elem).parent().find('section[class="jsinput"]');
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() {
var parent = $(spec.elem).parent();
return parent.find('input[id^="input_"]');
}
var inputField = _inputField();
// Get the grade function name
var getGradeFn = sectAttr("data");
// Get state getter
var getStateGetter = sectAttr("data-getstate");
// Get state setter
var getStateSetter = sectAttr("data-setstate");
// Get stored state
var getStoredState = sectAttr("data-stored");
// Put the return value of gradeFn in the hidden inputField.
var update = function () {
var ans;
ans = _deepKey(cWindow, gradeFn)();
// setstate presumes getstate, so don't getstate unless setstate is
// defined.
if (getStateGetter && getStateSetter) {
var state, store;
state = unescape(_deepKey(cWindow, getStateGetter)());
store = {
answer: ans,
state: state
};
inputField.val(JSON.stringify(store));
} else {
inputField.val(ans);
}
return;
};
/* Public methods */
that.update = update;
/* Initialization */
jsinput.arr.push(that);
// 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() {
inputField.data('waitfor', that.update);
return;
}
var gradeFn = getGradeFn;
bindCheck();
// Check whether application takes in state and there is a saved
// state to give it. If getStateSetter is specified but calling it
// fails, wait and try again, since the iframe might still be
// loading.
if (getStateSetter && getStoredState) {
var sval, jsonVal;
try {
jsonVal = JSON.parse(getStoredState);
} catch (err) {
jsonVal = getStoredState;
}
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 < 5){
try {
_deepKey(cWindow, getStateSetter)(sval);
} catch (err) {
setTimeout(whileloop(n+1), 200);
}
}
else {
console.debug("Error: could not set state");
}
}
whileloop(0);
}
return that;
}
function walkDOM() {
var newid;
// Find all jsinput elements, and create a jsinput object for each one
var all = $(document).find('section[class="jsinput"]');
all.each(function(index, value) {
// Get just the mako variable 'id' from the id attribute
newid = $(value).attr("id").replace(/^inputtype_/, "");
if (!jsinput.exists(newid)){
var newJsElem = jsinputConstructor({
id: newid,
elem: value,
});
}
});
}
// This is ugly, but without a timeout pages with multiple/heavy jsinputs
// don't load properly.
if ($.isReady) {
setTimeout(walkDOM, 300);
} else {
$(document).ready(setTimeout(walkDOM, 300));
}
})(window.jsinput = window.jsinput || false);