Skip to content

Commit 6dec509

Browse files
committed
Add a message to the grade test confirmation dialog when questions are unanswered.
When the "Grade Test" button is clicked, JavaScript parses the `probstatus` inputs and determines what problems are in the test. Then it finds the answer inputs in the page and determines if there are any questions in a problem that are unanswered. If there are any problems with unanswered questions, then a message stating that is added to the confirmation dialog. This is to address the feature request in issue openwebwork#2183. Generally, this will work for most answers. However, there are a couple of cases that don't work right. These are cases where answer inputs are non-empty even if a student has not actively entered an answer. Most notable is the case of an answer to a `draggableProof.pl` or `draggableSubset.pl` question. The JavaScript for those always fills in a non-empty answer as soon as the problem is loaded into the page. Another case is if a problem uses the deprecated `PopUp` method from the `parserPopUp.pl` macro or the deprecated methods from `PGchoicemacros.pl`. For example, if `PopUp([ '?', 'a', 'b', 'c' ], 1)` is used. That will have the non-empty value `?` for the default answer. Unanswered questions that use the `draggableProof.pl` and `draggableSubset.pl` macros could be determined server side, but the only way to do so would be to process the problem via PG. Generally, there were several suggestions to issue openwebwork#2183 to use a server side set "flag" or have other server queries involved in this. Those really don't help other than in this case, and as just shouldn't be done. We really do not want to have a submission to the server that processes all problems in a test to determine if there are unanswered questions before submitting a test for grading in which all problems will again be processed. There was a suggestion to change the "preview test" button into a "save answers" button, but even that doesn't really help. It would not be reliable, because the information would only be there if the button were used first. There is no guarantee it would be used first, and this needs to have the ability to process the answers regardless. Note that attempting to correctly determine if a question that uses `parserPopUp.pl` or `PGchoicemacros.pl` is unanswered cannot even be done by processing the problem. This just adds emphasis that those should not be used anymore. There may be some other cases that have non-empty answers without students actively answering a question, but I can't think of any at this point. One thing to note is that if a problem has never even been displayed on the page (for example a `draggableProof.pl` problem that is on a page of the test the student never goes to) this approach will work correctly even for the above cases.
1 parent df5b59c commit 6dec509

File tree

2 files changed

+61
-0
lines changed

2 files changed

+61
-0
lines changed

htdocs/js/GatewayQuiz/gateway.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,41 @@
188188

189189
if (actuallySubmit) return;
190190

191+
const inputs = Array.from(document.querySelectorAll('input, select'));
192+
193+
// All problem numbers are represented by a probstatus hidden input. Use those to determine the problem
194+
// numbers of problems in the test. Note that problem numbering displayed on the page will not match these
195+
// numbers in the cases that the test definition has non-consecutive numbering or that problem order is
196+
// randomized. But the problem numbering will always match the quiz prefix numbering.
197+
const problems = [];
198+
for (const input of inputs.filter((i) => /^probstatus\d*/.test(i.name))) {
199+
problems[parseInt(input.name.replace('probstatus', ''))] = {};
200+
}
201+
202+
// Determine which questions have been answered. Note that there can be multiple inputs for a
203+
// given question (for example for checkbox or radio answers).
204+
for (const input of inputs.filter(
205+
(i) => /Q\d{4}_/.test(i.name) && !/^MaThQuIlL_/.test(i.name) && !/^previous_/.test(i.name)
206+
)) {
207+
const answered =
208+
input.type === 'radio' || input.type === 'checkbox' ? !!input.checked : /\S/.test(input.value);
209+
const match = /Q(\d{4})_/.exec(input.name);
210+
const problemNumber = parseInt(match?.[1] ?? '0');
211+
if (!(input.name in problems[problemNumber])) problems[problemNumber][input.name] = answered;
212+
else if (answered) problems[problemNumber][input.name] = 1;
213+
}
214+
215+
// Determine if there are any unanswered questions in each problem.
216+
let numProblemsWithUnanswered = 0;
217+
for (const problem of problems) {
218+
// Skip problem 0 and any problems that don't exist in the test
219+
// due to non-consecutive numbering in the test definition.
220+
if (!problem) continue;
221+
222+
if (!Object.keys(problem).length || !Object.values(problem).every((answered) => answered))
223+
++numProblemsWithUnanswered;
224+
}
225+
191226
// Prevent the gwquiz form from being submitted until after confirmation.
192227
evt.preventDefault();
193228

@@ -224,6 +259,23 @@
224259
modalBodyContent.textContent = submitAnswers.dataset.confirmDialogMessage;
225260
modalBody.append(modalBodyContent);
226261

262+
if (numProblemsWithUnanswered) {
263+
const modalSecondaryContent = document.createElement('div');
264+
modalSecondaryContent.classList.add('mt-3');
265+
modalSecondaryContent.textContent =
266+
(numProblemsWithUnanswered > 1
267+
? submitAnswers.dataset.unansweredQuestionsMessage
268+
? submitAnswers.dataset.unansweredQuestionsMessage.replace('%d', numProblemsWithUnanswered)
269+
: `There are ${numProblemsWithUnanswered} problems with unanswered questions.`
270+
: submitAnswers.dataset.unansweredQuestionMessage ??
271+
'There is a problem with unanswered questions.') +
272+
' ' +
273+
(submitAnswers.dataset.returnToTestMessage ??
274+
'Are you sure you want to grade the test? ' +
275+
'Select "No" if you would like to return to the test to enter more answers.');
276+
modalBody.append(modalSecondaryContent);
277+
}
278+
227279
const modalFooter = document.createElement('div');
228280
modalFooter.classList.add('modal-footer');
229281

templates/ContentGenerator/GatewayQuiz.html.ep

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,15 @@
705705
: maketext(
706706
'This is your only submission. If you say yes, then your answers will be final, '
707707
. 'and you will not be able to continue to work this test version.'
708+
),
709+
# This is always a plural form. JavaScript replaces %d with the
710+
# number of unanswered questions which will be greater than one.
711+
unanswered_questions_message =>
712+
maketext('There are [_1] problems with unanswered questions.', '%d'),
713+
unanswered_question_message => maketext('There is a problem with unanswered questions.'),
714+
return_to_test_message => maketext(
715+
'Are you sure you want to grade the test? '
716+
. 'Select "No" if you would like to return to the test to enter more answers.'
708717
)
709718
}
710719
)

0 commit comments

Comments
 (0)