MDL-68864 qtype_multichoice: clearchoice
authorBas Brands <bas@moodle.com>
Wed, 3 Jun 2020 15:40:33 +0000 (17:40 +0200)
committerJun Pataleta <jun@moodle.com>
Wed, 10 Jun 2020 03:10:46 +0000 (11:10 +0800)
- Reverted changes from MDL-67901

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 [deleted file]
question/type/multichoice/tests/walkthrough_test.php
theme/boost/scss/moodle/question.scss
theme/boost/style/moodle.css
theme/classic/style/moodle.css

index 61bd64b..d68c688 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 75a3b58..a777477 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 850bbf7..5ca09ce 100644 (file)
 define(['jquery', 'core/custom_interaction_events'], function($, CustomEvents) {
 
     var SELECTORS = {
-        ANSWER_RADIOS: '.answer input',
-        CLEARRESULTS_BUTTON: 'button[data-action="clearresults"]'
+        CHOICE_ELEMENT: '.answer input',
+        CLEAR_CHOICE_ELEMENT: 'div[class="qtype_multichoice_clearchoice"]'
     };
 
-    var CSSHIDDEN = 'd-none';
+    /**
+     * Mark clear choice radio as checked.
+     *
+     * @param {Object} clearChoiceContainer The clear choice option container.
+     */
+    var checkClearChoiceRadio = function(clearChoiceContainer) {
+        clearChoiceContainer.find('input[type="radio"]').prop('checked', true);
+    };
+
+    /**
+     * Get the clear choice div container.
+     *
+     * @param {Object} root The question root element.
+     * @param {string} fieldPrefix The question outer div prefix.
+     * @returns {Object} The clear choice div container.
+     */
+    var getClearChoiceElement = function(root, fieldPrefix) {
+        return root.find('div[id="' + fieldPrefix + '"]');
+    };
+
+    /**
+     * Hide clear choice option.
+     *
+     * @param {Object} clearChoiceContainer The clear choice option container.
+     */
+    var hideClearChoiceOption = function(clearChoiceContainer) {
+        clearChoiceContainer.addClass('sr-only');
+    };
+
+    /**
+     * Shows clear choice option.
+     *
+     * @param {Object} clearChoiceContainer The clear choice option container.
+     */
+    var showClearChoiceOption = function(clearChoiceContainer) {
+        clearChoiceContainer.removeClass('sr-only');
+    };
 
     /**
      * Register event listeners for the clear choice module.
      *
      * @param {Object} root The question outer div prefix.
+     * @param {string} fieldPrefix The "Clear choice" div prefix.
      */
-    var registerEventListeners = function(root) {
+    var registerEventListeners = function(root, fieldPrefix) {
+        var clearChoiceContainer = getClearChoiceElement(root, fieldPrefix);
+
+        root.on(CustomEvents.events.activate, SELECTORS.CLEAR_CHOICE_ELEMENT, function(e, data) {
 
-        var clearChoiceButton = root.find(SELECTORS.CLEARRESULTS_BUTTON);
+                // Mark the clear choice radio element as checked.
+                checkClearChoiceRadio(clearChoiceContainer);
+                // Now that the hidden radio has been checked, hide the clear choice option.
+                hideClearChoiceOption(clearChoiceContainer);
 
-        root.on(CustomEvents.events.activate, SELECTORS.CLEARRESULTS_BUTTON, function(e, data) {
-            root.find(SELECTORS.ANSWER_RADIOS).each(function() {
-                $(this).prop('checked', false);
-            });
-            $(e.target).addClass(CSSHIDDEN);
-            data.originalEvent.preventDefault();
+                data.originalEvent.preventDefault();
         });
 
-        root.on(CustomEvents.events.activate, SELECTORS.ANSWER_RADIOS, function() {
-            clearChoiceButton.removeClass(CSSHIDDEN);
+        root.on(CustomEvents.events.activate, SELECTORS.CHOICE_ELEMENT, function() {
+            // If the event has been triggered by any other choice, show the clear choice option.
+            showClearChoiceOption(clearChoiceContainer);
         });
     };
 
@@ -56,10 +95,11 @@ define(['jquery', 'core/custom_interaction_events'], function($, CustomEvents) {
      * Initialise clear choice module.
 
      * @param {string} root The question outer div prefix.
+     * @param {string} fieldPrefix The "Clear choice" div prefix.
      */
-    var init = function(root) {
+    var init = function(root, fieldPrefix) {
         root = $('#' + root);
-        registerEventListeners(root);
+        registerEventListeners(root, fieldPrefix);
     };
 
     return {
index 7412d7a..bca60c8 100644 (file)
@@ -286,25 +286,35 @@ class qtype_multichoice_single_renderer extends qtype_multichoice_renderer_base
             }
         }
 
-        $questiondivid = $qa->get_outer_question_div_unique_id();
+        $clearchoiceid = $this->get_input_id($qa, -1);
+        $clearchoicefieldname = $qa->get_qt_field_name('clearchoice');
+        $clearchoiceradioattrs = [
+            'type' => $this->get_input_type(),
+            'name' => $qa->get_qt_field_name('answer'),
+            'id' => $clearchoiceid,
+            'value' => -1,
+            'class' => 'sr-only'
+        ];
 
+        $cssclass = 'qtype_multichoice_clearchoice';
         // When no choice selected during rendering, then hide the clear choice option.
-        $cssclass = '';
         if (!$hascheckedchoice && $response == -1) {
-            $cssclass = 'd-none';
+            $cssclass .= ' sr-only';
+            $clearchoiceradioattrs['checked'] = 'checked';
         }
+        // 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']);
 
-        $clearchoicebutton = html_writer::tag('button', get_string('clearchoice', 'qtype_multichoice'), [
-            'class' => 'btn btn-link ml-3 ' . $cssclass,
-            'data-action' => 'clearresults',
-            'data-target' => '#' . $questiondivid
-        ]);
+        // Now wrap the radio and label inside a div.
+        $result = html_writer::tag('div', $clearchoiceradio, ['id' => $clearchoicefieldname, 'class' => $cssclass]);
 
         // Load required clearchoice AMD module.
         $this->page->requires->js_call_amd('qtype_multichoice/clearchoice', 'init',
-            [$questiondivid]);
+            [$qa->get_outer_question_div_unique_id(), $clearchoicefieldname]);
 
-        return $clearchoicebutton;
+        return $result;
     }
 
 }
diff --git a/question/type/multichoice/tests/behat/clearanswers.feature b/question/type/multichoice/tests/behat/clearanswers.feature
deleted file mode 100644 (file)
index 2facc00..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-@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"
index 95902b7..53ae95e 100644 (file)
@@ -126,6 +126,75 @@ class qtype_multichoice_walkthrough_test extends qbehaviour_walkthrough_test_bas
                 new question_pattern_expectation('/class="r1"/'));
     }
 
+    /**
+     * Test for clear choice option.
+     */
+    public function test_deferredfeedback_feedback_multichoice_clearchoice() {
+
+        // Create a multichoice, single question.
+        $mc = test_question_maker::make_a_multichoice_single_question();
+        $mc->shuffleanswers = false;
+
+        $clearchoice = -1;
+        $rightchoice = 0;
+        $wrongchoice = 2;
+
+        $this->start_attempt_at_question($mc, 'deferredfeedback', 3);
+
+        // Let's first submit the wrong choice (2).
+        $this->process_submission(array('answer' => $wrongchoice));  // Wrong choice (2).
+
+        $this->check_current_mark(null);
+        // Clear choice radio should not be checked.
+        $this->check_current_output(
+            $this->get_contains_mc_radio_expectation($rightchoice, true, false), // Not checked.
+            $this->get_contains_mc_radio_expectation($rightchoice + 1, true, false), // Not checked.
+            $this->get_contains_mc_radio_expectation($rightchoice + 2, true, true), // Wrong choice (2) checked.
+            $this->get_contains_mc_radio_expectation($clearchoice, true, false), // Not checked.
+            $this->get_does_not_contain_correctness_expectation(),
+            $this->get_does_not_contain_feedback_expectation()
+        );
+
+        // Now, let's clear our previous choice.
+        $this->process_submission(array('answer' => $clearchoice)); // Clear choice (-1).
+        $this->check_current_mark(null);
+
+        // This time, the clear choice radio should be the only one checked.
+        $this->check_current_output(
+            $this->get_contains_mc_radio_expectation($rightchoice, true, false), // Not checked.
+            $this->get_contains_mc_radio_expectation($rightchoice + 1, true, false), // Not checked.
+            $this->get_contains_mc_radio_expectation($rightchoice + 2, true, false), // Not checked.
+            $this->get_contains_mc_radio_expectation($clearchoice, true, true), // Clear choice radio checked.
+            $this->get_does_not_contain_correctness_expectation(),
+            $this->get_does_not_contain_feedback_expectation()
+        );
+
+        // Finally, let's submit the right choice.
+        $this->process_submission(array('answer' => $rightchoice)); // Right choice (0).
+        $this->check_current_state(question_state::$complete);
+        $this->check_current_mark(null);
+        $this->check_current_output(
+            $this->get_contains_mc_radio_expectation($rightchoice, true, true),
+            $this->get_contains_mc_radio_expectation($rightchoice + 1, true, false),
+            $this->get_contains_mc_radio_expectation($rightchoice + 2, true, false),
+            $this->get_contains_mc_radio_expectation($clearchoice, true, false),
+            $this->get_does_not_contain_correctness_expectation(),
+            $this->get_does_not_contain_feedback_expectation()
+        );
+
+        // Finish the attempt.
+        $this->finish();
+
+        // Verify.
+        $this->check_current_state(question_state::$gradedright);
+        $this->check_current_mark(3);
+        $this->check_current_output(
+            $this->get_contains_mc_radio_expectation($rightchoice, false, true),
+            $this->get_contains_correct_expectation(),
+            new question_pattern_expectation('/class="r0 correct"/'),
+            new question_pattern_expectation('/class="r1"/'));
+    }
+
     public function test_deferredfeedback_feedback_multichoice_multi_showstandardunstruction_yes() {
 
         // Create a multichoice, multi question.
index 4ed5c9a..d4d19a0 100644 (file)
@@ -305,6 +305,14 @@ body.path-question-type {
 .que.multichoice .answer div.r1 .icon.fa-remove {
     text-indent: 0;
 }
+.qtype_multichoice_clearchoice {
+    padding-top: 10px;
+    a {
+        cursor: pointer;
+        text-decoration: underline;
+        padding-left: 30px;
+    }
+}
 
 .formulation input[type="text"],
 .formulation select {
index c9fc671..af0ee78 100644 (file)
@@ -15604,6 +15604,13 @@ body.path-question-type {
 .que.multichoice .answer div.r1 .icon.fa-remove {
   text-indent: 0; }
 
+.qtype_multichoice_clearchoice {
+  padding-top: 10px; }
+  .qtype_multichoice_clearchoice a {
+    cursor: pointer;
+    text-decoration: underline;
+    padding-left: 30px; }
+
 .formulation input[type="text"],
 .formulation select {
   width: auto;
index 7be6184..8227275 100644 (file)
@@ -15827,6 +15827,13 @@ body.path-question-type {
 .que.multichoice .answer div.r1 .icon.fa-remove {
   text-indent: 0; }
 
+.qtype_multichoice_clearchoice {
+  padding-top: 10px; }
+  .qtype_multichoice_clearchoice a {
+    cursor: pointer;
+    text-decoration: underline;
+    padding-left: 30px; }
+
 .formulation input[type="text"],
 .formulation select {
   width: auto;