Merge branch 'MDL-80747-main' of https://github.com/rezaies/moodle
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
issueNumber: MDL-80747
|
||||
notes:
|
||||
core:
|
||||
- message: >
|
||||
The core\output\select_menu widget now supports rendering dividers
|
||||
between menu options. Empty elements (null or empty strings) within the
|
||||
array of options are considered and rendered as dividers in the dropdown
|
||||
menu.
|
||||
type: improved
|
||||
@@ -0,0 +1,9 @@
|
||||
issueNumber: MDL-80747
|
||||
notes:
|
||||
core:
|
||||
- message: >
|
||||
The `core\output\select_menu` widget now supports a new feature: inline
|
||||
labels. You can render the label inside the combobox widget by passing
|
||||
`true` to the `$inlinelabel` parameter when calling the `->set_label()`
|
||||
method.
|
||||
type: improved
|
||||
@@ -53,6 +53,14 @@ class behat_form_select_menu extends behat_form_field {
|
||||
return $input->getValue();
|
||||
}
|
||||
|
||||
public function matches($expectedvalue) {
|
||||
$actualvalue = $this->get_value();
|
||||
$selectedcontainer = $this->field->find('css', '[data-selected-option]');
|
||||
$actualtext = $selectedcontainer ? $selectedcontainer->getText() : $this->field->getText();
|
||||
|
||||
return ($expectedvalue == $actualvalue || $expectedvalue == $actualtext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a given option exists in the select menu field.
|
||||
*
|
||||
|
||||
@@ -39,6 +39,9 @@ class select_menu implements renderable, templatable {
|
||||
/** @var array Button label's attributes */
|
||||
protected $labelattributes = [];
|
||||
|
||||
/** @var bool Whether the label is inline or not */
|
||||
protected $inlinelabel = false;
|
||||
|
||||
/** @var string Name of the combobox element */
|
||||
protected $name;
|
||||
|
||||
@@ -47,7 +50,7 @@ class select_menu implements renderable, templatable {
|
||||
*
|
||||
* @param string $name Name of the combobox element
|
||||
* @param array $options List of options in an associative array format like ['val' => 'Option'].
|
||||
* Supports grouped options as well.
|
||||
* Supports grouped options as well. Empty string or null values will be rendered as dividers.
|
||||
* @param string|null $selected The value of the preselected option.
|
||||
*/
|
||||
public function __construct(string $name, array $options, ?string $selected = null) {
|
||||
@@ -61,10 +64,12 @@ class select_menu implements renderable, templatable {
|
||||
*
|
||||
* @param string $label The label.
|
||||
* @param array $attributes List of attributes to apply on the label element.
|
||||
* @param bool $inlinelabel Whether the label is inline or not.
|
||||
*/
|
||||
public function set_label(string $label, array $attributes = []) {
|
||||
public function set_label(string $label, array $attributes = [], bool $inlinelabel = false) {
|
||||
$this->label = $label;
|
||||
$this->labelattributes = $attributes;
|
||||
$this->inlinelabel = $inlinelabel;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,21 +92,29 @@ class select_menu implements renderable, templatable {
|
||||
];
|
||||
}
|
||||
foreach ($optoptions as $optvalue => $optoption) {
|
||||
$flattened[$groupname]['options'][$optvalue] = [
|
||||
'name' => $optoption,
|
||||
'value' => $optvalue,
|
||||
'selected' => $this->selected == $optvalue,
|
||||
'id' => \html_writer::random_id('select-menu-option'),
|
||||
];
|
||||
if (empty($optoption)) {
|
||||
$flattened[$groupname]['options'][$optvalue] = ['isdivider' => true];
|
||||
} else {
|
||||
$flattened[$groupname]['options'][$optvalue] = [
|
||||
'name' => $optoption,
|
||||
'value' => $optvalue,
|
||||
'selected' => $this->selected == $optvalue,
|
||||
'id' => \html_writer::random_id('select-menu-option'),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$flattened[$value] = [
|
||||
'name' => $option,
|
||||
'value' => $value,
|
||||
'selected' => $this->selected == $value,
|
||||
'id' => \html_writer::random_id('select-menu-option'),
|
||||
];
|
||||
if (empty($option)) {
|
||||
$flattened[$value] = ['isdivider' => true];
|
||||
} else {
|
||||
$flattened[$value] = [
|
||||
'name' => $option,
|
||||
'value' => $value,
|
||||
'selected' => $this->selected == $value,
|
||||
'id' => \html_writer::random_id('select-menu-option'),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,6 +166,7 @@ class select_menu implements renderable, templatable {
|
||||
$data = new \stdClass();
|
||||
$data->baseid = \html_writer::random_id('select-menu');
|
||||
$data->label = $this->label;
|
||||
$data->inlinelabel = $this->inlinelabel;
|
||||
$data->options = $this->flatten_options();
|
||||
$data->selectedoption = $this->get_selected_option();
|
||||
$data->name = $this->name;
|
||||
|
||||
@@ -85,11 +85,11 @@
|
||||
}
|
||||
}}
|
||||
<div class="dropdown select-menu" id="{{baseid}}">
|
||||
{{#label}}
|
||||
{{#label}}{{^inlinelabel}}
|
||||
<label id="{{baseid}}-label"{{#labelattributes}} {{name}}="{{value}}"{{/labelattributes}}>{{label}}</label>
|
||||
{{/label}}
|
||||
{{/inlinelabel}}{{/label}}
|
||||
<div
|
||||
class="btn dropdown-toggle"
|
||||
class="btn dropdown-toggle{{#inlinelabel}} d-flex text-left align-items-center p-0{{/inlinelabel}}"
|
||||
role="combobox"
|
||||
data-toggle="dropdown"
|
||||
{{#label}}aria-labelledby="{{baseid}}-label"{{/label}}
|
||||
@@ -99,7 +99,19 @@
|
||||
data-input-element="{{baseid}}-input"
|
||||
tabindex="0"
|
||||
>
|
||||
{{selectedoption}}
|
||||
{{#inlinelabel}}
|
||||
<div class="pr-3 text-truncate">
|
||||
{{#label}}
|
||||
<label class="d-block m-0 small" id="{{baseid}}-label"{{#labelattributes}} {{name}}="{{value}}"{{/labelattributes}}>{{label}}</label>
|
||||
{{/label}}
|
||||
<span class="font-weight-bold" data-selected-option>
|
||||
{{selectedoption}}
|
||||
</span>
|
||||
</div>
|
||||
{{/inlinelabel}}
|
||||
{{^inlinelabel}}
|
||||
{{selectedoption}}
|
||||
{{/inlinelabel}}
|
||||
</div>
|
||||
<ul class="dropdown-menu" role="listbox" id="{{baseid}}-listbox" {{#label}}aria-labelledby="{{baseid}}-label"{{/label}}>
|
||||
{{#options}}
|
||||
@@ -108,17 +120,27 @@
|
||||
<ul role="group" aria-labelledby="{{id}}">
|
||||
<li role="presentation" id="{{id}}">{{name}}</li>
|
||||
{{#options}}
|
||||
<li class="dropdown-item" role="option" id="{{id}}" data-value="{{value}}" {{#selected}}aria-selected="true"{{/selected}}>
|
||||
{{name}}
|
||||
</li>
|
||||
{{#isdivider}}
|
||||
<li role="separator" class="dropdown-divider"></li>
|
||||
{{/isdivider}}
|
||||
{{^isdivider}}
|
||||
<li class="dropdown-item" role="option" id="{{id}}" data-value="{{value}}" {{#selected}}aria-selected="true"{{/selected}}>
|
||||
{{name}}
|
||||
</li>
|
||||
{{/isdivider}}
|
||||
{{/options}}
|
||||
</ul>
|
||||
</li>
|
||||
{{/isgroup}}
|
||||
{{^isgroup}}
|
||||
<li class="dropdown-item" role="option" id="{{id}}" data-value="{{value}}" {{#selected}}aria-selected="true"{{/selected}}>
|
||||
{{name}}
|
||||
</li>
|
||||
{{#isdivider}}
|
||||
<li role="separator" class="dropdown-divider"></li>
|
||||
{{/isdivider}}
|
||||
{{^isdivider}}
|
||||
<li class="dropdown-item" role="option" id="{{id}}" data-value="{{value}}" {{#selected}}aria-selected="true"{{/selected}}>
|
||||
{{name}}
|
||||
</li>
|
||||
{{/isdivider}}
|
||||
{{/isgroup}}
|
||||
{{/options}}
|
||||
</ul>
|
||||
|
||||
@@ -63,15 +63,6 @@ class grading_options_temp_form extends \moodleform {
|
||||
}
|
||||
}
|
||||
$mform->addElement('select', 'perpage', get_string('assignmentsperpage', 'assign'), $options);
|
||||
$options = array('' => get_string('filternone', 'assign'),
|
||||
ASSIGN_FILTER_NOT_SUBMITTED => get_string('filternotsubmitted', 'assign'),
|
||||
ASSIGN_FILTER_DRAFT => get_string('filterdraft', 'assign'),
|
||||
ASSIGN_FILTER_SUBMITTED => get_string('filtersubmitted', 'assign'),
|
||||
ASSIGN_FILTER_REQUIRE_GRADING => get_string('filterrequiregrading', 'assign'),
|
||||
ASSIGN_FILTER_GRANTED_EXTENSION => get_string('filtergrantedextension', 'assign'));
|
||||
if ($instance['submissionsenabled']) {
|
||||
$mform->addElement('select', 'filter', get_string('filter', 'assign'), $options);
|
||||
}
|
||||
if (!empty($instance['markingallocationopt'])) {
|
||||
$markingfilter = get_string('markerfilter', 'assign');
|
||||
$mform->addElement('select', 'markerfilter', $markingfilter, $instance['markingallocationopt']);
|
||||
|
||||
@@ -113,16 +113,28 @@ class grading_actionmenu implements templatable, renderable {
|
||||
$data['extrafiltersdropdown'] = $OUTPUT->render($extrafiltersdropdown);
|
||||
}
|
||||
|
||||
if (groups_get_activity_group($cm) || $this->get_applied_extra_filters_count() > 0) {
|
||||
$activitygroup = groups_get_activity_group($cm);
|
||||
$hasuserfilter = get_user_preferences('assign_filter');
|
||||
$hasextrafilters = $this->get_applied_extra_filters_count() > 0;
|
||||
if ($activitygroup || $hasuserfilter || $hasextrafilters) {
|
||||
$url = new moodle_url('/mod/assign/view.php', [
|
||||
'id' => $this->cmid,
|
||||
'action' => 'grading',
|
||||
'group' => 0,
|
||||
'status' => '',
|
||||
'workflowfilter' => '',
|
||||
'sesskey' => sesskey(),
|
||||
]);
|
||||
$data['pagereset'] = $url->out(false);
|
||||
}
|
||||
|
||||
if ($this->assign->is_any_submission_plugin_enabled()) {
|
||||
['statusmenu' => $statusmenu, 'currentvalue' => $currentvalue] = $this->get_status_menu();
|
||||
$statusselect = new \core\output\select_menu('status', $statusmenu, $currentvalue);
|
||||
$statusselect->set_label(get_string('status', 'mod_assign'), [], true);
|
||||
$data['statusselector'] = $statusselect->export_for_template($output);
|
||||
}
|
||||
|
||||
if ($this->assign->can_grade()) {
|
||||
$url = new moodle_url('/mod/assign/view.php', [
|
||||
'id' => $this->assign->get_course_module()->id,
|
||||
@@ -199,6 +211,45 @@ class grading_actionmenu implements templatable, renderable {
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the status menu for the grading action menu.
|
||||
*
|
||||
* @return array An array containing the status menu and the current value.
|
||||
*/
|
||||
private function get_status_menu(): array {
|
||||
$statusmenu = [];
|
||||
$currentvalue = '';
|
||||
|
||||
$groupedfilters = $this->assign->get_filters(true);
|
||||
foreach ($groupedfilters as $group => $filters) {
|
||||
foreach ($filters as $filter) {
|
||||
if ($filter['key'] === 'none') {
|
||||
// The 'none' filter is not a real filter.
|
||||
$filter['key'] = '';
|
||||
}
|
||||
$url = new moodle_url('/mod/assign/view.php', [
|
||||
'id' => $this->assign->get_course_module()->id,
|
||||
'action' => 'grading',
|
||||
'sesskey' => sesskey(),
|
||||
'status' => $filter['key'],
|
||||
]);
|
||||
$statusmenu[$url->out(false)] = $filter['name'];
|
||||
|
||||
if ($filter['active']) {
|
||||
$currentvalue = $url->out(false);
|
||||
}
|
||||
}
|
||||
if ($group !== array_key_last($groupedfilters)) {
|
||||
$statusmenu[] = '';
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'statusmenu' => $statusmenu,
|
||||
'currentvalue' => $currentvalue,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* The renderable for the extra filters dropdown, if available.
|
||||
*
|
||||
|
||||
@@ -293,6 +293,7 @@ $string['feedbacksettings'] = 'Feedback settings';
|
||||
$string['feedbacktypes'] = 'Feedback types';
|
||||
$string['filesubmissions'] = 'File submissions';
|
||||
$string['filter'] = 'Filter';
|
||||
$string['filterall'] = 'All';
|
||||
$string['filterdraft'] = 'Draft';
|
||||
$string['filtergrantedextension'] = 'Granted extension';
|
||||
$string['filternone'] = 'No filter';
|
||||
|
||||
+36
-23
@@ -4484,6 +4484,12 @@ class assign {
|
||||
require_once($CFG->dirroot . '/mod/assign/gradingoptionsform.php');
|
||||
require_once($CFG->dirroot . '/mod/assign/quickgradingform.php');
|
||||
require_once($CFG->dirroot . '/mod/assign/gradingbatchoperationsform.php');
|
||||
|
||||
$submittedfilter = optional_param('status', null, PARAM_ALPHA);
|
||||
if (isset($submittedfilter) && confirm_sesskey()) {
|
||||
set_user_preference('assign_filter', $submittedfilter);
|
||||
}
|
||||
|
||||
$o = '';
|
||||
$cmid = $this->get_course_module()->id;
|
||||
|
||||
@@ -4570,7 +4576,6 @@ class assign {
|
||||
|
||||
$gradingoptionsdata = new stdClass();
|
||||
$gradingoptionsdata->perpage = $perpage;
|
||||
$gradingoptionsdata->filter = $filter;
|
||||
$gradingoptionsdata->markerfilter = $markerfilter;
|
||||
$gradingoptionsform->set_data($gradingoptionsdata);
|
||||
|
||||
@@ -7422,9 +7427,6 @@ class assign {
|
||||
$mform = new mod_assign\form\grading_options_temp_form(null, $gradingoptionsparams);
|
||||
if ($formdata = $mform->get_data()) {
|
||||
set_user_preference('assign_perpage', $formdata->perpage);
|
||||
if (isset($formdata->filter)) {
|
||||
set_user_preference('assign_filter', $formdata->filter);
|
||||
}
|
||||
if (isset($formdata->markerfilter)) {
|
||||
set_user_preference('assign_markerfilter', $formdata->markerfilter);
|
||||
}
|
||||
@@ -9659,35 +9661,46 @@ class assign {
|
||||
/**
|
||||
* Return array of valid search filters for the grading interface.
|
||||
*
|
||||
* @param bool $grouped Whether to return the filters grouped or not.
|
||||
* @return array
|
||||
*/
|
||||
public function get_filters() {
|
||||
$filterkeys = [
|
||||
ASSIGN_FILTER_NOT_SUBMITTED,
|
||||
ASSIGN_FILTER_DRAFT,
|
||||
ASSIGN_FILTER_SUBMITTED,
|
||||
ASSIGN_FILTER_REQUIRE_GRADING,
|
||||
ASSIGN_FILTER_GRANTED_EXTENSION
|
||||
public function get_filters(bool $grouped = false): array {
|
||||
$groupedfilterkeys = [
|
||||
[
|
||||
ASSIGN_FILTER_NOT_SUBMITTED,
|
||||
ASSIGN_FILTER_DRAFT,
|
||||
ASSIGN_FILTER_SUBMITTED,
|
||||
ASSIGN_FILTER_REQUIRE_GRADING,
|
||||
],
|
||||
[
|
||||
ASSIGN_FILTER_GRANTED_EXTENSION,
|
||||
],
|
||||
];
|
||||
|
||||
$current = get_user_preferences('assign_filter', '');
|
||||
|
||||
$filters = [];
|
||||
// First is always "no filter" option.
|
||||
array_push($filters, [
|
||||
'key' => 'none',
|
||||
'name' => get_string('filternone', 'assign'),
|
||||
'active' => ($current == '')
|
||||
]);
|
||||
$filters[0] = [
|
||||
[
|
||||
'key' => 'none',
|
||||
'name' => get_string('filterall', 'assign'),
|
||||
'active' => ($current == ''),
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($filterkeys as $key) {
|
||||
array_push($filters, [
|
||||
'key' => $key,
|
||||
'name' => get_string('filter' . $key, 'assign'),
|
||||
'active' => ($current == $key)
|
||||
]);
|
||||
foreach ($groupedfilterkeys as $group => $filterkeys) {
|
||||
foreach ($filterkeys as $key) {
|
||||
$filters[$group] = $filters[$group] ?? [];
|
||||
$filters[$group][] = [
|
||||
'key' => $key,
|
||||
'name' => get_string('filter' . $key, 'assign'),
|
||||
'active' => ($current == $key),
|
||||
];
|
||||
}
|
||||
}
|
||||
return $filters;
|
||||
|
||||
return $grouped ? $filters : array_merge(...$filters);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -131,12 +131,6 @@ M.mod_assign.init_grading_options = function(Y) {
|
||||
paginationelement.on('change', function(e) {
|
||||
Y.one('form.gradingoptionsform').submit();
|
||||
});
|
||||
var filterelement = Y.one('#id_filter');
|
||||
if (filterelement) {
|
||||
filterelement.on('change', function(e) {
|
||||
Y.one('form.gradingoptionsform').submit();
|
||||
});
|
||||
}
|
||||
var markerfilterelement = Y.one('#id_markerfilter');
|
||||
if (markerfilterelement) {
|
||||
markerfilterelement.on('change', function(e) {
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
Context variables required for this template:
|
||||
* userselector - HTML that outputs the user selector
|
||||
* groupselector - (optional) HTML that outputs the group selector
|
||||
* statusselector - (optional) The data object containing the required properties to render the status selector
|
||||
* extrafiltersdropdown - HTML that outputs the extra filters dropdown
|
||||
* actions - (optional) HTML that outputs the bulk action menu
|
||||
|
||||
@@ -32,6 +33,20 @@
|
||||
{
|
||||
"userselector": "<div class='user-search'></div>",
|
||||
"groupselector": "<div class='group-selector'></div>",
|
||||
"statusselector": {
|
||||
"name": "filter",
|
||||
"value": "submitted",
|
||||
"baseid": "select-menu56789",
|
||||
"label": "Status",
|
||||
"inlinelabel": true,
|
||||
"selectedoption": "Not submitted",
|
||||
"options": [
|
||||
{"name": "All", "value": "", "selected": false, "id": "select-menu-option56789a"},
|
||||
{"name": "Not submitted", "value": "notsubmitted", "selected": true, "id": "select-menu-option56789b"},
|
||||
{"isdivider": true},
|
||||
{"name": "granted extention", "value": "grantedextension", "selected": false, "id": "select-menu-option56789d"}
|
||||
]
|
||||
},
|
||||
"extrafiltersdropdown": "<div class='dropdown extrafilters'></div>",
|
||||
"pagereset": "http://moodle.local/mod/assign/view.php?id=2&action=grading&group=0",
|
||||
"actions": "<div class='action-menu'></div>"
|
||||
@@ -56,6 +71,17 @@
|
||||
</div>
|
||||
<div class="navitem-divider d-none d-sm-flex"></div>
|
||||
{{/groupselector}}
|
||||
{{#statusselector}}
|
||||
<div class="navitem">
|
||||
{{>core/select_menu}}
|
||||
</div>
|
||||
<div class="navitem-divider d-none d-sm-flex"></div>
|
||||
{{#js}}
|
||||
document.querySelector('#{{baseid}}').addEventListener('change', function(e) {
|
||||
window.location.href = e.target.value;
|
||||
});
|
||||
{{/js}}
|
||||
{{/statusselector}}
|
||||
{{#extrafiltersdropdown}}
|
||||
<div class="navitem">
|
||||
{{{.}}}
|
||||
|
||||
@@ -43,7 +43,7 @@ Feature: In an assignment, teachers can filter displayed submissions and see dra
|
||||
Scenario: View assignments with draft status on the view all submissions page
|
||||
Given I am on the "Test assignment" Activity page logged in as teacher1
|
||||
And I navigate to "Submissions" in current page administration
|
||||
When I set the field "Filter" to "Draft"
|
||||
When I set the field "Status" to "Draft"
|
||||
Then I should see "Student 2"
|
||||
And I should not see "Student 1"
|
||||
And I should not see "Student 3"
|
||||
|
||||
@@ -47,7 +47,7 @@ Feature: In an assignment, teachers can change filters in the grading app
|
||||
|
||||
And I am on the "Test assignment name &" Activity page
|
||||
And I navigate to "Submissions" in current page administration
|
||||
And I set the field "filter" to "Not submitted"
|
||||
And I set the field "Status" to "Not submitted"
|
||||
And I set the field "markerfilter" to "Marker 1"
|
||||
And I click on "Advanced" "button" in the ".tertiary-navigation" "css_element"
|
||||
And I set the field "Marking state" in the ".extrafilters .dropdown-menu" "css_element" to "In marking"
|
||||
@@ -77,7 +77,7 @@ Feature: In an assignment, teachers can change filters in the grading app
|
||||
And I set the field "markerfilter" to "Marker 1"
|
||||
And I set the field "workflowfilter" to "In marking"
|
||||
And I click on "View all submissions" "link"
|
||||
Then the field "filter" matches value "Not submitted"
|
||||
Then the field "Status" matches value "Not submitted"
|
||||
And the field "markerfilter" matches value "Marker 1"
|
||||
And I click on "Advanced" "button" in the ".tertiary-navigation" "css_element"
|
||||
And the field "Marking state" matches value "In marking"
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -380,7 +380,12 @@ const comboboxFix = () => {
|
||||
if (combobox.hasAttribute('value')) {
|
||||
combobox.value = option.dataset.shortText || option.textContent.replace(/[\n\r]+|[\s]{2,}/g, ' ').trim();
|
||||
} else {
|
||||
combobox.textContent = option.dataset.shortText || option.textContent;
|
||||
const selectedOptionContainer = combobox.querySelector('[data-selected-option]');
|
||||
if (selectedOptionContainer) {
|
||||
selectedOptionContainer.textContent = option.dataset.shortText || option.textContent;
|
||||
} else {
|
||||
combobox.textContent = option.dataset.shortText || option.textContent;
|
||||
}
|
||||
}
|
||||
|
||||
if (combobox.dataset.inputElement) {
|
||||
|
||||
Reference in New Issue
Block a user