|  | 
| 188 | 188 | 
 | 
| 189 | 189 | 			if (actuallySubmit) return; | 
| 190 | 190 | 
 | 
|  | 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 | + | 
| 191 | 226 | 			// Prevent the gwquiz form from being submitted until after confirmation. | 
| 192 | 227 | 			evt.preventDefault(); | 
| 193 | 228 | 
 | 
|  | 
| 224 | 259 | 			modalBodyContent.textContent = submitAnswers.dataset.confirmDialogMessage; | 
| 225 | 260 | 			modalBody.append(modalBodyContent); | 
| 226 | 261 | 
 | 
|  | 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 | + | 
| 227 | 279 | 			const modalFooter = document.createElement('div'); | 
| 228 | 280 | 			modalFooter.classList.add('modal-footer'); | 
| 229 | 281 | 
 | 
|  | 
0 commit comments