Commit 74bb976e by Julian Arni

Abort submission and alter user if gradefn throws an exception

parent bc25defd
...@@ -129,6 +129,30 @@ class @Problem ...@@ -129,6 +129,30 @@ class @Problem
if setupMethod? if setupMethod?
@inputtypeDisplays[id] = setupMethod(inputtype) @inputtypeDisplays[id] = setupMethod(inputtype)
# If some function wants to be called before sending the answer to the
# server, give it a chance to do so.
#
# check_waitfor allows the callee to send alerts if the user's input is
# invalid. To do so, the callee must throw an exception named "Waitfor
# Exception". This and any other errors or exceptions that arise from the
# callee are rethrown and abort the submission.
#
# In order to use this feature, add a 'data-waitfor' attribute to the input,
# and specify the function to be called by the check button before sending
# off @answers
check_waitfor: =>
for inp in @inputs
if not ($(inp).attr("data-waitfor")?)
try
$(inp).data("waitfor")()
catch e
if e.name == "Waitfor Exception"
alert e.message
else
alert "Could not grade your answer. The submission was aborted."
throw e
@refreshAnswers()
### ###
# 'check_fd' uses FormData to allow file submissions in the 'problem_check' dispatch, # 'check_fd' uses FormData to allow file submissions in the 'problem_check' dispatch,
...@@ -140,11 +164,7 @@ class @Problem ...@@ -140,11 +164,7 @@ class @Problem
check_fd: => check_fd: =>
Logger.log 'problem_check', @answers Logger.log 'problem_check', @answers
# If some function wants to be called before sending the answer to the
# server, give it a chance to do so.
if $('input[waitfor]').length != 0
($(lcall).data("waitfor").call() for lcall in $('input[waitfor]'))
@refreshAnswers()
# If there are no file inputs in the problem, we can fall back on @check # If there are no file inputs in the problem, we can fall back on @check
if $('input:file').length == 0 if $('input:file').length == 0
@check() @check()
...@@ -217,6 +237,7 @@ class @Problem ...@@ -217,6 +237,7 @@ class @Problem
$.ajaxWithPrefix("#{@url}/problem_check", settings) $.ajaxWithPrefix("#{@url}/problem_check", settings)
check: => check: =>
@check_waitfor()
Logger.log 'problem_check', @answers Logger.log 'problem_check', @answers
$.postWithPrefix "#{@url}/problem_check", @answers, (response) => $.postWithPrefix "#{@url}/problem_check", @answers, (response) =>
switch response.success switch response.success
......
...@@ -10,16 +10,15 @@ ...@@ -10,16 +10,15 @@
// Use this array to keep track of the elements that have already been // Use this array to keep track of the elements that have already been
// initialized. // initialized.
jsinput.jsinputarr = jsinput.jsinputarr || []; jsinput.jsinputarr = jsinput.jsinputarr || [];
if (isFirst) { jsinput.jsinputarr.exists = function (id) {
jsinput.jsinputarr.exists = function (id) { this.filter(function(e, i, a) {
this.filter(function(e, i, a) { return e.id = id;
return e.id = id; });
}); };
};
}
function jsinputConstructor(spec) { function jsinputConstructor(spec) {
// Define an class that will be instantiated for each.jsinput element // Define an class that will be instantiated for each jsinput element
// of the DOM // of the DOM
// 'that' is the object returned by the constructor. It has a single // 'that' is the object returned by the constructor. It has a single
...@@ -35,6 +34,11 @@ ...@@ -35,6 +34,11 @@
return parent.find('input[id^="input_"]'); return parent.find('input[id^="input_"]');
} }
// For the state and grade functions below, use functions instead of
// storing their return values since we might need to call them
// repeatedly, and they might change (e.g., they might not be defined
// when we first try calling them).
// Get the grade function name // Get the grade function name
function getgradefn() { function getgradefn() {
return $(sect).attr("data"); return $(sect).attr("data");
...@@ -54,24 +58,24 @@ ...@@ -54,24 +58,24 @@
return $(sect).attr("data-stored"); return $(sect).attr("data-stored");
} }
var thisIFrame = $(spec.elem).
find('iframe[name^="iframe_"]').
get(0);
var cWindow = thisIFrame.contentWindow;
// 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 // If passed an argument, does not call gradefn, and instead directly
// updates the inputfield with the passed value. // updates the inputfield with the passed value.
var update = function (answer) { var update = function (answer) {
var ans; var ans;
ans = $(spec.elem). ans = cWindow[gradefn]();
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 // setstate presumes getstate, so don't getstate unless setstate is
// defined. // defined.
if (getgetstate() && getsetstate()) { if (getgetstate() && getsetstate()) {
var state, store; var state, store;
state = $(spec.elem). state = cWindow[getgetstate()]();
find('iframe[name^="iframe_"]').
get(0).
contentWindow[getgetstate()]();
store = { store = {
answer: ans, answer: ans,
state: state state: state
...@@ -91,8 +95,6 @@ ...@@ -91,8 +95,6 @@
$(updatebutton).click(update); $(updatebutton).click(update);
} }
/* Public methods */ /* Public methods */
that.update = update; that.update = update;
...@@ -116,19 +118,30 @@ ...@@ -116,19 +118,30 @@
updateHandler(); updateHandler();
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 // state to give it. If getsetstate is specified but calling it
// fails, wait and try again, since the iframe might still be
// loading.
if (getsetstate() && getstoredstate()) { if (getsetstate() && getstoredstate()) {
console.log("Using stored state...");
var sval; var sval;
if (typeof(getstoredstate()) === "object") { if (typeof(getstoredstate()) === "object") {
sval = getstoredstate()["state"]; sval = getstoredstate()["state"];
} else { } else {
sval = getstoredstate(); sval = getstoredstate();
} }
$(spec.elem). function whileloop(n) {
find('iframe[name^="iframe_"]'). if (n < 10){
get(0). try {
contentWindow[getsetstate()](sval); cWindow[getsetstate()](sval);
} catch (err) {
setTimeout(whileloop(n+1), 200);
}
}
else {
console.log("Error: could not set state");
}
}
whileloop(0);
} }
} else { } else {
// NOT CURRENTLY SUPPORTED // NOT CURRENTLY SUPPORTED
...@@ -171,11 +184,13 @@ ...@@ -171,11 +184,13 @@
all.each(function() { all.each(function() {
// Get just the mako variable 'id' from the id attribute // Get just the mako variable 'id' from the id attribute
newid = $(this).attr("id").replace(/^inputtype_/, ""); newid = $(this).attr("id").replace(/^inputtype_/, "");
var newJsElem = jsinputConstructor({ if (! jsinput.jsinputarr.exists(newid)){
id: newid, var newJsElem = jsinputConstructor({
elem: this, id: newid,
passive: false elem: this,
}); passive: false
});
}
}); });
} }
...@@ -193,5 +208,6 @@ ...@@ -193,5 +208,6 @@
//} //}
//}; //};
setTimeout(walkDOM, 200);
setTimeout(walkDOM, 100);
})(window.jsinput = window.jsinput || {}) })(window.jsinput = window.jsinput || {})
...@@ -87,6 +87,11 @@ attributes are also used) be passed as a string to the enclosing response type. ...@@ -87,6 +87,11 @@ attributes are also used) be passed as a string to the enclosing response type.
In the customresponse example above, this means cfn will be passed this answer In the customresponse example above, this means cfn will be passed this answer
as `ans`. as `ans`.
If the `gradefn` function throws an exception when a student attempts to
submit a problem, the submission is aborted, and the student receives a generic
alert. The alert can be customised by making the exception name `Waitfor
Exception`; in that case, the alert message will be the exception message.
**IMPORTANT** : the `gradefn` function should not be at all asynchronous, since **IMPORTANT** : the `gradefn` function should not be at all asynchronous, since
this could result in the student's latest answer not being passed correctly. this could result in the student's latest answer not being passed correctly.
Moreover, the function should also return promptly, since currently the student Moreover, the function should also return promptly, since currently the student
......
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