MDL-76134 gradebook: Hook into tertiary dropdown item
This commit is contained in:
+10
@@ -0,0 +1,10 @@
|
||||
define("core_grades/searchwidget/initials",["exports","core/pending","core/url","core/custom_interaction_events","jquery"],(function(_exports,_pending,Url,_custom_interaction_events,_jquery){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
|
||||
/**
|
||||
* A small dropdown to filter users within the gradebook.
|
||||
*
|
||||
* @module core_grades/searchwidget/initials
|
||||
* @copyright 2022 Mathew May <mathew.solutions>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_pending=_interopRequireDefault(_pending),Url=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(Url),_custom_interaction_events=_interopRequireDefault(_custom_interaction_events),_jquery=_interopRequireDefault(_jquery);let registered=!1;const selectors_pageListItem="page-item",selectors_pageClickableItem=".page-link",selectors_activeItem="active",selectors_formDropdown=".initialsdropdownform",selectors_parentDomNode=".initials-selector",selectors_firstInitial="firstinitial",selectors_lastInitial="lastinitial",selectors_initialBars=".initialbar",selectors_targetButton="initialswidget",selectors_formItems={type:"submit",save:"save",cancel:"cancel"};_exports.init=callingLink=>{if(registered)return;const pendingPromise=new _pending.default;registerListenerEvents(callingLink),(0,_jquery.default)(selectors_parentDomNode).on("shown.bs.dropdown",(()=>{document.querySelector(selectors_pageClickableItem).focus({preventScroll:!0})})),pendingPromise.resolve(),registered=!0};const registerListenerEvents=callingLink=>{const events=["click",_custom_interaction_events.default.events.activate,_custom_interaction_events.default.events.keyboardActivate];_custom_interaction_events.default.define(document,events),events.forEach((event=>{document.addEventListener(event,(e=>{let{firstActive:firstActive,lastActive:lastActive,sifirst:sifirst,silast:silast}=onClickVariables(),itemToReset="";if(e.target.closest(selectors_formDropdown)&&e.preventDefault(),e.target.closest("".concat(selectors_formDropdown," .").concat(selectors_pageListItem))){if(e.target.classList.contains(selectors_pageListItem))return;e.target.closest(selectors_initialBars).classList.contains(selectors_firstInitial)?(sifirst=e.target,itemToReset=firstActive):(silast=e.target,itemToReset=lastActive),swapActiveItems(itemToReset,e)}e.target.type===selectors_formItems.type&&(e.target.dataset.action===selectors_formItems.save&&(window.location=Url.relativeUrl(callingLink,{id:e.target.closest(selectors_formDropdown).dataset.courseid,sifirst:sifirst.parentElement.classList.contains("initialbarall")?"":sifirst.value,silast:silast.parentElement.classList.contains("initialbarall")?"":silast.value})),e.target.dataset.action===selectors_formItems.cancel&&(0,_jquery.default)(".".concat(selectors_targetButton)).dropdown("toggle"))}))}))},onClickVariables=()=>{const firstItems=[...document.querySelectorAll(".".concat(selectors_firstInitial," li"))],lastItems=[...document.querySelectorAll(".".concat(selectors_lastInitial," li"))],firstActive=firstItems.filter((item=>item.classList.contains(selectors_activeItem)))[0],lastActive=lastItems.filter((item=>item.classList.contains(selectors_activeItem)))[0];let sifirst=firstActive.querySelector(selectors_pageClickableItem),silast=lastActive.querySelector(selectors_pageClickableItem);return{firstActive:firstActive,lastActive:lastActive,sifirst:sifirst,silast:silast}},swapActiveItems=(itemToReset,e)=>{itemToReset.classList.remove(selectors_activeItem),itemToReset.querySelector(selectors_pageClickableItem).ariaCurrent=!1;e.target.parentElement.classList.add(selectors_activeItem),e.target.ariaCurrent=!0}}));
|
||||
|
||||
//# sourceMappingURL=initials.min.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,169 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* A small dropdown to filter users within the gradebook.
|
||||
*
|
||||
* @module core_grades/searchwidget/initials
|
||||
* @copyright 2022 Mathew May <mathew.solutions>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
import Pending from 'core/pending';
|
||||
import * as Url from 'core/url';
|
||||
import CustomEvents from "core/custom_interaction_events";
|
||||
import $ from 'jquery';
|
||||
|
||||
/**
|
||||
* Whether the event listener has already been registered for this module.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
let registered = false;
|
||||
|
||||
// Contain our selectors within this file until they could be of use elsewhere.
|
||||
const selectors = {
|
||||
pageListItem: 'page-item',
|
||||
pageClickableItem: '.page-link',
|
||||
activeItem: 'active',
|
||||
formDropdown: '.initialsdropdownform',
|
||||
parentDomNode: '.initials-selector',
|
||||
firstInitial: 'firstinitial',
|
||||
lastInitial: 'lastinitial',
|
||||
initialBars: '.initialbar', // Both first and last name use this class.
|
||||
targetButton: 'initialswidget',
|
||||
formItems: {
|
||||
type: 'submit',
|
||||
save: 'save',
|
||||
cancel: 'cancel'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Our initial hook into the module which will eventually allow us to handle the dropdown initials bar form.
|
||||
*
|
||||
* @param {String} callingLink The link to redirect upon form submission.
|
||||
*/
|
||||
export const init = (callingLink) => {
|
||||
if (registered) {
|
||||
return;
|
||||
}
|
||||
const pendingPromise = new Pending();
|
||||
registerListenerEvents(callingLink);
|
||||
// BS events always bubble so, we need to listen for the event higher up the chain.
|
||||
$(selectors.parentDomNode).on('shown.bs.dropdown', () => {
|
||||
document.querySelector(selectors.pageClickableItem).focus({preventScroll: true});
|
||||
});
|
||||
pendingPromise.resolve();
|
||||
registered = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Register event listeners.
|
||||
*
|
||||
* @param {String} callingLink The link to redirect upon form submission.
|
||||
*/
|
||||
const registerListenerEvents = (callingLink) => {
|
||||
const events = [
|
||||
'click',
|
||||
CustomEvents.events.activate,
|
||||
CustomEvents.events.keyboardActivate
|
||||
];
|
||||
CustomEvents.define(document, events);
|
||||
|
||||
// Register events.
|
||||
events.forEach((event) => {
|
||||
document.addEventListener(event, (e) => {
|
||||
// Always fetch the latest information when we click as state is a fickle thing.
|
||||
let {firstActive, lastActive, sifirst, silast} = onClickVariables();
|
||||
let itemToReset = '';
|
||||
|
||||
// Prevent the usual form behaviour.
|
||||
if (e.target.closest(selectors.formDropdown)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
// Handle the state of active initials before form submission.
|
||||
if (e.target.closest(`${selectors.formDropdown} .${selectors.pageListItem}`)) {
|
||||
// Ensure the li items don't cause weird clicking emptying out the form.
|
||||
if (e.target.classList.contains(selectors.pageListItem)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const initialsBar = e.target.closest(selectors.initialBars); // Find out which initial bar we are in.
|
||||
|
||||
// We want to find the current active item in the menu area the user selected.
|
||||
// We also want to fetch the raw item out of the array for instant manipulation.
|
||||
if (initialsBar.classList.contains(selectors.firstInitial)) {
|
||||
sifirst = e.target;
|
||||
itemToReset = firstActive;
|
||||
} else {
|
||||
silast = e.target;
|
||||
itemToReset = lastActive;
|
||||
}
|
||||
swapActiveItems(itemToReset, e);
|
||||
}
|
||||
|
||||
// Handle form submissions.
|
||||
if (e.target.type === selectors.formItems.type) {
|
||||
if (e.target.dataset.action === selectors.formItems.save) {
|
||||
// Ensure we strip out the value (All) as it messes with the PHP side of the initials bar.
|
||||
// Then we will redirect the user back onto the page with new filters applied.
|
||||
window.location = Url.relativeUrl(callingLink, {
|
||||
'id': e.target.closest(selectors.formDropdown).dataset.courseid,
|
||||
'sifirst': sifirst.parentElement.classList.contains('initialbarall') ? '' : sifirst.value,
|
||||
'silast': silast.parentElement.classList.contains('initialbarall') ? '' : silast.value,
|
||||
});
|
||||
}
|
||||
if (e.target.dataset.action === selectors.formItems.cancel) {
|
||||
$(`.${selectors.targetButton}`).dropdown('toggle');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* A small abstracted helper function which allows us to ensure we have up-to-date lists of nodes.
|
||||
*
|
||||
* @returns {{firstActive: HTMLElement, lastActive: HTMLElement, sifirst: ?String, silast: ?String}}
|
||||
*/
|
||||
const onClickVariables = () => {
|
||||
// Ensure we have an up-to-date initials bar.
|
||||
const firstItems = [...document.querySelectorAll(`.${selectors.firstInitial} li`)];
|
||||
const lastItems = [...document.querySelectorAll(`.${selectors.lastInitial} li`)];
|
||||
const firstActive = firstItems.filter((item) => item.classList.contains(selectors.activeItem))[0];
|
||||
const lastActive = lastItems.filter((item) => item.classList.contains(selectors.activeItem))[0];
|
||||
// Ensure we retain both of the selections from a previous instance.
|
||||
let sifirst = firstActive.querySelector(selectors.pageClickableItem);
|
||||
let silast = lastActive.querySelector(selectors.pageClickableItem);
|
||||
return {firstActive, lastActive, sifirst, silast};
|
||||
};
|
||||
|
||||
/**
|
||||
* Given we are provided the old li and current click event, swap around the active properties.
|
||||
*
|
||||
* @param {HTMLElement} itemToReset
|
||||
* @param {Event} e
|
||||
*/
|
||||
const swapActiveItems = (itemToReset, e) => {
|
||||
itemToReset.classList.remove(selectors.activeItem);
|
||||
itemToReset.querySelector(selectors.pageClickableItem).ariaCurrent = false;
|
||||
|
||||
// Set the select item as the current item.
|
||||
const itemToSetActive = e.target.parentElement;
|
||||
itemToSetActive.classList.add(selectors.activeItem);
|
||||
e.target.ariaCurrent = true;
|
||||
};
|
||||
@@ -85,6 +85,54 @@ class core_grades_renderer extends plugin_renderer_base {
|
||||
return $this->render_from_template('core_grades/group_selector', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the data to render the initials bar filter within the gradebook.
|
||||
* Using this initials selector means you'll have to retain the use of the templates & JS to handle form submission.
|
||||
* If a simple redirect on each selection is desired the standard user_search() within the user renderer is what you are after.
|
||||
*
|
||||
* @param object $course The course object.
|
||||
* @param context $context Our current context.
|
||||
* @param string $slug The slug for the report that called this function.
|
||||
* @return stdClass The data to output.
|
||||
*/
|
||||
public function initials_selector(
|
||||
object $course,
|
||||
context $context,
|
||||
string $slug
|
||||
): stdClass {
|
||||
global $SESSION, $COURSE;
|
||||
// User search.
|
||||
$url = new moodle_url($slug, ['id' => $course->id]);
|
||||
$firstinitial = $SESSION->gradereport["filterfirstname-{$context->id}"] ?? '';
|
||||
$lastinitial = $SESSION->gradereport["filtersurname-{$context->id}"] ?? '';
|
||||
|
||||
$renderer = $this->page->get_renderer('core_user');
|
||||
$initialsbar = $renderer->partial_user_search($url, $firstinitial, $lastinitial, true);
|
||||
|
||||
$currentfilter = '';
|
||||
if ($firstinitial !== '' && $lastinitial !== '') {
|
||||
$currentfilter = get_string('filterbothactive', 'grades', ['first' => $firstinitial, 'last' => $lastinitial]);
|
||||
} else if ($firstinitial !== '') {
|
||||
$currentfilter = get_string('filterfirstactive', 'grades', ['first' => $firstinitial]);
|
||||
} else if ($lastinitial !== '') {
|
||||
$currentfilter = get_string('filterlastactive', 'grades', ['last' => $lastinitial]);
|
||||
}
|
||||
|
||||
$this->page->requires->js_call_amd('core_grades/searchwidget/initials', 'init', [$slug]);
|
||||
|
||||
$formdata = (object) [
|
||||
'courseid' => $COURSE->id,
|
||||
'initialsbars' => $initialsbar,
|
||||
];
|
||||
$dropdowncontent = $this->render_from_template('core_grades/initials_dropdown_form', $formdata);
|
||||
|
||||
return (object) [
|
||||
'buttoncontent' => $currentfilter !== '' ? $currentfilter : get_string('filterbyname', 'core_grades'),
|
||||
'buttonheader' => $currentfilter !== '' ? get_string('name') : null,
|
||||
'dropdowncontent' => $dropdowncontent,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and renders a custom user heading.
|
||||
*
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace gradereport_grader\output;
|
||||
|
||||
use moodle_url;
|
||||
use core_grades\output\general_action_bar;
|
||||
use core_grades\output\gradebook_dropdown;
|
||||
|
||||
/**
|
||||
* Renderable class for the action bar elements in the grader report.
|
||||
@@ -53,12 +54,37 @@ class action_bar extends \core_grades\output\action_bar {
|
||||
* @return array
|
||||
*/
|
||||
public function export_for_template(\renderer_base $output): array {
|
||||
global $PAGE;
|
||||
// If in the course context, we should display the general navigation selector in gradebook.
|
||||
$courseid = $this->context->instanceid;
|
||||
// Get the data used to output the general navigation selector.
|
||||
$generalnavselector = new general_action_bar($this->context,
|
||||
new moodle_url('/grade/report/grader/index.php', ['id' => $courseid]), 'gradereport', 'grader');
|
||||
|
||||
return $generalnavselector->export_for_template($output);
|
||||
$data = $generalnavselector->export_for_template($output);
|
||||
|
||||
// If the user has the capability to view all grades, display the group selector (if applicable), the user selector
|
||||
// and the view mode selector (if applicable).
|
||||
if (has_capability('moodle/grade:viewall', $this->context)) {
|
||||
$course = get_course($courseid);
|
||||
$gradesrenderer = $PAGE->get_renderer('core_grades');
|
||||
|
||||
$initialscontent = $gradesrenderer->initials_selector(
|
||||
$course,
|
||||
$this->context,
|
||||
'/grade/report/grader/index.php'
|
||||
);
|
||||
$initialselector = new gradebook_dropdown(
|
||||
false,
|
||||
$initialscontent->buttoncontent,
|
||||
$initialscontent->dropdowncontent,
|
||||
'initials-selector',
|
||||
'initialswidget',
|
||||
'initialsdropdown',
|
||||
$initialscontent->buttonheader,
|
||||
);
|
||||
$data['initialselector'] = $initialselector->export_for_template($output);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,19 +109,18 @@ if (!empty($target) && !empty($action) && confirm_sesskey()) {
|
||||
|
||||
$reportname = get_string('pluginname', 'gradereport_grader');
|
||||
|
||||
// Do this check just before printing the grade header (and only do it once).
|
||||
grade_regrade_final_grades_if_required($course);
|
||||
|
||||
$actionbar = new \gradereport_grader\output\action_bar($context);
|
||||
// Print header
|
||||
print_grade_page_head($COURSE->id, 'report', 'grader', $reportname, false, $buttons, true,
|
||||
null, null, null, $actionbar);
|
||||
|
||||
//Initialise the grader report object that produces the table
|
||||
//the class grade_report_grader_ajax was removed as part of MDL-21562
|
||||
$report = new grade_report_grader($courseid, $gpr, $context, $page, $sortitemid);
|
||||
$numusers = $report->get_numusers(true, true);
|
||||
|
||||
// Do this check just before printing the grade header (and only do it once).
|
||||
grade_regrade_final_grades_if_required($course);
|
||||
|
||||
$actionbar = new \gradereport_grader\output\action_bar($context, $report, $numusers);
|
||||
print_grade_page_head($COURSE->id, 'report', 'grader', $reportname, false, $buttons, true,
|
||||
null, null, null, $actionbar);
|
||||
|
||||
// make sure separate group does not prevent view
|
||||
if ($report->currentgroup == -2) {
|
||||
echo $OUTPUT->heading(get_string("notingroup"));
|
||||
@@ -141,14 +140,6 @@ $report->load_users();
|
||||
$report->load_final_grades();
|
||||
echo $report->group_selector;
|
||||
|
||||
// User search
|
||||
$url = new moodle_url('/grade/report/grader/index.php', array('id' => $course->id));
|
||||
$firstinitial = $SESSION->gradereport["filterfirstname-{$context->id}"] ?? '';
|
||||
$lastinitial = $SESSION->gradereport["filtersurname-{$context->id}"] ?? '';
|
||||
$totalusers = $report->get_numusers(true, false);
|
||||
$renderer = $PAGE->get_renderer('core_user');
|
||||
echo $renderer->user_search($url, $firstinitial, $lastinitial, $numusers, $totalusers, $report->currentgroupname);
|
||||
|
||||
//show warnings if any
|
||||
foreach ($warnings as $warning) {
|
||||
echo $OUTPUT->notification($warning);
|
||||
|
||||
@@ -201,3 +201,12 @@
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.path-grade-report-grader .initialsdropdown {
|
||||
width: 460px;
|
||||
}
|
||||
|
||||
.path-grade-report-grader .initialsdropdown::after {
|
||||
content: '';
|
||||
}
|
||||
|
||||
|
||||
@@ -69,5 +69,11 @@
|
||||
</div>
|
||||
<div class="navitem-divider"></div>
|
||||
{{/generalnavselector}}
|
||||
{{#initialselector}}
|
||||
<div class="navitem d-flex flex-column align-self-center">
|
||||
{{>core_grades/tertiary_navigation_dropdown}}
|
||||
</div>
|
||||
<div class="navitem-divider"></div>
|
||||
{{/initialselector}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
{{!
|
||||
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/>.
|
||||
}}
|
||||
{{!
|
||||
@template core_grades/initials_dropdown_form
|
||||
|
||||
The form component of our dropdown within the tertiary navigation.
|
||||
|
||||
Context variables required for this template:
|
||||
* courseid - The course ID.
|
||||
* initialsbars - The rendered mini renderer from the user renderer.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"courseid": "2",
|
||||
"initialsbars": "Output from renderer method"
|
||||
}
|
||||
}}
|
||||
<form class="initialsdropdownform" action="#" data-courseid="{{courseid}}">
|
||||
{{{initialsbars}}}
|
||||
<div class="d-flex flex-row justify-content-end">
|
||||
<input class="btn btn-outline-secondary pull-right mx-2" data-action="cancel" type="submit" value="{{#str}}closebuttontitle{{/str}}">
|
||||
<input class="btn btn-primary pull-right" data-action="save" type="submit" value="{{#str}}apply{{/str}}">
|
||||
</div>
|
||||
</form>
|
||||
@@ -239,6 +239,10 @@ $string['feedbacks'] = 'Feedbacks';
|
||||
$string['feedbacksaved'] = 'Feedback saved';
|
||||
$string['feedbackview'] = 'View feedback';
|
||||
$string['finalgrade'] = 'Final grade';
|
||||
$string['filterbothactive'] = 'First ({$a->first}) Last ({$a->last})';
|
||||
$string['filterbyname'] = 'Filter by name';
|
||||
$string['filterfirstactive'] = 'First ({$a->first})';
|
||||
$string['filterlastactive'] = 'Last ({$a->last})';
|
||||
$string['finalgrade_help'] = 'If the overridden checkbox is ticked, a grade may be added or amended.';
|
||||
$string['fixedstudents'] = 'Static students column';
|
||||
$string['fixedstudents_help'] = 'Allows grades to scroll horizontally without losing sight of the students column, by making it static.';
|
||||
|
||||
@@ -154,6 +154,7 @@ $string['answer'] = 'Answer';
|
||||
$string['any'] = 'Any';
|
||||
$string['approve'] = 'Approve';
|
||||
$string['appearance'] = 'Appearance';
|
||||
$string['apply'] = 'Apply';
|
||||
$string['applyfilters'] = 'Apply filters';
|
||||
$string['areyousure'] = 'Are you sure?';
|
||||
$string['areyousuretorestorethis'] = 'Do you want to continue?';
|
||||
|
||||
Reference in New Issue
Block a user