MDL-70032 qtype_multichoice: Fix answer labelling
authorJun Pataleta <jun@moodle.com>
Thu, 29 Oct 2020 07:36:15 +0000 (15:36 +0800)
committerJun Pataleta <jun@moodle.com>
Thu, 29 Oct 2020 07:36:15 +0000 (15:36 +0800)
* Discard the use of the label element in order to be able to render
multiple choice answers as they are and have these act as the radio
button/checkbox' label through the aria-labelledby attribute.
* New JS module qtype_multichoice/answers that listens for click events
on the answer text container and selects the appropriate answer radio
button/checkbox.

question/type/multichoice/amd/build/answers.min.js [new file with mode: 0644]
question/type/multichoice/amd/build/answers.min.js.map [new file with mode: 0644]
question/type/multichoice/amd/src/answers.js [new file with mode: 0644]
question/type/multichoice/renderer.php

diff --git a/question/type/multichoice/amd/build/answers.min.js b/question/type/multichoice/amd/build/answers.min.js
new file mode 100644 (file)
index 0000000..5f519e3
Binary files /dev/null and b/question/type/multichoice/amd/build/answers.min.js differ
diff --git a/question/type/multichoice/amd/build/answers.min.js.map b/question/type/multichoice/amd/build/answers.min.js.map
new file mode 100644 (file)
index 0000000..f93028e
Binary files /dev/null and b/question/type/multichoice/amd/build/answers.min.js.map differ
diff --git a/question/type/multichoice/amd/src/answers.js b/question/type/multichoice/amd/src/answers.js
new file mode 100644 (file)
index 0000000..bd5cd27
--- /dev/null
@@ -0,0 +1,57 @@
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Handles events related to the multiple-choice question type answers.
+ *
+ * @module     qtype_multichoice/answers
+ * @package    qtype_multichoice
+ * @copyright  2020 Jun Pataleta <jun@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * Selectors for this module.
+ *
+ * @type {{ANSWER_LABEL: string}}
+ */
+const SELECTORS = {
+    ANSWER_LABEL: '[data-region=answer-label]',
+};
+
+/**
+ * Init method.
+ *
+ * @param {string} rootId The ID of the question container.
+ */
+const init = (rootId) => {
+    const root = document.getElementById(rootId);
+
+    // Add click event handlers for the divs containing the answer since these cannot be enclosed in a label element.
+    const answerLabels = root.querySelectorAll(SELECTORS.ANSWER_LABEL);
+    answerLabels.forEach((answerLabel) => {
+        answerLabel.addEventListener('click', (e) => {
+            const labelId = e.currentTarget.id;
+            // Fetch the answer this label is assigned to.
+            const linkedOption = root.querySelector(`[aria-labelledby="${labelId}"]`);
+            // Trigger the click event.
+            linkedOption.click();
+        });
+    });
+};
+
+export default {
+    init: init
+};
index c886cda..0c83b5e 100644 (file)
@@ -88,6 +88,7 @@ abstract class qtype_multichoice_renderer_base extends qtype_with_combined_feedb
             $inputattributes['name'] = $this->get_input_name($qa, $value);
             $inputattributes['value'] = $this->get_input_value($value);
             $inputattributes['id'] = $this->get_input_id($qa, $value);
+            $inputattributes['aria-labelledby'] = $inputattributes['id'] . '_label';
             $isselected = $question->is_choice_selected($response, $value);
             if ($isselected) {
                 $inputattributes['checked'] = 'checked';
@@ -102,15 +103,16 @@ abstract class qtype_multichoice_renderer_base extends qtype_with_combined_feedb
                     'value' => 0,
                 ));
             }
+
+            $questionnumber = html_writer::span($this->number_in_style($value, $question->answernumbering), 'answernumber');
+            $answertext = $question->format_text($ans->answer, $ans->answerformat, $qa, 'question', 'answer', $ansid);
+            $questionanswer = html_writer::div($answertext, 'flex-fill ml-1');
+
             $radiobuttons[] = $hidden . html_writer::empty_tag('input', $inputattributes) .
-                    html_writer::tag('label',
-                        html_writer::span($this->number_in_style($value, $question->answernumbering), 'answernumber') .
-                        html_writer::tag('div',
-                        $question->format_text(
-                                    $ans->answer, $ans->answerformat,
-                                    $qa, 'question', 'answer', $ansid),
-                        array('class' => 'flex-fill ml-1')),
-                        array('for' => $inputattributes['id'], 'class' => 'd-flex w-100'));
+                    html_writer::div($questionnumber . $questionanswer, 'd-flex w-100', [
+                        'id' => $inputattributes['id'] . '_label',
+                        'data-region' => 'answer-label',
+                    ]);
 
             // Param $options->suppresschoicefeedback is a hack specific to the
             // oumultiresponse question type. It would be good to refactor to
@@ -151,6 +153,9 @@ abstract class qtype_multichoice_renderer_base extends qtype_with_combined_feedb
         }
         $result .= html_writer::end_tag('div'); // Answer.
 
+        // Load JS module for the question answers.
+        $this->page->requires->js_call_amd('qtype_multichoice/answers', 'init',
+            [$qa->get_outer_question_div_unique_id()]);
         $result .= $this->after_choices($qa, $options);
 
         $result .= html_writer::end_tag('div'); // Ablock.