MDL-77656 quiz editing: customisable numbers only in unshuffled sections

This works by always outputting both the customised and uncustomised
number into the HTML of the Questions page, then uses CSS & JS to
show the appropriate one as shuffle is toggled.
This commit is contained in:
Tim Hunt
2023-03-31 16:01:23 +01:00
parent d7462893b0
commit 24bbb18eb2
8 changed files with 67 additions and 61 deletions
+15 -9
View File
@@ -390,7 +390,10 @@ class edit_renderer extends \plugin_renderer_base {
$sectionstyle = '';
if ($structure->is_only_one_slot_in_section($section)) {
$sectionstyle = ' only-has-one-slot';
$sectionstyle .= ' only-has-one-slot';
}
if ($section->shufflequestions) {
$sectionstyle .= ' shuffled';
}
if ($section->heading) {
@@ -750,9 +753,8 @@ class edit_renderer extends \plugin_renderer_base {
}
if ($structure->can_display_number_be_customised($slot)) {
$questionnumbercustomised = $this->output->render($structure->make_slot_display_number_in_place_editable(
$questionnumber = $this->output->render($structure->make_slot_display_number_in_place_editable(
$slotid, $structure->get_context()));
$questionnumber = $questionnumbercustomised;
} else {
$questionnumber = $structure->get_displayed_number_for_slot($slot);
}
@@ -761,7 +763,7 @@ class edit_renderer extends \plugin_renderer_base {
'slotid' => $slotid,
'canbeedited' => $structure->can_be_edited(),
'checkbox' => $this->get_checkbox_render($structure, $slot),
'questionnumber' => $this->question_number($questionnumber),
'questionnumber' => $this->question_number($questionnumber, $structure->get_slot_by_number($slot)->defaultnumber),
'questionname' => $this->get_question_name_for_slot($structure, $slot, $pageurl),
'questionicons' => $this->get_action_icon($structure, $slot, $pageurl),
'questiondependencyicon' => ($structure->can_be_edited() ? $this->question_dependency_icon($structure, $slot) : ''),
@@ -865,14 +867,18 @@ class edit_renderer extends \plugin_renderer_base {
/**
* Output the question number.
* @param string $number The number, or 'i'.
*
* @param string $editablenumber The, which may be an in-place editable.
* @param string $uncustomisednumber The un-customised number number, or 'i'.
* @return string HTML to output.
*/
public function question_number($number) {
if ($number !== get_string('infoshort', 'quiz')) {
$number = html_writer::span(get_string('question'), 'accesshide') . ' ' . $number;
public function question_number(string $editablenumber, string $uncustomisednumber) {
if ($editablenumber !== get_string('infoshort', 'quiz')) {
$editablenumber = html_writer::span(get_string('question'), 'accesshide') . ' ' . $editablenumber;
$uncustomisednumber = html_writer::span(get_string('question'), 'accesshide') . ' ' . $uncustomisednumber;
}
return html_writer::tag('span', $number, ['class' => 'slotnumber']);
return html_writer::tag('span', $editablenumber, ['class' => 'slotnumber unshuffled']) .
html_writer::tag('span', $uncustomisednumber, ['class' => 'slotnumber shuffled']);
}
/**
+19 -33
View File
@@ -139,37 +139,32 @@ class structure {
* @return string the question number ot display for this slot.
*/
public function get_displayed_number_for_slot($slotnumber) {
return $this->slotsinorder[$slotnumber]->displayednumber;
$slot = $this->slotsinorder[$slotnumber];
return $slot->displaynumber ?? $slot->defaultnumber;
}
/**
* Check whether the question number can be customised.
* Check the question has a number that could be customised.
*
* @param int $slotnumber
* @return bool
*/
public function can_display_number_be_customised(int $slotnumber): bool {
if (!$this->is_real_question($slotnumber)) {
return false;
}
$slot = $this->get_slot_by_number($slotnumber);
if ($slot->section->shufflequestions) {
return false;
}
if (quiz_has_attempts($this->quizobj->get_quizid())) {
return false;
}
return true;
return $this->is_real_question($slotnumber) && !quiz_has_attempts($this->quizobj->get_quizid());
}
/**
* Check whether the question number is customised.
*
* @param int $slotid
* @return bool
* @todo MDL-76612 Final deprecation in Moodle 4.6
* @deprecated since 4.2. $slot->displayednumber is no longer used. If you need this,
* use isset(...->displaynumber), but this method was not used.
*/
public function is_display_number_customised(int $slotid): bool {
$slotobj = $this->get_slot_by_id($slotid);
return $slotobj->displayednumber === $slotobj->displaynumber;
return isset($slotobj->displaynumber);
}
/**
@@ -184,11 +179,7 @@ class structure {
$editable = has_capability('mod/quiz:manage', $context);
// Get the current value.
if ($this->is_display_number_customised($slotid)) {
$value = $slot->displaynumber;
} else {
$value = $this->get_displayed_number_for_slot($slot->slot);
}
$value = $slot->displaynumber ?? $slot->defaultnumber;
$displayvalue = s($value);
return new inplace_editable('mod_quiz', 'slotdisplaynumber', $slotid,
@@ -729,7 +720,6 @@ class structure {
global $DB;
$slots = qbank_helper::get_question_structure($this->quizobj->get_quizid(), $this->quizobj->get_context());
$this->questions = [];
$this->slotsinorder = [];
foreach ($slots as $slotdata) {
@@ -759,10 +749,6 @@ class structure {
}
for ($slot = $section->firstslot; $slot <= $section->lastslot; $slot += 1) {
$this->slotsinorder[$slot]->section = $section;
if ($section->shufflequestions) {
// Hide customised value and disable editing while shuffle checkbox is enabled.
$this->slotsinorder[$slot]->displaynumber = null;
}
}
}
}
@@ -773,17 +759,17 @@ class structure {
protected function populate_question_numbers() {
$number = 1;
foreach ($this->slotsinorder as $slot) {
if ($this->questions[$slot->questionid]->length == 0) {
$slot->displayednumber = get_string('infoshort', 'quiz');
$question = $this->questions[$slot->questionid];
if ($question->length == 0) {
$slot->displaynumber = null;
$slot->defaultnumber = get_string('infoshort', 'quiz');
} else {
// Whether question numbering is customised or is numeric and automatically incremented.
if (!empty($slot->displaynumber)) {
$slot->displayednumber = $slot->displaynumber;
} else {
$slot->displayednumber = $number;
}
$number += 1;
$slot->defaultnumber = $number;
}
if ($slot->displaynumber === '') {
$slot->displaynumber = null;
}
$number += $question->length;
}
}
+12
View File
@@ -560,6 +560,18 @@ table.quizreviewsummary td.cell {
min-width: 2em;
display: inline-block;
}
#page-mod-quiz-edit li.section .slotnumber.unshuffled {
display: inline-block;
}
#page-mod-quiz-edit li.section .slotnumber.shuffled {
display: none;
}
#page-mod-quiz-edit li.section.shuffled .slotnumber.unshuffled {
display: none;
}
#page-mod-quiz-edit li.section.shuffled .slotnumber.shuffled {
display: inline-block;
}
#page-mod-quiz-edit .section-heading {
margin-left: 20px;
@@ -93,8 +93,8 @@ Feature: Editing question numbering of the existing questions already in a quiz
And I should see question "abcdefghijklmnop" in section "Section 2" in the quiz navigation
Scenario: Shuffling questions within a section with customised question numbers.
Given I am on the "Quiz 1" "mod_quiz > Edit" page logged in as "teacher1"
And I should see "Section 1"
When I am on the "Quiz 1" "mod_quiz > Edit" page logged in as "teacher1"
Then I should see "Section 1"
And I should see "i" on quiz page "1"
And I should see "1.a" on quiz page "1"
And I should see "1.b" on quiz page "1"
@@ -102,8 +102,7 @@ Feature: Editing question numbering of the existing questions already in a quiz
And I should see "3" on quiz page "2"
And I should see "4" on quiz page "2"
When I set the field "Shuffle" to "1"
And I reload the page
And I set the field "Shuffle" to "1"
And I should see "Section 1"
And I should see "i" on quiz page "1"
And I should see "1" on quiz page "1"
@@ -111,9 +110,9 @@ Feature: Editing question numbering of the existing questions already in a quiz
And I should see "Section 2"
And I should see "3" on quiz page "2"
And I should see "4" on quiz page "2"
When I set the field "Shuffle" to "0"
And I reload the page
And I set the field "Shuffle" to "0"
And I should see "Section 1"
And I should see "i" on quiz page "1"
And I should see "1.a" on quiz page "1"
@@ -1133,17 +1133,21 @@ Y.extend(SECTIONTOOLBOX, TOOLBOX, {
* @param {EventFacade} ev The event that was fired.
* @param {Node} button The button that triggered this action.
* @param {Node} activity The activity node that this action will be performed on.
* @param {String} action The action that has been requested.
* @return Boolean
*/
edit_shuffle_questions: function(ev, button, activity) {
var newvalue;
if (activity.one(SELECTOR.EDITSHUFFLEQUESTIONSACTION).get('checked')) {
newvalue = 1;
activity.addClass('shuffled');
} else {
newvalue = 0;
activity.removeClass('shuffled');
}
// Prevent the default actions.
ev.preventDefault();
// Get the element we're working on
var data = {
'class': 'section',
@@ -1152,9 +1156,6 @@ Y.extend(SECTIONTOOLBOX, TOOLBOX, {
'newshuffle': newvalue
};
// Prevent the default actions.
ev.preventDefault();
// Send request.
var spinner = M.util.add_spinner(Y, activity.one(SELECTOR.EDITSHUFFLEAREA));
this.send_request(data, spinner);
File diff suppressed because one or more lines are too long
@@ -1133,17 +1133,21 @@ Y.extend(SECTIONTOOLBOX, TOOLBOX, {
* @param {EventFacade} ev The event that was fired.
* @param {Node} button The button that triggered this action.
* @param {Node} activity The activity node that this action will be performed on.
* @param {String} action The action that has been requested.
* @return Boolean
*/
edit_shuffle_questions: function(ev, button, activity) {
var newvalue;
if (activity.one(SELECTOR.EDITSHUFFLEQUESTIONSACTION).get('checked')) {
newvalue = 1;
activity.addClass('shuffled');
} else {
newvalue = 0;
activity.removeClass('shuffled');
}
// Prevent the default actions.
ev.preventDefault();
// Get the element we're working on
var data = {
'class': 'section',
@@ -1152,9 +1156,6 @@ Y.extend(SECTIONTOOLBOX, TOOLBOX, {
'newshuffle': newvalue
};
// Prevent the default actions.
ev.preventDefault();
// Send request.
var spinner = M.util.add_spinner(Y, activity.one(SELECTOR.EDITSHUFFLEAREA));
this.send_request(data, spinner);
+5 -4
View File
@@ -317,17 +317,21 @@ Y.extend(SECTIONTOOLBOX, TOOLBOX, {
* @param {EventFacade} ev The event that was fired.
* @param {Node} button The button that triggered this action.
* @param {Node} activity The activity node that this action will be performed on.
* @param {String} action The action that has been requested.
* @return Boolean
*/
edit_shuffle_questions: function(ev, button, activity) {
var newvalue;
if (activity.one(SELECTOR.EDITSHUFFLEQUESTIONSACTION).get('checked')) {
newvalue = 1;
activity.addClass('shuffled');
} else {
newvalue = 0;
activity.removeClass('shuffled');
}
// Prevent the default actions.
ev.preventDefault();
// Get the element we're working on
var data = {
'class': 'section',
@@ -336,9 +340,6 @@ Y.extend(SECTIONTOOLBOX, TOOLBOX, {
'newshuffle': newvalue
};
// Prevent the default actions.
ev.preventDefault();
// Send request.
var spinner = M.util.add_spinner(Y, activity.one(SELECTOR.EDITSHUFFLEAREA));
this.send_request(data, spinner);