Skip to content

Commit

Permalink
MDL-76849 qtype_multianswer: Include question number in answer fields
Browse files Browse the repository at this point in the history
* Add the question number to the answer fields if it's available.
* Improve multiple choice question accessibility:
  - Label the multiple choice question groups appropriately by
    enclosing them in fieldset tags and applying sr-only legend tags to
    label them.
  - Apply Bootstrap form-check classes to the radio buttons, so they
    are rendered better and become responsive as well. This also helps
    avoid the use of the table element for layout purposes when
    rendering horizontal multiple choice sub-questions.
  • Loading branch information
junpataleta committed Mar 9, 2023
1 parent 716c223 commit 9ba9723
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 12 deletions.
1 change: 1 addition & 0 deletions question/type/multianswer/lang/en/qtype_multianswer.php
Expand Up @@ -38,6 +38,7 @@
$string['layoutundefined'] = 'Undefined layout';
$string['layoutvertical'] = 'Vertical column of radio buttons';
$string['missingsubquestion'] = 'This subquestion is missing from your system and cannot be displayed.';
$string['multichoicex'] = 'Multiple choice {$a}';
$string['nooptionsforsubquestion'] = 'Unable to get options for question part # {$a->sub} (question->id={$a->id})';
$string['noquestions'] = 'The Cloze(multianswer) question "<strong>{$a}</strong>" does not contain any question';
$string['pleaseananswerallparts'] = 'Please answer all parts of the question.';
Expand Down
73 changes: 61 additions & 12 deletions question/type/multianswer/renderer.php
Expand Up @@ -128,6 +128,7 @@ public function subquestion(question_attempt $qa,
} else {
throw new coding_exception('Unexpected subquestion type.', $subq);
}
/** @var qtype_multianswer_subq_renderer_base $renderer */
$renderer = $this->page->get_renderer('qtype_multianswer', $subrenderer);
return $renderer->subquestion($qa, $options, $index, $subq);
}
Expand All @@ -147,6 +148,12 @@ public function correct_response(question_attempt $qa) {
*/
abstract class qtype_multianswer_subq_renderer_base extends qtype_renderer {

/** @var int[] Stores the counts of answer instances for questions. */
protected static $answercount = [];

/** @var question_display_options Question display options instance for any necessary information for rendering the question. */
protected $displayoptions;

abstract public function subquestion(question_attempt $qa,
question_display_options $options, $index,
question_graded_automatically $subq);
Expand Down Expand Up @@ -198,6 +205,36 @@ protected function feedback_popup(question_graded_automatically $subq,
return html_writer::tag('span', implode('<br />', $feedback),
array('class' => 'feedbackspan accesshide'));
}

/**
* Generates a label for an answer field.
*
* If the question number is set ({@see qtype_renderer::$questionnumber}), the label will
* include the question number in order to indicate which question the answer field belongs to.
*
* @param string $langkey The lang string key for the lang string that does not include the question number.
* @param string $component The Frankenstyle component name.
* @return string
* @throws coding_exception
*/
protected function get_answer_label(
string $langkey = 'answerx',
string $component = 'question'
): string {
// There may be multiple answer fields for a question, so we need to increment the answer fields in order to distinguish
// them from one another.
$questionnumber = $this->displayoptions->questionidentifier ?? '';
$questionnumberindex = $questionnumber !== '' ? $questionnumber : 0;
if (isset(self::$answercount[$questionnumberindex][$langkey])) {
self::$answercount[$questionnumberindex][$langkey]++;
} else {
self::$answercount[$questionnumberindex][$langkey] = 1;
}

$params = self::$answercount[$questionnumberindex][$langkey];

return $this->displayoptions->add_question_identifier_to_label(get_string($langkey, $component, $params));
}
}


Expand All @@ -213,6 +250,8 @@ class qtype_multianswer_textfield_renderer extends qtype_multianswer_subq_render
public function subquestion(question_attempt $qa, question_display_options $options,
$index, question_graded_automatically $subq) {

$this->displayoptions = $options;

$fieldprefix = 'sub' . $index . '_';
$fieldname = $fieldprefix . 'answer';

Expand Down Expand Up @@ -272,7 +311,8 @@ public function subquestion(question_attempt $qa, question_display_options $opti
s($correctanswer->answer), $options);

$output = html_writer::start_tag('span', array('class' => 'subquestion form-inline d-inline'));
$output .= html_writer::tag('label', get_string('answer'),

$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;
Expand All @@ -296,6 +336,8 @@ class qtype_multianswer_multichoice_inline_renderer
public function subquestion(question_attempt $qa, question_display_options $options,
$index, question_graded_automatically $subq) {

$this->displayoptions = $options;

$fieldprefix = 'sub' . $index . '_';
$fieldname = $fieldprefix . 'answer';

Expand Down Expand Up @@ -340,7 +382,7 @@ public function subquestion(question_attempt $qa, question_display_options $opti
$qa, 'question', 'answer', $rightanswer->id), $options);

$output = html_writer::start_tag('span', array('class' => 'subquestion'));
$output .= html_writer::tag('label', get_string('answer'),
$output .= html_writer::tag('label', $this->get_answer_label(),
array('class' => 'subq accesshide', 'for' => $inputattributes['id']));
$output .= $select;
$output .= $feedbackimg;
Expand All @@ -364,13 +406,16 @@ class qtype_multianswer_multichoice_vertical_renderer extends qtype_multianswer_
public function subquestion(question_attempt $qa, question_display_options $options,
$index, question_graded_automatically $subq) {

$this->displayoptions = $options;

$fieldprefix = 'sub' . $index . '_';
$fieldname = $fieldprefix . 'answer';
$response = $qa->get_last_qt_var($fieldname);

$inputattributes = array(
'type' => 'radio',
'name' => $qa->get_qt_field_name($fieldname),
'class' => 'form-check-input',
);
if ($options->readonly) {
$inputattributes['disabled'] = 'disabled';
Expand All @@ -392,7 +437,7 @@ public function subquestion(question_attempt $qa, question_display_options $opti
unset($inputattributes['checked']);
}

$class = 'r' . ($value % 2);
$class = 'form-check text-wrap text-break';
if ($options->correctness && $isselected) {
$feedbackimg = $this->feedback_image($ans->fraction);
$class .= ' ' . $this->feedback_class($ans->fraction);
Expand All @@ -404,7 +449,7 @@ public function subquestion(question_attempt $qa, question_display_options $opti
$result .= html_writer::empty_tag('input', $inputattributes);
$result .= html_writer::tag('label', $subq->format_text($ans->answer,
$ans->answerformat, $qa, 'question', 'answer', $ansid),
array('for' => $inputattributes['id']));
array('for' => $inputattributes['id'], 'class' => 'form-check-label text-body'));
$result .= $feedbackimg;

if ($options->feedback && $isselected && trim($ans->feedback)) {
Expand Down Expand Up @@ -465,14 +510,17 @@ protected function choice_wrapper_end() {
* @return string HTML to go before all the choices.
*/
protected function all_choices_wrapper_start() {
return html_writer::start_tag('div', array('class' => 'answer'));
$wrapperstart = html_writer::start_tag('fieldset', array('class' => 'answer'));
$legendtext = $this->get_answer_label('multichoicex', 'qtype_multianswer');
$wrapperstart .= html_writer::tag('legend', $legendtext, ['class' => 'sr-only']);
return $wrapperstart;
}

/**
* @return string HTML to go after all the choices.
*/
protected function all_choices_wrapper_end() {
return html_writer::end_tag('div');
return html_writer::end_tag('fieldset');
}
}

Expand All @@ -488,21 +536,22 @@ class qtype_multianswer_multichoice_horizontal_renderer
extends qtype_multianswer_multichoice_vertical_renderer {

protected function choice_wrapper_start($class) {
return html_writer::start_tag('td', array('class' => $class));
return html_writer::start_tag('div', array('class' => $class . ' form-check-inline'));
}

protected function choice_wrapper_end() {
return html_writer::end_tag('td');
return html_writer::end_tag('div');
}

protected function all_choices_wrapper_start() {
return html_writer::start_tag('table', array('class' => 'answer')) .
html_writer::start_tag('tbody') . html_writer::start_tag('tr');
$wrapperstart = html_writer::start_tag('fieldset', ['class' => 'answer']);
$captiontext = $this->get_answer_label('multichoicex', 'qtype_multianswer');
$wrapperstart .= html_writer::tag('legend', $captiontext, ['class' => 'sr-only']);
return $wrapperstart;
}

protected function all_choices_wrapper_end() {
return html_writer::end_tag('tr') . html_writer::end_tag('tbody') .
html_writer::end_tag('table');
return html_writer::end_tag('fieldset');
}
}

Expand Down

0 comments on commit 9ba9723

Please sign in to comment.