(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. // First time this function was called? var isFirst = typeof(jsinput.jsinputarr) != 'undefined'; // Use this array to keep track of the elements that have already been // initialized. jsinput.jsinputarr = jsinput.jsinputarr || []; if (isFirst) { jsinput.jsinputarr.exists = function (id) { this.filter(function(e, i, a) { return e.id = id; }); }; } 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"]'); // Get the hidden input field to pass to customresponse function inputfield() { var parent = $(spec.elem).parent(); return parent.find('input[id^="input_"]'); } // Get the grade function name function getgradefn() { return $(sect).attr("data"); } // Get state getter function getgetstate() { return $(sect).attr("data-getstate"); } // Get state setter function getsetstate() { var gss = $(sect).attr("data-setstate"); return gss; } // Get stored state function getstoredstate() { return $(sect).attr("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) { var ans; ans = $(spec.elem). find('iframe[name^="iframe_"]'). get(0). // jquery might not be available in the iframe contentWindow[gradefn](); // setstate presumes getstate, so don't getstate unless setstate is // defined. if (getgetstate() && getsetstate()) { var state, store; state = $(spec.elem). find('iframe[name^="iframe_"]'). get(0). contentWindow[getgetstate()](); store = { answer: ans, state: state }; inputfield().val(JSON.stringify(store)); } else { inputfield().val(ans); } return; }; // Find the update button, and bind the update function to its click // event. function updateHandler() { var updatebutton = $(spec.elem). find('button[class="update"]').get(0); $(updatebutton).click(update); } /* Public methods */ that.update = update; /* Initialization */ jsinput.jsinputarr.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(); if (spec.passive === false) { updateHandler(); bindCheck(); // Check whether application takes in state and there is a saved // state to give it if (getsetstate() && getstoredstate()) { console.log("Using stored state..."); var sval; if (typeof(getstoredstate()) === "object") { sval = getstoredstate()["state"]; } else { sval = getstoredstate(); } $(spec.elem). find('iframe[name^="iframe_"]'). get(0). contentWindow[getsetstate()](sval); } } else { // NOT CURRENTLY SUPPORTED // If set up to passively receive updates (intercept a function's // return value whenever the function is called) add an event // listener that listens to messages that match "that"'s id. // Decorate the iframe gradefn with updateDecorator. iframe.contentWindow[gradefn] = updateDecorator(iframe.contentWindow[gradefn]); iframe.contentWindow.addEventListener('message', function (e) { var id = e.data[0], msg = e.data[1]; if (id === spec.id) { update(msg); } }); } return that; } function updateDecorator(fn, id) { // NOT CURRENTLY SUPPORTED // Simple function decorator that posts the output of a function to the // parent iframe before returning the original function's value. // Can be used to decorate one or more gradefn (instead of using an // explicit "Update" button) when gradefn is automatically called as part // of an application's natural behavior. // The id argument is used to specify which of the instances of jsinput on // the parent page the message is being posted to. return function () { var result = fn.apply(null, arguments); window.parent.contentWindow.postMessage([id, result], document.referrer); return result; }; } function walkDOM() { // Find all jsinput elements, and create a jsinput object for each one var all = $(document).find('section[class="jsinput"]'); var newid; all.each(function() { // Get just the mako variable 'id' from the id attribute newid = $(this).attr("id").replace(/^inputtype_/, ""); var newJsElem = jsinputConstructor({ id: newid, elem: this, passive: false }); }); } // TODO: Inject css into, and retrieve frame size from, the iframe (for non // "seamless"-supporting browsers). //var iframeInjection = { //injectStyles : function (style) { //$(document.body).css(style); //}, //sendMySize : function () { //var height = html.height, //width = html.width; //window.parent.postMessage(['height', height], '*'); //window.parent.postMessage(['width', width], '*'); //} //}; setTimeout(walkDOM, 200); })(window.jsinput = window.jsinput || {})