MDL-67901 type_multichoice: clear my choice accessibility

Improve the accessibility for the clear my choice option. The
extra radio input controlling the reset feature was removed and
resetting the choice is now controlled by JavaScript.
This fixes the missing label reported in Accessibility audit and W3C
validation of the reset link

By this change this issue also fixes MDL-67280
This commit is contained in:
Bas Brands
2020-03-04 17:04:18 +01:00
parent 95eb7d8343
commit 43b11cfc9e
5 changed files with 71 additions and 144 deletions
+1 -1
View File
@@ -1 +1 @@
define(["jquery","core/custom_interaction_events"],function(a,b){var c={CHOICE_ELEMENT:".answer input",CLEAR_CHOICE_ELEMENT:'div[class="qtype_multichoice_clearchoice"]'},d=function(a){a.find('input[type="radio"]').prop("checked",!0)},e=function(a,b){return a.find('div[id="'+b+'"]')},f=function(a){a.addClass("sr-only")},g=function(a){a.removeClass("sr-only")},h=function(a,h){var i=e(a,h);a.on(b.events.activate,c.CLEAR_CHOICE_ELEMENT,function(a,b){d(i),f(i),b.originalEvent.preventDefault()}),a.on(b.events.activate,c.CHOICE_ELEMENT,function(){g(i)})},i=function(b,c){b=a("#"+b),h(b,c)};return{init:i}});
define(["jquery","core/custom_interaction_events"],function(a,b){var c={ANSWER_RADIOS:".answer input",CLEARRESULTS_LINK:'a[data-action="clearresults"]'},d="d-none",e=function(e){var f=e.find(c.CLEARRESULTS_LINK);e.on(b.events.activate,c.CLEARRESULTS_LINK,function(b,f){e.find(c.ANSWER_RADIOS).each(function(){a(this).prop("checked",!1)}),a(b.target).addClass(d),f.originalEvent.preventDefault()}),e.on(b.events.activate,c.ANSWER_RADIOS,function(){f.removeClass(d)})},f=function(b){b=a("#"+b),e(b)};return{init:f}});
@@ -24,70 +24,31 @@
define(['jquery', 'core/custom_interaction_events'], function($, CustomEvents) {
var SELECTORS = {
CHOICE_ELEMENT: '.answer input',
CLEAR_CHOICE_ELEMENT: 'div[class="qtype_multichoice_clearchoice"]'
ANSWER_RADIOS: '.answer input',
CLEARRESULTS_LINK: 'a[data-action="clearresults"]'
};
/**
* 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');
};
var CSSHIDDEN = 'd-none';
/**
* 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, fieldPrefix) {
var clearChoiceContainer = getClearChoiceElement(root, fieldPrefix);
var registerEventListeners = function(root) {
root.on(CustomEvents.events.activate, SELECTORS.CLEAR_CHOICE_ELEMENT, function(e, data) {
var clearChoiceButton = root.find(SELECTORS.CLEARRESULTS_LINK);
// 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);
data.originalEvent.preventDefault();
root.on(CustomEvents.events.activate, SELECTORS.CLEARRESULTS_LINK, function(e, data) {
root.find(SELECTORS.ANSWER_RADIOS).each(function() {
$(this).prop('checked', false);
});
$(e.target).addClass(CSSHIDDEN);
data.originalEvent.preventDefault();
});
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);
root.on(CustomEvents.events.activate, SELECTORS.ANSWER_RADIOS, function() {
clearChoiceButton.removeClass(CSSHIDDEN);
});
};
@@ -95,11 +56,10 @@ 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, fieldPrefix) {
var init = function(root) {
root = $('#' + root);
registerEventListeners(root, fieldPrefix);
registerEventListeners(root);
};
return {
+13 -19
View File
@@ -274,6 +274,7 @@ class qtype_multichoice_single_renderer extends qtype_multichoice_renderer_base
$question = $qa->get_question();
$response = $question->get_response($qa);
$clearchoicefieldname = $qa->get_qt_field_name('clearchoice');
$hascheckedchoice = false;
foreach ($question->get_order($qa) as $value => $ansid) {
if ($question->is_choice_selected($response, $value)) {
@@ -282,33 +283,26 @@ class qtype_multichoice_single_renderer extends qtype_multichoice_renderer_base
}
}
$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'
];
$questiondivid = $qa->get_outer_question_div_unique_id();
$cssclass = 'qtype_multichoice_clearchoice';
// When no choice selected during rendering, then hide the clear choice option.
$csshidden = '';
if (!$hascheckedchoice && $response == -1) {
$cssclass .= ' sr-only';
$clearchoiceradioattrs['checked'] = 'checked';
$csshidden = ' d-none';
}
// 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']);
// Now wrap the radio and label inside a div.
$result = html_writer::tag('div', $clearchoiceradio, ['id' => $clearchoicefieldname, 'class' => $cssclass]);
$clearchoicelink = html_writer::link('', get_string('clearchoice', 'qtype_multichoice'), [
'class' => $csshidden,
'role' => 'button',
'data-action' => 'clearresults',
'data-target' => '#' . $questiondivid
]);
$result = html_writer::tag('div', $clearchoicelink, ['id' => $clearchoicefieldname,
'class' => 'qtype_multichoice_clearchoice']);
// Load required clearchoice AMD module.
$this->page->requires->js_call_amd('qtype_multichoice/clearchoice', 'init',
[$qa->get_outer_question_div_unique_id(), $clearchoicefieldname]);
[$questiondivid]);
return $result;
}
@@ -0,0 +1,42 @@
@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"
@@ -97,73 +97,4 @@ class qtype_multichoice_walkthrough_test extends qbehaviour_walkthrough_test_bas
new question_pattern_expectation('/class="r0 correct"/'),
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"/'));
}
}