MDL-68864 qtype_multichoice: shift focus to first option on keyboard tab
authorBas Brands <bas@moodle.com>
Tue, 9 Jun 2020 13:39:34 +0000 (15:39 +0200)
committerJun Pataleta <jun@moodle.com>
Wed, 10 Jun 2020 03:10:46 +0000 (11:10 +0800)
- This prevents losing focus when using keyboard navigation.

question/type/multichoice/amd/build/clearchoice.min.js
question/type/multichoice/amd/build/clearchoice.min.js.map
question/type/multichoice/amd/src/clearchoice.js
question/type/multichoice/renderer.php
question/type/multichoice/tests/behat/clearanswers.feature [new file with mode: 0644]

index d68c688..1eb3da4 100644 (file)
Binary files a/question/type/multichoice/amd/build/clearchoice.min.js and b/question/type/multichoice/amd/build/clearchoice.min.js differ
index a777477..f51b3a5 100644 (file)
Binary files a/question/type/multichoice/amd/build/clearchoice.min.js.map and b/question/type/multichoice/amd/build/clearchoice.min.js.map differ
index 5ca09ce..5fb268c 100644 (file)
@@ -25,16 +25,17 @@ define(['jquery', 'core/custom_interaction_events'], function($, CustomEvents) {
 
     var SELECTORS = {
         CHOICE_ELEMENT: '.answer input',
-        CLEAR_CHOICE_ELEMENT: 'div[class="qtype_multichoice_clearchoice"]'
+        LINK: 'a',
+        RADIO: 'input[type="radio"]'
     };
 
     /**
-     * Mark clear choice radio as checked.
+     * Mark clear choice radio as enabled and checked.
      *
      * @param {Object} clearChoiceContainer The clear choice option container.
      */
     var checkClearChoiceRadio = function(clearChoiceContainer) {
-        clearChoiceContainer.find('input[type="radio"]').prop('checked', true);
+        clearChoiceContainer.find(SELECTORS.RADIO).prop('disabled', false).prop('checked', true);
     };
 
     /**
@@ -55,6 +56,7 @@ define(['jquery', 'core/custom_interaction_events'], function($, CustomEvents) {
      */
     var hideClearChoiceOption = function(clearChoiceContainer) {
         clearChoiceContainer.addClass('sr-only');
+        clearChoiceContainer.find(SELECTORS.LINK).attr('tabindex', -1);
     };
 
     /**
@@ -64,6 +66,8 @@ define(['jquery', 'core/custom_interaction_events'], function($, CustomEvents) {
      */
     var showClearChoiceOption = function(clearChoiceContainer) {
         clearChoiceContainer.removeClass('sr-only');
+        clearChoiceContainer.find(SELECTORS.LINK).attr('tabindex', 0);
+        clearChoiceContainer.find(SELECTORS.RADIO).prop('disabled', true);
     };
 
     /**
@@ -75,7 +79,7 @@ define(['jquery', 'core/custom_interaction_events'], function($, CustomEvents) {
     var registerEventListeners = function(root, fieldPrefix) {
         var clearChoiceContainer = getClearChoiceElement(root, fieldPrefix);
 
-        root.on(CustomEvents.events.activate, SELECTORS.CLEAR_CHOICE_ELEMENT, function(e, data) {
+        clearChoiceContainer.on(CustomEvents.events.activate, SELECTORS.LINK, function(e, data) {
 
                 // Mark the clear choice radio element as checked.
                 checkClearChoiceRadio(clearChoiceContainer);
@@ -89,6 +93,13 @@ define(['jquery', 'core/custom_interaction_events'], function($, CustomEvents) {
             // If the event has been triggered by any other choice, show the clear choice option.
             showClearChoiceOption(clearChoiceContainer);
         });
+
+        // If the clear choice radio receives focus from using the tab key, return the focus
+        // to the first answer option.
+        clearChoiceContainer.find(SELECTORS.RADIO).focus(function() {
+            var firstChoice = root.find(SELECTORS.CHOICE_ELEMENT).first();
+            firstChoice.focus();
+        });
     };
 
     /**
index bca60c8..8a09a69 100644 (file)
@@ -298,14 +298,16 @@ class qtype_multichoice_single_renderer extends qtype_multichoice_renderer_base
 
         $cssclass = 'qtype_multichoice_clearchoice';
         // When no choice selected during rendering, then hide the clear choice option.
+        $linktabindex = 0;
         if (!$hascheckedchoice && $response == -1) {
             $cssclass .= ' sr-only';
             $clearchoiceradioattrs['checked'] = 'checked';
+            $linktabindex = -1;
         }
         // Adds an hidden radio that will be checked to give the impression the choice has been cleared.
         $clearchoiceradio = html_writer::empty_tag('input', $clearchoiceradioattrs);
         $clearchoiceradio .= html_writer::link('', get_string('clearchoice', 'qtype_multichoice'),
-            ['for' => $clearchoiceid, 'role' => 'button']);
+            ['for' => $clearchoiceid, 'role' => 'button', 'tabindex' => $linktabindex]);
 
         // Now wrap the radio and label inside a div.
         $result = html_writer::tag('div', $clearchoiceradio, ['id' => $clearchoicefieldname, 'class' => $cssclass]);
diff --git a/question/type/multichoice/tests/behat/clearanswers.feature b/question/type/multichoice/tests/behat/clearanswers.feature
new file mode 100644 (file)
index 0000000..3f78277
--- /dev/null
@@ -0,0 +1,57 @@
+@qtype @qtype_multichoice
+Feature: Clear my answers
+  As a student
+  In order to reset Multiple choice ansers
+  I need to clear my choice
+
+  Background:
+    Given the following "users" exist:
+      | username | firstname | lastname | email               |
+      | student1 | S1        | Student1 | student1@moodle.com |
+    And the following "courses" exist:
+      | fullname | shortname | category |
+      | Course 1 | C1        | 0        |
+    And the following "course enrolments" exist:
+      | user     | course | role           |
+      | student1 | C1     | student        |
+    And the following "question categories" exist:
+      | contextlevel | reference | name           |
+      | Course       | C1        | Test questions |
+    And the following "questions" exist:
+      | questioncategory | qtype       | name             | template    | questiontext    |
+      | Test questions   | multichoice | Multi-choice-001 | one_of_four | Question One    |
+    And the following "activities" exist:
+      | activity   | name   | intro              | course | idnumber | preferredbehaviour | canredoquestions |
+      | quiz       | Quiz 1 | Quiz 1 description | C1     | quiz1    | immediatefeedback  | 1                |
+    And quiz "Quiz 1" contains the following questions:
+      | question         | page |
+      | Multi-choice-001 | 1    |
+
+  @javascript
+  Scenario: Attempt a quiz and reset my chosen answer.
+    When I log in as "student1"
+    And I am on "Course 1" course homepage
+    And I follow "Quiz 1"
+    And I press "Attempt quiz now"
+    And I should see "Question One"
+    And I click on "Four" "radio" in the "Question One" "question"
+    And I should see "Clear my choice"
+    And I click on "Clear my choice" "button" in the "Question One" "question"
+    Then I should not see "Clear my choice"
+    And I click on "Check" "button" in the "Question One" "question"
+    And I should see "Please select an answer" in the "Question One" "question"
+
+  @javascript
+  Scenario: Attempt a quiz and revisit a cleared answer.
+    When I log in as "student1"
+    And I am on "Course 1" course homepage
+    And I follow "Quiz 1"
+    And I press "Attempt quiz now"
+    And I should see "Question One"
+    And I click on "Four" "radio" in the "Question One" "question"
+    And I follow "Finish attempt ..."
+    And I click on "Return to attempt" "button"
+    And I click on "Clear my choice" "button" in the "Question One" "question"
+    And I follow "Finish attempt ..."
+    And I click on "Return to attempt" "button"
+    Then I should not see "Clear my choice"