MDL-77856 qtype_multianswer: Use Bootstrap Popover for subq feedback

The YUI Overlay widget encloses the subquestion feedback in a div
which causes a div element to be enclosed in the subquestion span. This
leads to an accessibility issue in terms of HTML parsing as inline
elements (span) should not contain block elements (div)
The YUI Overlay widget is also not accessible as it does not really hide
the overlay contents via aria-hidden when the overlay is not being
shown. It's better if we stop using this and use Bootstrap's
popover component which is more accessible by default.

This patch also removes module.js for the qtype_multianswer plugin as
it only contains codes related to rendering the feedback contents in the
YUI overlay widget which is no longer necessary.
This commit is contained in:
Jun Pataleta 2023-04-04 00:03:00 +08:00
parent fba0658777
commit e3c0c2f2c2
3 changed files with 30 additions and 68 deletions

View File

@ -1,54 +0,0 @@
// 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/>.
/**
* JavaScript required by the multianswer question type.
*
* @package qtype
* @subpackage multianswer
* @copyright 2011 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
M.qtype_multianswer = M.qtype_multianswer || {};
M.qtype_multianswer.init = function (Y, questiondiv) {
Y.one(questiondiv).all('span.subquestion').each(function(subqspan) {
var feedbackspan = subqspan.one('.feedbackspan');
if (!feedbackspan) {
return;
}
var overlay = new Y.Overlay({
srcNode: feedbackspan,
visible: false,
align: {
node: subqspan,
points: [Y.WidgetPositionAlign.TC, Y.WidgetPositionAlign.BC]
},
constrain: subqspan.ancestor('div.que'),
zIndex: 1,
preventOverlap: true
});
overlay.render();
Y.on('mouseover', function() { overlay.show(); }, subqspan);
Y.on('mouseout', function() { overlay.hide(); }, subqspan);
feedbackspan.removeClass('accesshide');
});
};

View File

@ -88,13 +88,6 @@ class qtype_multianswer_renderer extends qtype_renderer {
array('class' => 'validationerror'));
}
$this->page->requires->js_init_call('M.qtype_multianswer.init',
array('#' . $qa->get_outer_question_div_unique_id()), false, array(
'name' => 'qtype_multianswer',
'fullpath' => '/question/type/multianswer/module.js',
'requires' => array('base', 'node', 'event', 'overlay'),
));
return $output;
}
@ -202,8 +195,33 @@ abstract class qtype_multianswer_subq_renderer_base extends qtype_renderer {
return '';
}
return html_writer::tag('span', implode('<br />', $feedback),
array('class' => 'feedbackspan accesshide'));
return html_writer::tag('span', implode('<br />', $feedback), [
'class' => 'feedbackspan',
]);
}
/**
* Render the feedback icon for a sub-question which is also the trigger for the feedback popover.
*
* @param string $icon The feedback icon
* @param string $feedbackcontents The feedback contents to be shown on the popover.
* @return string
*/
protected function get_feedback_image(string $icon, string $feedbackcontents): string {
if ($icon === '') {
return '';
}
return html_writer::tag('button', $icon, [
'type' => 'button',
'class' => 'btn btn-link p-0',
'data-toggle' => 'popover',
'data-container' => 'body',
'data-content' => $feedbackcontents,
'data-placement' => 'right',
'data-trigger' => 'focus',
'data-html' => 'true',
]);
}
/**
@ -315,8 +333,7 @@ class qtype_multianswer_textfield_renderer extends qtype_multianswer_subq_render
$output .= html_writer::tag('label', $this->get_answer_label(),
array('class' => 'subq accesshide', 'for' => $inputattributes['id']));
$output .= html_writer::empty_tag('input', $inputattributes);
$output .= $feedbackimg;
$output .= $feedbackpopup;
$output .= $this->get_feedback_image($feedbackimg, $feedbackpopup);
$output .= html_writer::end_tag('span');
return $output;
@ -385,8 +402,7 @@ class qtype_multianswer_multichoice_inline_renderer
$output .= html_writer::tag('label', $this->get_answer_label(),
array('class' => 'subq accesshide', 'for' => $inputattributes['id']));
$output .= $select;
$output .= $feedbackimg;
$output .= $feedbackpopup;
$output .= $this->get_feedback_image($feedbackimg, $feedbackpopup);
$output .= html_writer::end_tag('span');
return $output;

View File

@ -41,7 +41,7 @@ class walkthrough_test extends \qbehaviour_walkthrough_test_base {
protected function get_contains_subq_status(question_state $state) {
return new \question_pattern_expectation('~' .
preg_quote($state->default_string(true), '~') . '<br />~');
preg_quote($state->default_string(true), '~') . '~');
}
public function test_deferred_feedback() {