diff --git a/js/blanks.js b/js/blanks.js index 30194de..d3434d3 100644 --- a/js/blanks.js +++ b/js/blanks.js @@ -196,14 +196,8 @@ H5P.Blanks = (function ($, Question) { if (!self.params.behaviour.autoCheck && this.params.behaviour.enableCheckButton) { // Check answer button self.addButton('check-answer', self.params.checkAnswer, function () { - // Move focus to top of content - self.a11yHeader.innerHTML = self.params.a11yHeader; - self.a11yHeader.focus(); - - self.toggleButtonVisibility(STATE_CHECKING); self.markResults(); - self.showEvaluation(); - self.triggerAnswered(); + self.handleCheckAnswer({ focusFirstAnswer: true }); }, true, { 'aria-label': self.params.a11yCheck, }, { @@ -220,7 +214,7 @@ H5P.Blanks = (function ($, Question) { // Show solution button self.addButton('show-solution', self.params.showSolutions, function () { - self.showCorrectAnswers(false); + self.showCorrectAnswers(false, true); }, self.params.behaviour.enableSolutionsButton, { 'aria-label': self.params.a11yShowSolution, }); @@ -228,7 +222,6 @@ H5P.Blanks = (function ($, Question) { // Try again button if (self.params.behaviour.enableRetry === true) { self.addButton('try-again', self.params.tryAgain, function () { - self.a11yHeader.innerHTML = ''; self.resetTask(); self.$questions.filter(':first').find('input:first').focus(); }, true, { @@ -245,6 +238,26 @@ H5P.Blanks = (function ($, Question) { self.toggleButtonVisibility(STATE_ONGOING); }; + /** + * Handle checking answer. + * + * @param {object} [params={}] Parameters. + * @param {boolean} [params.focusFirstAnswer] If true, focus first answer. + */ + Blanks.prototype.handleCheckAnswer = function(params={}) { + this.toggleButtonVisibility(STATE_CHECKING); + this.showEvaluation(); + this.triggerAnswered(); + + if (params.focusFirstAnswer) { + this.read(this.params.a11yHeader); // Announce 'checking mode' + + window.setTimeout(() => { + this.$questions.filter(':first').find('input:first').focus(); + }, 1); // Read 'checking mode' before announcing focus of first answer + } + } + /** * Find blanks in a string and run a handler on those blanks * @@ -318,11 +331,6 @@ H5P.Blanks = (function ($, Question) { self.hasClozes = clozeNumber > 0; this.$questions = $(html); - self.a11yHeader = document.createElement('div'); - self.a11yHeader.classList.add('hidden-but-read'); - self.a11yHeader.tabIndex = -1; - self.$questions[0].insertBefore(self.a11yHeader, this.$questions[0].childNodes[0] || null); - // Set input fields. this.$questions.find('input').each(function (i) { @@ -346,10 +354,7 @@ H5P.Blanks = (function ($, Question) { var answer = $("
").text(this.getUserAnswer()).html(); self.read((this.correct() ? self.params.answerIsCorrect : self.params.answerIsWrong).replace(':ans', answer)); if (self.done || self.allBlanksFilledOut()) { - // All answers has been given. Show solutions button. - self.toggleButtonVisibility(STATE_CHECKING); - self.showEvaluation(); - self.triggerAnswered(); + self.handleCheckAnswer(); self.done = true; } }; @@ -561,11 +566,12 @@ H5P.Blanks = (function ($, Question) { /** - * Displays the correct answers - * @param {boolean} [alwaysShowSolution] - * Will always show solution if true + * Displays the correct answers. + * + * @param {boolean} [alwaysShowSolution] Will always show solution if true. + * @param {boolean} [focusFirstAnswer] If true, focus first answer. */ - Blanks.prototype.showCorrectAnswers = function (alwaysShowSolution) { + Blanks.prototype.showCorrectAnswers = function (alwaysShowSolution, focusFirstAnswer) { if (!alwaysShowSolution && !this.allowSolution()) { return; } @@ -573,6 +579,10 @@ H5P.Blanks = (function ($, Question) { this.toggleButtonVisibility(STATE_SHOWING_SOLUTION); this.hideSolutions(); + if (focusFirstAnswer) { + this.$questions.filter(':first').find('input:first').focus(); + } + for (var i = 0; i < this.clozes.length; i++) { this.clozes[i].showSolution(); } @@ -930,9 +940,13 @@ H5P.Blanks = (function ($, Question) { /** * Disables any active input. Useful for freezing the task and dis-allowing * modification of wrong answers. + * + * Not used here, but may be used by other content types, currently IV only. */ Blanks.prototype.disableInput = function () { - this.$questions.find('input').attr('disabled', true); + this.clozes.forEach((cloze) => { + cloze.disableInput(); + }); }; Blanks.idCounter = 0; diff --git a/js/cloze.js b/js/cloze.js index 8a60a7b..432a59b 100644 --- a/js/cloze.js +++ b/js/cloze.js @@ -82,8 +82,7 @@ var isCorrect = correct(checkedAnswer); if (isCorrect) { $wrapper.addClass('h5p-correct'); - $input.attr('disabled', true) - .attr('aria-label', inputLabel + '. ' + l10n.answeredCorrectly); + this.toggleInput(false, inputLabel + '. ' + l10n.answeredCorrectly); } else { $wrapper.addClass('h5p-wrong'); @@ -108,12 +107,19 @@ }; /** - * Toggles input enable/disable + * Toggles input enable/disable. Leaves input field tabable. + * * @method toggleInput - * @param {boolean} enabled True if input should be enabled, otherwise false + * @param {boolean} enabled True if input should be enabled, otherwise false. + * @param {string} [ariaLabel] Optional change for aria label. */ - this.toggleInput = function (enabled) { - $input.attr('disabled', !enabled); + this.toggleInput = function (enabled, ariaLabel) { + $input.attr('aria-disabled', !enabled); + $input.attr('readonly', !enabled ? true : null); + + if (typeof ariaLabel === 'string') { + $input.attr('aria-label', ariaLabel); + } }; /** @@ -130,12 +136,12 @@ text: H5P.trim(answer.replace(/\s*\/\s*/g, '/')), insertAfter: $wrapper }); - $input.attr('disabled', true); - var ariaLabel = inputLabel + '. ' + + + const ariaLabel = inputLabel + '. ' + l10n.solutionLabel + ' ' + answer + '. ' + l10n.answeredIncorrectly; - $input.attr('aria-label', ariaLabel); + this.toggleInput(false, ariaLabel); }; /** @@ -214,7 +220,7 @@ // Set trimmed answer $input.val(trimmedAnswer); if (behaviour.formulaEditor) { - // If fomula editor is enabled set trimmed text + // If fomula editor is enabled set trimmed text $input.parent().find('.wiris-h5p-input').html(trimmedAnswer); } return trimmedAnswer;