Commit e06b302e authored by bas's avatar bas
Browse files

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
parent 37b2ee3f
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
......@@ -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_BUTTON: 'button[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);
root.on(CustomEvents.events.activate, SELECTORS.CLEAR_CHOICE_ELEMENT, function(e, data) {
var registerEventListeners = function(root) {
// 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);
var clearChoiceButton = root.find(SELECTORS.CLEARRESULTS_BUTTON);
data.originalEvent.preventDefault();
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();
});
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 {
......
......@@ -282,35 +282,25 @@ 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.
$cssclass = '';
if (!$hascheckedchoice && $response == -1) {
$cssclass .= ' sr-only';
$clearchoiceradioattrs['checked'] = 'checked';
$cssclass = '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]);
$clearchoicebutton = html_writer::tag('button', get_string('clearchoice', 'qtype_multichoice'), [
'class' => 'btn btn-link ml-3 ' . $cssclass,
'data-action' => 'clearresults',
'data-target' => '#' . $questiondivid
]);
// 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;
return $clearchoicebutton;
}
}
......
@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"/'));
}
}
......@@ -310,14 +310,6 @@ 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 {
......
......@@ -14964,13 +14964,6 @@ 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;
......
......@@ -15184,13 +15184,6 @@ 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;
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment