Abort submission and alter user if gradefn throws an exception
This commit is contained in:
@@ -129,6 +129,30 @@ class @Problem
|
||||
if setupMethod?
|
||||
@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,
|
||||
@@ -140,11 +164,7 @@ class @Problem
|
||||
check_fd: =>
|
||||
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 $('input:file').length == 0
|
||||
@check()
|
||||
@@ -217,6 +237,7 @@ class @Problem
|
||||
$.ajaxWithPrefix("#{@url}/problem_check", settings)
|
||||
|
||||
check: =>
|
||||
@check_waitfor()
|
||||
Logger.log 'problem_check', @answers
|
||||
$.postWithPrefix "#{@url}/problem_check", @answers, (response) =>
|
||||
switch response.success
|
||||
|
||||
@@ -10,16 +10,15 @@
|
||||
// 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;
|
||||
});
|
||||
};
|
||||
}
|
||||
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
|
||||
// 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
|
||||
@@ -35,6 +34,11 @@
|
||||
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
|
||||
function getgradefn() {
|
||||
return $(sect).attr("data");
|
||||
@@ -54,24 +58,24 @@
|
||||
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.
|
||||
// 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]();
|
||||
ans = cWindow[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()]();
|
||||
state = cWindow[getgetstate()]();
|
||||
store = {
|
||||
answer: ans,
|
||||
state: state
|
||||
@@ -91,8 +95,6 @@
|
||||
$(updatebutton).click(update);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Public methods */
|
||||
|
||||
that.update = update;
|
||||
@@ -116,19 +118,30 @@
|
||||
updateHandler();
|
||||
bindCheck();
|
||||
// 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()) {
|
||||
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);
|
||||
function whileloop(n) {
|
||||
if (n < 10){
|
||||
try {
|
||||
cWindow[getsetstate()](sval);
|
||||
} catch (err) {
|
||||
setTimeout(whileloop(n+1), 200);
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log("Error: could not set state");
|
||||
}
|
||||
}
|
||||
whileloop(0);
|
||||
|
||||
}
|
||||
} else {
|
||||
// NOT CURRENTLY SUPPORTED
|
||||
@@ -171,11 +184,13 @@
|
||||
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
|
||||
});
|
||||
if (! jsinput.jsinputarr.exists(newid)){
|
||||
var newJsElem = jsinputConstructor({
|
||||
id: newid,
|
||||
elem: this,
|
||||
passive: false
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -193,5 +208,6 @@
|
||||
//}
|
||||
//};
|
||||
|
||||
setTimeout(walkDOM, 200);
|
||||
|
||||
setTimeout(walkDOM, 100);
|
||||
})(window.jsinput = window.jsinput || {})
|
||||
|
||||
@@ -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
|
||||
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
|
||||
this could result in the student's latest answer not being passed correctly.
|
||||
Moreover, the function should also return promptly, since currently the student
|
||||
|
||||
Reference in New Issue
Block a user