From 939e6b546ae56d95b897c0e8fa165793da7bf353 Mon Sep 17 00:00:00 2001 From: Kevin Percy Date: Tue, 9 Jul 2024 12:26:48 +0800 Subject: [PATCH] MDL-80746 core_course: Move initials selector from core_grades --- course/amd/build/actionbar/initials.min.js | 10 + .../amd/build/actionbar/initials.min.js.map | 1 + course/amd/src/actionbar/initials.js | 183 ++++++++++++++++++ .../output/actionbar/initial_selector.php | 134 +++++++++++++ course/classes/output/actionbar/renderer.php | 11 ++ .../templates/initials_dropdown_form.mustache | 38 ++++ grade/amd/build/searchwidget/initials.min.js | 2 + .../build/searchwidget/initials.min.js.map | 2 +- grade/amd/src/searchwidget/initials.js | 2 + grade/renderer.php | 9 + .../templates/initials_dropdown_form.mustache | 3 + lang/en/course.php | 4 + lang/en/deprecated.txt | 4 + lang/en/grades.php | 10 +- 14 files changed, 408 insertions(+), 5 deletions(-) create mode 100644 course/amd/build/actionbar/initials.min.js create mode 100644 course/amd/build/actionbar/initials.min.js.map create mode 100644 course/amd/src/actionbar/initials.js create mode 100644 course/classes/output/actionbar/initial_selector.php create mode 100644 course/templates/initials_dropdown_form.mustache diff --git a/course/amd/build/actionbar/initials.min.js b/course/amd/build/actionbar/initials.min.js new file mode 100644 index 00000000000..b712e4d2a31 --- /dev/null +++ b/course/amd/build/actionbar/initials.min.js @@ -0,0 +1,10 @@ +define("core_course/actionbar/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. + * + * @module core_course/actionbar/initials + * @copyright 2022 Mathew May + * @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=function(callingLink){let firstInitialParam=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"sifirst",lastInitialParam=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"silast",additionalParams=arguments.length>3&&void 0!==arguments[3]?arguments[3]:[];if(registered)return;const pendingPromise=new _pending.default;registerListenerEvents(callingLink,firstInitialParam,lastInitialParam,additionalParams),(0,_jquery.default)(selectors_parentDomNode).on("shown.bs.dropdown",(()=>{document.querySelector(selectors_pageClickableItem).focus({preventScroll:!0})})),pendingPromise.resolve(),registered=!0};const registerListenerEvents=function(callingLink){let firstInitialParam=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"sifirst",lastInitialParam=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"silast",additionalParams=arguments.length>3&&void 0!==arguments[3]?arguments[3]:[];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)}if(e.target.closest("".concat(selectors_formDropdown))&&e.target.type===selectors_formItems.type){if(e.target.dataset.action===selectors_formItems.save){const params={id:e.target.closest(selectors_formDropdown).dataset.courseid,[firstInitialParam]:sifirst.parentElement.classList.contains("initialbarall")?"":sifirst.value,[lastInitialParam]:silast.parentElement.classList.contains("initialbarall")?"":silast.value};for(const[key,value]of Object.entries(additionalParams))params[key]=value;window.location=Url.relativeUrl(callingLink,params)}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 \ No newline at end of file diff --git a/course/amd/build/actionbar/initials.min.js.map b/course/amd/build/actionbar/initials.min.js.map new file mode 100644 index 00000000000..9cc36c1bfd9 --- /dev/null +++ b/course/amd/build/actionbar/initials.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"initials.min.js","sources":["../../src/actionbar/initials.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * A small dropdown to filter users.\n *\n * @module core_course/actionbar/initials\n * @copyright 2022 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Pending from 'core/pending';\nimport * as Url from 'core/url';\nimport CustomEvents from \"core/custom_interaction_events\";\nimport $ from 'jquery';\n\n/**\n * Whether the event listener has already been registered for this module.\n *\n * @type {boolean}\n */\nlet registered = false;\n\n// Contain our selectors within this file until they could be of use elsewhere.\nconst selectors = {\n pageListItem: 'page-item',\n pageClickableItem: '.page-link',\n activeItem: 'active',\n formDropdown: '.initialsdropdownform',\n parentDomNode: '.initials-selector',\n firstInitial: 'firstinitial',\n lastInitial: 'lastinitial',\n initialBars: '.initialbar', // Both first and last name use this class.\n targetButton: 'initialswidget',\n formItems: {\n type: 'submit',\n save: 'save',\n cancel: 'cancel'\n }\n};\n\n/**\n * Our initial hook into the module which will eventually allow us to handle the dropdown initials bar form.\n *\n * @param {String} callingLink The link to redirect upon form submission.\n * @param {String} firstInitialParam The URL parameter to set for the first name initial.\n * @param {String} lastInitialParam The URL parameter to set for the last name initial.\n * @param {Array} additionalParams Any additional parameters to set for the URL.\n */\nexport const init = (callingLink, firstInitialParam = 'sifirst',\n lastInitialParam = 'silast', additionalParams = []) => {\n if (registered) {\n return;\n }\n const pendingPromise = new Pending();\n registerListenerEvents(callingLink, firstInitialParam, lastInitialParam, additionalParams);\n // BS events always bubble so, we need to listen for the event higher up the chain.\n $(selectors.parentDomNode).on('shown.bs.dropdown', () => {\n document.querySelector(selectors.pageClickableItem).focus({preventScroll: true});\n });\n pendingPromise.resolve();\n registered = true;\n};\n\n/**\n * Register event listeners.\n *\n * @param {String} callingLink The link to redirect upon form submission.\n * @param {String} firstInitialParam The URL parameter to set for the first name initial.\n * @param {String} lastInitialParam The URL parameter to set for the last name initial.\n * @param {Array} additionalParams Any additional parameters to set for the URL.\n */\nconst registerListenerEvents = (callingLink, firstInitialParam = 'sifirst',\n lastInitialParam = 'silast', additionalParams = []) => {\n const events = [\n 'click',\n CustomEvents.events.activate,\n CustomEvents.events.keyboardActivate\n ];\n CustomEvents.define(document, events);\n\n // Register events.\n events.forEach((event) => {\n document.addEventListener(event, (e) => {\n // Always fetch the latest information when we click as state is a fickle thing.\n let {firstActive, lastActive, sifirst, silast} = onClickVariables();\n let itemToReset = '';\n\n // Prevent the usual form behaviour.\n if (e.target.closest(selectors.formDropdown)) {\n e.preventDefault();\n }\n\n // Handle the state of active initials before form submission.\n if (e.target.closest(`${selectors.formDropdown} .${selectors.pageListItem}`)) {\n // Ensure the li items don't cause weird clicking emptying out the form.\n if (e.target.classList.contains(selectors.pageListItem)) {\n return;\n }\n\n const initialsBar = e.target.closest(selectors.initialBars); // Find out which initial bar we are in.\n\n // We want to find the current active item in the menu area the user selected.\n // We also want to fetch the raw item out of the array for instant manipulation.\n if (initialsBar.classList.contains(selectors.firstInitial)) {\n sifirst = e.target;\n itemToReset = firstActive;\n } else {\n silast = e.target;\n itemToReset = lastActive;\n }\n swapActiveItems(itemToReset, e);\n }\n\n // Handle form submissions.\n if (e.target.closest(`${selectors.formDropdown}`) && e.target.type === selectors.formItems.type) {\n if (e.target.dataset.action === selectors.formItems.save) {\n // Ensure we strip out the value (All) as it messes with the PHP side of the initials bar.\n // Then we will redirect the user back onto the page with new filters applied.\n const params = {\n 'id': e.target.closest(selectors.formDropdown).dataset.courseid,\n [firstInitialParam]: sifirst.parentElement.classList.contains('initialbarall') ? '' : sifirst.value,\n [lastInitialParam]: silast.parentElement.classList.contains('initialbarall') ? '' : silast.value,\n };\n\n // If additional parameters are passed, add them here (overriding any already set above).\n for (const [key, value] of Object.entries(additionalParams)) {\n params[key] = value;\n }\n window.location = Url.relativeUrl(callingLink, params);\n }\n if (e.target.dataset.action === selectors.formItems.cancel) {\n $(`.${selectors.targetButton}`).dropdown('toggle');\n }\n }\n });\n });\n};\n\n/**\n * A small abstracted helper function which allows us to ensure we have up-to-date lists of nodes.\n *\n * @returns {{firstActive: HTMLElement, lastActive: HTMLElement, sifirst: ?String, silast: ?String}}\n */\nconst onClickVariables = () => {\n // Ensure we have an up-to-date initials bar.\n const firstItems = [...document.querySelectorAll(`.${selectors.firstInitial} li`)];\n const lastItems = [...document.querySelectorAll(`.${selectors.lastInitial} li`)];\n const firstActive = firstItems.filter((item) => item.classList.contains(selectors.activeItem))[0];\n const lastActive = lastItems.filter((item) => item.classList.contains(selectors.activeItem))[0];\n // Ensure we retain both of the selections from a previous instance.\n let sifirst = firstActive.querySelector(selectors.pageClickableItem);\n let silast = lastActive.querySelector(selectors.pageClickableItem);\n return {firstActive, lastActive, sifirst, silast};\n};\n\n/**\n * Given we are provided the old li and current click event, swap around the active properties.\n *\n * @param {HTMLElement} itemToReset\n * @param {Event} e\n */\nconst swapActiveItems = (itemToReset, e) => {\n itemToReset.classList.remove(selectors.activeItem);\n itemToReset.querySelector(selectors.pageClickableItem).ariaCurrent = false;\n\n // Set the select item as the current item.\n const itemToSetActive = e.target.parentElement;\n itemToSetActive.classList.add(selectors.activeItem);\n e.target.ariaCurrent = true;\n};\n"],"names":["registered","selectors","type","save","cancel","callingLink","firstInitialParam","lastInitialParam","additionalParams","pendingPromise","Pending","registerListenerEvents","on","document","querySelector","focus","preventScroll","resolve","events","CustomEvents","activate","keyboardActivate","define","forEach","event","addEventListener","e","firstActive","lastActive","sifirst","silast","onClickVariables","itemToReset","target","closest","preventDefault","classList","contains","swapActiveItems","dataset","action","params","courseid","parentElement","value","key","Object","entries","window","location","Url","relativeUrl","dropdown","firstItems","querySelectorAll","lastItems","filter","item","remove","ariaCurrent","add"],"mappings":";;;;;;;44BAiCIA,YAAa,QAGXC,uBACY,YADZA,4BAEiB,aAFjBA,qBAGU,SAHVA,uBAIY,wBAJZA,wBAKa,qBALbA,uBAMY,eANZA,sBAOW,cAPXA,sBAQW,cARXA,uBASY,iBATZA,oBAUS,CACPC,KAAM,SACNC,KAAM,OACNC,OAAQ,wBAYI,SAACC,iBAAaC,yEAAoB,UAC9CC,wEAAmB,SAAUC,wEAAmB,MAChDR,wBAGES,eAAiB,IAAIC,iBAC3BC,uBAAuBN,YAAaC,kBAAmBC,iBAAkBC,sCAEvEP,yBAAyBW,GAAG,qBAAqB,KAC/CC,SAASC,cAAcb,6BAA6Bc,MAAM,CAACC,eAAe,OAE9EP,eAAeQ,UACfjB,YAAa,SAWXW,uBAAyB,SAACN,iBAAaC,yEAAoB,UACzDC,wEAAmB,SAAUC,wEAAmB,SAC9CU,OAAS,CACX,QACAC,mCAAaD,OAAOE,SACpBD,mCAAaD,OAAOG,qDAEXC,OAAOT,SAAUK,QAG9BA,OAAOK,SAASC,QACZX,SAASY,iBAAiBD,OAAQE,QAE1BC,YAACA,YAADC,WAAcA,WAAdC,QAA0BA,QAA1BC,OAAmCA,QAAUC,mBAC7CC,YAAc,MAGdN,EAAEO,OAAOC,QAAQjC,yBACjByB,EAAES,iBAIFT,EAAEO,OAAOC,kBAAWjC,oCAA2BA,yBAA2B,IAEtEyB,EAAEO,OAAOG,UAAUC,SAASpC,+BAIZyB,EAAEO,OAAOC,QAAQjC,uBAIrBmC,UAAUC,SAASpC,yBAC/B4B,QAAUH,EAAEO,OACZD,YAAcL,cAEdG,OAASJ,EAAEO,OACXD,YAAcJ,YAElBU,gBAAgBN,YAAaN,MAI7BA,EAAEO,OAAOC,kBAAWjC,0BAA6ByB,EAAEO,OAAO/B,OAASD,oBAAoBC,KAAM,IACzFwB,EAAEO,OAAOM,QAAQC,SAAWvC,oBAAoBE,KAAM,OAGhDsC,OAAS,IACLf,EAAEO,OAAOC,QAAQjC,wBAAwBsC,QAAQG,UACtDpC,mBAAoBuB,QAAQc,cAAcP,UAAUC,SAAS,iBAAmB,GAAKR,QAAQe,OAC7FrC,kBAAmBuB,OAAOa,cAAcP,UAAUC,SAAS,iBAAmB,GAAKP,OAAOc,WAI1F,MAAOC,IAAKD,SAAUE,OAAOC,QAAQvC,kBACtCiC,OAAOI,KAAOD,MAElBI,OAAOC,SAAWC,IAAIC,YAAY9C,YAAaoC,QAE/Cf,EAAEO,OAAOM,QAAQC,SAAWvC,oBAAoBG,uCAC1CH,yBAA0BmD,SAAS,kBAYvDrB,iBAAmB,WAEfsB,WAAa,IAAIxC,SAASyC,4BAAqBrD,gCAC/CsD,UAAY,IAAI1C,SAASyC,4BAAqBrD,+BAC9C0B,YAAc0B,WAAWG,QAAQC,MAASA,KAAKrB,UAAUC,SAASpC,wBAAuB,GACzF2B,WAAa2B,UAAUC,QAAQC,MAASA,KAAKrB,UAAUC,SAASpC,wBAAuB,OAEzF4B,QAAUF,YAAYb,cAAcb,6BACpC6B,OAASF,WAAWd,cAAcb,mCAC/B,CAAC0B,YAAAA,YAAaC,WAAAA,WAAYC,QAAAA,QAASC,OAAAA,SASxCQ,gBAAkB,CAACN,YAAaN,KAClCM,YAAYI,UAAUsB,OAAOzD,sBAC7B+B,YAAYlB,cAAcb,6BAA6B0D,aAAc,EAG7CjC,EAAEO,OAAOU,cACjBP,UAAUwB,IAAI3D,sBAC9ByB,EAAEO,OAAO0B,aAAc"} \ No newline at end of file diff --git a/course/amd/src/actionbar/initials.js b/course/amd/src/actionbar/initials.js new file mode 100644 index 00000000000..e48bf533a90 --- /dev/null +++ b/course/amd/src/actionbar/initials.js @@ -0,0 +1,183 @@ +// 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 . + +/** + * A small dropdown to filter users. + * + * @module core_course/actionbar/initials + * @copyright 2022 Mathew May + * @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. + * @param {String} firstInitialParam The URL parameter to set for the first name initial. + * @param {String} lastInitialParam The URL parameter to set for the last name initial. + * @param {Array} additionalParams Any additional parameters to set for the URL. + */ +export const init = (callingLink, firstInitialParam = 'sifirst', + lastInitialParam = 'silast', additionalParams = []) => { + if (registered) { + return; + } + const pendingPromise = new Pending(); + registerListenerEvents(callingLink, firstInitialParam, lastInitialParam, additionalParams); + // 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. + * @param {String} firstInitialParam The URL parameter to set for the first name initial. + * @param {String} lastInitialParam The URL parameter to set for the last name initial. + * @param {Array} additionalParams Any additional parameters to set for the URL. + */ +const registerListenerEvents = (callingLink, firstInitialParam = 'sifirst', + lastInitialParam = 'silast', additionalParams = []) => { + 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.closest(`${selectors.formDropdown}`) && 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. + const params = { + 'id': e.target.closest(selectors.formDropdown).dataset.courseid, + [firstInitialParam]: sifirst.parentElement.classList.contains('initialbarall') ? '' : sifirst.value, + [lastInitialParam]: silast.parentElement.classList.contains('initialbarall') ? '' : silast.value, + }; + + // If additional parameters are passed, add them here (overriding any already set above). + for (const [key, value] of Object.entries(additionalParams)) { + params[key] = value; + } + window.location = Url.relativeUrl(callingLink, params); + } + 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; +}; diff --git a/course/classes/output/actionbar/initial_selector.php b/course/classes/output/actionbar/initial_selector.php new file mode 100644 index 00000000000..5d227b24857 --- /dev/null +++ b/course/classes/output/actionbar/initial_selector.php @@ -0,0 +1,134 @@ +. + +namespace core_course\output\actionbar; + +use core\output\comboboxsearch; +use stdClass; + +/** + * Renderable class for the initial selector element in the action bar. + * + * @package core_course + * @copyright 2024 Kevin Percy + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class initial_selector extends comboboxsearch { + + /** + * The class constructor. + * + * @param stdClass $course The course object. + * @param string $targeturl The target URL to send the form to. + * @param string $firstinitial The selected first initial. + * @param string $lastinitial The selected last initial. + * @param string $firstinitialparam The parameter name for the first initial. + * @param string $lastinitialparam The parameter name for the last initial. + * @param array $additionalparams Any additional parameters required for the form submission URL. + */ + public function __construct( + stdClass $course, + string $targeturl, + string $firstinitial = '', + string $lastinitial = '', + string $firstinitialparam = 'sifirst', + string $lastinitialparam = 'silast', + array $additionalparams = [] + ) { + $initialselectorcontent = $this->initial_selector_output( + course: $course, + targeturl: $targeturl, + firstinitial: $firstinitial, + lastinitial: $lastinitial, + firstinitialparam: $firstinitialparam, + lastinitialparam: $lastinitialparam, + additionalparams: $additionalparams + ); + $currentfilteroutput = $this->current_filter_output($firstinitial, $lastinitial); + + parent::__construct( + false, + $currentfilteroutput !== '' ? $currentfilteroutput : get_string('filterbyname', 'course'), + $initialselectorcontent, + 'initials-selector', + 'initialswidget', + 'initialsdropdown', + $currentfilteroutput !== '' ? get_string('name') : null, + true, + get_string('filterbyname', 'course'), + 'nameinitials', + json_encode([ + 'first' => $firstinitial, + 'last' => $lastinitial, + ]) + ); + } + + /** + * Method to generate the current filter information for the initial selector label. + * + * @param string $firstinitial The selected first initial. + * @param string $lastinitial The selected last initial. + * @return string + */ + public function current_filter_output(string $firstinitial, string $lastinitial): string { + if ($firstinitial !== '' && $lastinitial !== '') { + return get_string('filterbothactive', 'course', ['first' => $firstinitial, 'last' => $lastinitial]); + } else if ($firstinitial !== '') { + return get_string('filterfirstactive', 'course', ['first' => $firstinitial]); + } else if ($lastinitial !== '') { + return get_string('filterlastactive', 'course', ['last' => $lastinitial]); + } + + return ''; + } + + /** + * Method to generate the output for the initial selector. + * + * @param stdClass $course The course object. + * @param string $targeturl The target URL to send the form to. + * @param string $firstinitial The selected first initial. + * @param string $lastinitial The selected last initial. + * @param string $firstinitialparam The parameter name for the first initial. + * @param string $lastinitialparam The parameter name for the last initial. + * @param array $additionalparams Any additional parameters required for the form submission URL. + * @return string + */ + public function initial_selector_output( + stdClass $course, + string $targeturl, + string $firstinitial = '', + string $lastinitial = '', + string $firstinitialparam = 'sifirst', + string $lastinitialparam = 'silast', + array $additionalparams = [] + ): string { + global $OUTPUT, $PAGE; + + $renderer = $PAGE->get_renderer('core_user'); + $initialsbar = $renderer->partial_user_search($targeturl, $firstinitial, $lastinitial, true); + + $PAGE->requires->js_call_amd('core_course/actionbar/initials', 'init', + [$targeturl, $firstinitialparam, $lastinitialparam, $additionalparams]); + + $formdata = (object) [ + 'courseid' => $course->id, + 'initialsbars' => $initialsbar, + ]; + return $OUTPUT->render_from_template('core_course/initials_dropdown_form', $formdata); + } +} diff --git a/course/classes/output/actionbar/renderer.php b/course/classes/output/actionbar/renderer.php index adc5b7e89a2..cb5fdae49bf 100644 --- a/course/classes/output/actionbar/renderer.php +++ b/course/classes/output/actionbar/renderer.php @@ -46,4 +46,15 @@ class renderer extends \plugin_renderer_base { $data = $groupselector->export_for_template($this); return parent::render_from_template($groupselector->get_template(), $data); } + + /** + * Renders the initial selector trigger element in the action bar. + * + * @param initial_selector $initialselector The initial selector object. + * @return string The HTML output. + */ + protected function render_initial_selector(initial_selector $initialselector): string { + $data = $initialselector->export_for_template($this); + return parent::render_from_template($initialselector->get_template(), $data); + } } diff --git a/course/templates/initials_dropdown_form.mustache b/course/templates/initials_dropdown_form.mustache new file mode 100644 index 00000000000..d8ae89c95ff --- /dev/null +++ b/course/templates/initials_dropdown_form.mustache @@ -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 . +}} +{{! + @template core_course/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" + } +}} +
+ {{{initialsbars}}} +
+ + +
+
diff --git a/grade/amd/build/searchwidget/initials.min.js b/grade/amd/build/searchwidget/initials.min.js index fe40a9e7f85..4fbde753536 100644 --- a/grade/amd/build/searchwidget/initials.min.js +++ b/grade/amd/build/searchwidget/initials.min.js @@ -5,6 +5,8 @@ define("core_grades/searchwidget/initials",["exports","core/pending","core/url", * @module core_grades/searchwidget/initials * @copyright 2022 Mathew May * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @deprecated since Moodle 4.5 - please use core_course/actionbar/initials instead. + * @todo Final deprecation in Moodle 6.0. See MDL-82421. */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=function(callingLink){let gpr_userid=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,gpr_search=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;if(registered)return;const pendingPromise=new _pending.default;registerListenerEvents(callingLink,gpr_userid,gpr_search),(0,_jquery.default)(selectors_parentDomNode).on("shown.bs.dropdown",(()=>{document.querySelector(selectors_pageClickableItem).focus({preventScroll:!0})})),pendingPromise.resolve(),registered=!0};const registerListenerEvents=function(callingLink){let gpr_userid=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,gpr_search=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;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)}if(e.target.closest("".concat(selectors_formDropdown))&&e.target.type===selectors_formItems.type){if(e.target.dataset.action===selectors_formItems.save){const params={id:e.target.closest(selectors_formDropdown).dataset.courseid,gpr_search:null!==gpr_search?gpr_search:"",sifirst:sifirst.parentElement.classList.contains("initialbarall")?"":sifirst.value,silast:silast.parentElement.classList.contains("initialbarall")?"":silast.value};null!==gpr_userid&&(params.gpr_userid=gpr_userid),window.location=Url.relativeUrl(callingLink,params)}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 \ No newline at end of file diff --git a/grade/amd/build/searchwidget/initials.min.js.map b/grade/amd/build/searchwidget/initials.min.js.map index 364043b7e07..b2a47a7aa3b 100644 --- a/grade/amd/build/searchwidget/initials.min.js.map +++ b/grade/amd/build/searchwidget/initials.min.js.map @@ -1 +1 @@ -{"version":3,"file":"initials.min.js","sources":["../../src/searchwidget/initials.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * A small dropdown to filter users within the gradebook.\n *\n * @module core_grades/searchwidget/initials\n * @copyright 2022 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Pending from 'core/pending';\nimport * as Url from 'core/url';\nimport CustomEvents from \"core/custom_interaction_events\";\nimport $ from 'jquery';\n\n/**\n * Whether the event listener has already been registered for this module.\n *\n * @type {boolean}\n */\nlet registered = false;\n\n// Contain our selectors within this file until they could be of use elsewhere.\nconst selectors = {\n pageListItem: 'page-item',\n pageClickableItem: '.page-link',\n activeItem: 'active',\n formDropdown: '.initialsdropdownform',\n parentDomNode: '.initials-selector',\n firstInitial: 'firstinitial',\n lastInitial: 'lastinitial',\n initialBars: '.initialbar', // Both first and last name use this class.\n targetButton: 'initialswidget',\n formItems: {\n type: 'submit',\n save: 'save',\n cancel: 'cancel'\n }\n};\n\n/**\n * Our initial hook into the module which will eventually allow us to handle the dropdown initials bar form.\n *\n * @param {String} callingLink The link to redirect upon form submission.\n * @param {Null|Number} gpr_userid The user id to filter by.\n * @param {Null|String} gpr_search The search value to filter by.\n */\nexport const init = (callingLink, gpr_userid = null, gpr_search = null) => {\n if (registered) {\n return;\n }\n const pendingPromise = new Pending();\n registerListenerEvents(callingLink, gpr_userid, gpr_search);\n // BS events always bubble so, we need to listen for the event higher up the chain.\n $(selectors.parentDomNode).on('shown.bs.dropdown', () => {\n document.querySelector(selectors.pageClickableItem).focus({preventScroll: true});\n });\n pendingPromise.resolve();\n registered = true;\n};\n\n/**\n * Register event listeners.\n *\n * @param {String} callingLink The link to redirect upon form submission.\n * @param {Null|Number} gpr_userid The user id to filter by.\n * @param {Null|String} gpr_search The search value to filter by.\n */\nconst registerListenerEvents = (callingLink, gpr_userid = null, gpr_search = null) => {\n const events = [\n 'click',\n CustomEvents.events.activate,\n CustomEvents.events.keyboardActivate\n ];\n CustomEvents.define(document, events);\n\n // Register events.\n events.forEach((event) => {\n document.addEventListener(event, (e) => {\n // Always fetch the latest information when we click as state is a fickle thing.\n let {firstActive, lastActive, sifirst, silast} = onClickVariables();\n let itemToReset = '';\n\n // Prevent the usual form behaviour.\n if (e.target.closest(selectors.formDropdown)) {\n e.preventDefault();\n }\n\n // Handle the state of active initials before form submission.\n if (e.target.closest(`${selectors.formDropdown} .${selectors.pageListItem}`)) {\n // Ensure the li items don't cause weird clicking emptying out the form.\n if (e.target.classList.contains(selectors.pageListItem)) {\n return;\n }\n\n const initialsBar = e.target.closest(selectors.initialBars); // Find out which initial bar we are in.\n\n // We want to find the current active item in the menu area the user selected.\n // We also want to fetch the raw item out of the array for instant manipulation.\n if (initialsBar.classList.contains(selectors.firstInitial)) {\n sifirst = e.target;\n itemToReset = firstActive;\n } else {\n silast = e.target;\n itemToReset = lastActive;\n }\n swapActiveItems(itemToReset, e);\n }\n\n // Handle form submissions.\n if (e.target.closest(`${selectors.formDropdown}`) && e.target.type === selectors.formItems.type) {\n if (e.target.dataset.action === selectors.formItems.save) {\n // Ensure we strip out the value (All) as it messes with the PHP side of the initials bar.\n // Then we will redirect the user back onto the page with new filters applied.\n const params = {\n 'id': e.target.closest(selectors.formDropdown).dataset.courseid,\n 'gpr_search': gpr_search !== null ? gpr_search : '',\n 'sifirst': sifirst.parentElement.classList.contains('initialbarall') ? '' : sifirst.value,\n 'silast': silast.parentElement.classList.contains('initialbarall') ? '' : silast.value,\n };\n if (gpr_userid !== null) {\n params.gpr_userid = gpr_userid;\n }\n window.location = Url.relativeUrl(callingLink, params);\n }\n if (e.target.dataset.action === selectors.formItems.cancel) {\n $(`.${selectors.targetButton}`).dropdown('toggle');\n }\n }\n });\n });\n};\n\n/**\n * A small abstracted helper function which allows us to ensure we have up-to-date lists of nodes.\n *\n * @returns {{firstActive: HTMLElement, lastActive: HTMLElement, sifirst: ?String, silast: ?String}}\n */\nconst onClickVariables = () => {\n // Ensure we have an up-to-date initials bar.\n const firstItems = [...document.querySelectorAll(`.${selectors.firstInitial} li`)];\n const lastItems = [...document.querySelectorAll(`.${selectors.lastInitial} li`)];\n const firstActive = firstItems.filter((item) => item.classList.contains(selectors.activeItem))[0];\n const lastActive = lastItems.filter((item) => item.classList.contains(selectors.activeItem))[0];\n // Ensure we retain both of the selections from a previous instance.\n let sifirst = firstActive.querySelector(selectors.pageClickableItem);\n let silast = lastActive.querySelector(selectors.pageClickableItem);\n return {firstActive, lastActive, sifirst, silast};\n};\n\n/**\n * Given we are provided the old li and current click event, swap around the active properties.\n *\n * @param {HTMLElement} itemToReset\n * @param {Event} e\n */\nconst swapActiveItems = (itemToReset, e) => {\n itemToReset.classList.remove(selectors.activeItem);\n itemToReset.querySelector(selectors.pageClickableItem).ariaCurrent = false;\n\n // Set the select item as the current item.\n const itemToSetActive = e.target.parentElement;\n itemToSetActive.classList.add(selectors.activeItem);\n e.target.ariaCurrent = true;\n};\n"],"names":["registered","selectors","type","save","cancel","callingLink","gpr_userid","gpr_search","pendingPromise","Pending","registerListenerEvents","on","document","querySelector","focus","preventScroll","resolve","events","CustomEvents","activate","keyboardActivate","define","forEach","event","addEventListener","e","firstActive","lastActive","sifirst","silast","onClickVariables","itemToReset","target","closest","preventDefault","classList","contains","swapActiveItems","dataset","action","params","courseid","parentElement","value","window","location","Url","relativeUrl","dropdown","firstItems","querySelectorAll","lastItems","filter","item","remove","ariaCurrent","add"],"mappings":";;;;;;;44BAiCIA,YAAa,QAGXC,uBACY,YADZA,4BAEiB,aAFjBA,qBAGU,SAHVA,uBAIY,wBAJZA,wBAKa,qBALbA,uBAMY,eANZA,sBAOW,cAPXA,sBAQW,cARXA,uBASY,iBATZA,oBAUS,CACPC,KAAM,SACNC,KAAM,OACNC,OAAQ,wBAWI,SAACC,iBAAaC,kEAAa,KAAMC,kEAAa,QAC1DP,wBAGEQ,eAAiB,IAAIC,iBAC3BC,uBAAuBL,YAAaC,WAAYC,gCAE9CN,yBAAyBU,GAAG,qBAAqB,KAC/CC,SAASC,cAAcZ,6BAA6Ba,MAAM,CAACC,eAAe,OAE9EP,eAAeQ,UACfhB,YAAa,SAUXU,uBAAyB,SAACL,iBAAaC,kEAAa,KAAMC,kEAAa,WACnEU,OAAS,CACX,QACAC,mCAAaD,OAAOE,SACpBD,mCAAaD,OAAOG,qDAEXC,OAAOT,SAAUK,QAG9BA,OAAOK,SAASC,QACZX,SAASY,iBAAiBD,OAAQE,QAE1BC,YAACA,YAADC,WAAcA,WAAdC,QAA0BA,QAA1BC,OAAmCA,QAAUC,mBAC7CC,YAAc,MAGdN,EAAEO,OAAOC,QAAQhC,yBACjBwB,EAAES,iBAIFT,EAAEO,OAAOC,kBAAWhC,oCAA2BA,yBAA2B,IAEtEwB,EAAEO,OAAOG,UAAUC,SAASnC,+BAIZwB,EAAEO,OAAOC,QAAQhC,uBAIrBkC,UAAUC,SAASnC,yBAC/B2B,QAAUH,EAAEO,OACZD,YAAcL,cAEdG,OAASJ,EAAEO,OACXD,YAAcJ,YAElBU,gBAAgBN,YAAaN,MAI7BA,EAAEO,OAAOC,kBAAWhC,0BAA6BwB,EAAEO,OAAO9B,OAASD,oBAAoBC,KAAM,IACzFuB,EAAEO,OAAOM,QAAQC,SAAWtC,oBAAoBE,KAAM,OAGhDqC,OAAS,IACLf,EAAEO,OAAOC,QAAQhC,wBAAwBqC,QAAQG,oBAC1B,OAAflC,WAAsBA,WAAa,WACtCqB,QAAQc,cAAcP,UAAUC,SAAS,iBAAmB,GAAKR,QAAQe,aAC1Ed,OAAOa,cAAcP,UAAUC,SAAS,iBAAmB,GAAKP,OAAOc,OAElE,OAAfrC,aACAkC,OAAOlC,WAAaA,YAExBsC,OAAOC,SAAWC,IAAIC,YAAY1C,YAAamC,QAE/Cf,EAAEO,OAAOM,QAAQC,SAAWtC,oBAAoBG,uCAC1CH,yBAA0B+C,SAAS,kBAYvDlB,iBAAmB,WAEfmB,WAAa,IAAIrC,SAASsC,4BAAqBjD,gCAC/CkD,UAAY,IAAIvC,SAASsC,4BAAqBjD,+BAC9CyB,YAAcuB,WAAWG,QAAQC,MAASA,KAAKlB,UAAUC,SAASnC,wBAAuB,GACzF0B,WAAawB,UAAUC,QAAQC,MAASA,KAAKlB,UAAUC,SAASnC,wBAAuB,OAEzF2B,QAAUF,YAAYb,cAAcZ,6BACpC4B,OAASF,WAAWd,cAAcZ,mCAC/B,CAACyB,YAAAA,YAAaC,WAAAA,WAAYC,QAAAA,QAASC,OAAAA,SASxCQ,gBAAkB,CAACN,YAAaN,KAClCM,YAAYI,UAAUmB,OAAOrD,sBAC7B8B,YAAYlB,cAAcZ,6BAA6BsD,aAAc,EAG7C9B,EAAEO,OAAOU,cACjBP,UAAUqB,IAAIvD,sBAC9BwB,EAAEO,OAAOuB,aAAc"} \ No newline at end of file +{"version":3,"file":"initials.min.js","sources":["../../src/searchwidget/initials.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * A small dropdown to filter users within the gradebook.\n *\n * @module core_grades/searchwidget/initials\n * @copyright 2022 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @deprecated since Moodle 4.5 - please use core_course/actionbar/initials instead.\n * @todo Final deprecation in Moodle 6.0. See MDL-82421.\n */\n\nimport Pending from 'core/pending';\nimport * as Url from 'core/url';\nimport CustomEvents from \"core/custom_interaction_events\";\nimport $ from 'jquery';\n\n/**\n * Whether the event listener has already been registered for this module.\n *\n * @type {boolean}\n */\nlet registered = false;\n\n// Contain our selectors within this file until they could be of use elsewhere.\nconst selectors = {\n pageListItem: 'page-item',\n pageClickableItem: '.page-link',\n activeItem: 'active',\n formDropdown: '.initialsdropdownform',\n parentDomNode: '.initials-selector',\n firstInitial: 'firstinitial',\n lastInitial: 'lastinitial',\n initialBars: '.initialbar', // Both first and last name use this class.\n targetButton: 'initialswidget',\n formItems: {\n type: 'submit',\n save: 'save',\n cancel: 'cancel'\n }\n};\n\n/**\n * Our initial hook into the module which will eventually allow us to handle the dropdown initials bar form.\n *\n * @param {String} callingLink The link to redirect upon form submission.\n * @param {Null|Number} gpr_userid The user id to filter by.\n * @param {Null|String} gpr_search The search value to filter by.\n */\nexport const init = (callingLink, gpr_userid = null, gpr_search = null) => {\n if (registered) {\n return;\n }\n const pendingPromise = new Pending();\n registerListenerEvents(callingLink, gpr_userid, gpr_search);\n // BS events always bubble so, we need to listen for the event higher up the chain.\n $(selectors.parentDomNode).on('shown.bs.dropdown', () => {\n document.querySelector(selectors.pageClickableItem).focus({preventScroll: true});\n });\n pendingPromise.resolve();\n registered = true;\n};\n\n/**\n * Register event listeners.\n *\n * @param {String} callingLink The link to redirect upon form submission.\n * @param {Null|Number} gpr_userid The user id to filter by.\n * @param {Null|String} gpr_search The search value to filter by.\n */\nconst registerListenerEvents = (callingLink, gpr_userid = null, gpr_search = null) => {\n const events = [\n 'click',\n CustomEvents.events.activate,\n CustomEvents.events.keyboardActivate\n ];\n CustomEvents.define(document, events);\n\n // Register events.\n events.forEach((event) => {\n document.addEventListener(event, (e) => {\n // Always fetch the latest information when we click as state is a fickle thing.\n let {firstActive, lastActive, sifirst, silast} = onClickVariables();\n let itemToReset = '';\n\n // Prevent the usual form behaviour.\n if (e.target.closest(selectors.formDropdown)) {\n e.preventDefault();\n }\n\n // Handle the state of active initials before form submission.\n if (e.target.closest(`${selectors.formDropdown} .${selectors.pageListItem}`)) {\n // Ensure the li items don't cause weird clicking emptying out the form.\n if (e.target.classList.contains(selectors.pageListItem)) {\n return;\n }\n\n const initialsBar = e.target.closest(selectors.initialBars); // Find out which initial bar we are in.\n\n // We want to find the current active item in the menu area the user selected.\n // We also want to fetch the raw item out of the array for instant manipulation.\n if (initialsBar.classList.contains(selectors.firstInitial)) {\n sifirst = e.target;\n itemToReset = firstActive;\n } else {\n silast = e.target;\n itemToReset = lastActive;\n }\n swapActiveItems(itemToReset, e);\n }\n\n // Handle form submissions.\n if (e.target.closest(`${selectors.formDropdown}`) && e.target.type === selectors.formItems.type) {\n if (e.target.dataset.action === selectors.formItems.save) {\n // Ensure we strip out the value (All) as it messes with the PHP side of the initials bar.\n // Then we will redirect the user back onto the page with new filters applied.\n const params = {\n 'id': e.target.closest(selectors.formDropdown).dataset.courseid,\n 'gpr_search': gpr_search !== null ? gpr_search : '',\n 'sifirst': sifirst.parentElement.classList.contains('initialbarall') ? '' : sifirst.value,\n 'silast': silast.parentElement.classList.contains('initialbarall') ? '' : silast.value,\n };\n if (gpr_userid !== null) {\n params.gpr_userid = gpr_userid;\n }\n window.location = Url.relativeUrl(callingLink, params);\n }\n if (e.target.dataset.action === selectors.formItems.cancel) {\n $(`.${selectors.targetButton}`).dropdown('toggle');\n }\n }\n });\n });\n};\n\n/**\n * A small abstracted helper function which allows us to ensure we have up-to-date lists of nodes.\n *\n * @returns {{firstActive: HTMLElement, lastActive: HTMLElement, sifirst: ?String, silast: ?String}}\n */\nconst onClickVariables = () => {\n // Ensure we have an up-to-date initials bar.\n const firstItems = [...document.querySelectorAll(`.${selectors.firstInitial} li`)];\n const lastItems = [...document.querySelectorAll(`.${selectors.lastInitial} li`)];\n const firstActive = firstItems.filter((item) => item.classList.contains(selectors.activeItem))[0];\n const lastActive = lastItems.filter((item) => item.classList.contains(selectors.activeItem))[0];\n // Ensure we retain both of the selections from a previous instance.\n let sifirst = firstActive.querySelector(selectors.pageClickableItem);\n let silast = lastActive.querySelector(selectors.pageClickableItem);\n return {firstActive, lastActive, sifirst, silast};\n};\n\n/**\n * Given we are provided the old li and current click event, swap around the active properties.\n *\n * @param {HTMLElement} itemToReset\n * @param {Event} e\n */\nconst swapActiveItems = (itemToReset, e) => {\n itemToReset.classList.remove(selectors.activeItem);\n itemToReset.querySelector(selectors.pageClickableItem).ariaCurrent = false;\n\n // Set the select item as the current item.\n const itemToSetActive = e.target.parentElement;\n itemToSetActive.classList.add(selectors.activeItem);\n e.target.ariaCurrent = true;\n};\n"],"names":["registered","selectors","type","save","cancel","callingLink","gpr_userid","gpr_search","pendingPromise","Pending","registerListenerEvents","on","document","querySelector","focus","preventScroll","resolve","events","CustomEvents","activate","keyboardActivate","define","forEach","event","addEventListener","e","firstActive","lastActive","sifirst","silast","onClickVariables","itemToReset","target","closest","preventDefault","classList","contains","swapActiveItems","dataset","action","params","courseid","parentElement","value","window","location","Url","relativeUrl","dropdown","firstItems","querySelectorAll","lastItems","filter","item","remove","ariaCurrent","add"],"mappings":";;;;;;;;;44BAmCIA,YAAa,QAGXC,uBACY,YADZA,4BAEiB,aAFjBA,qBAGU,SAHVA,uBAIY,wBAJZA,wBAKa,qBALbA,uBAMY,eANZA,sBAOW,cAPXA,sBAQW,cARXA,uBASY,iBATZA,oBAUS,CACPC,KAAM,SACNC,KAAM,OACNC,OAAQ,wBAWI,SAACC,iBAAaC,kEAAa,KAAMC,kEAAa,QAC1DP,wBAGEQ,eAAiB,IAAIC,iBAC3BC,uBAAuBL,YAAaC,WAAYC,gCAE9CN,yBAAyBU,GAAG,qBAAqB,KAC/CC,SAASC,cAAcZ,6BAA6Ba,MAAM,CAACC,eAAe,OAE9EP,eAAeQ,UACfhB,YAAa,SAUXU,uBAAyB,SAACL,iBAAaC,kEAAa,KAAMC,kEAAa,WACnEU,OAAS,CACX,QACAC,mCAAaD,OAAOE,SACpBD,mCAAaD,OAAOG,qDAEXC,OAAOT,SAAUK,QAG9BA,OAAOK,SAASC,QACZX,SAASY,iBAAiBD,OAAQE,QAE1BC,YAACA,YAADC,WAAcA,WAAdC,QAA0BA,QAA1BC,OAAmCA,QAAUC,mBAC7CC,YAAc,MAGdN,EAAEO,OAAOC,QAAQhC,yBACjBwB,EAAES,iBAIFT,EAAEO,OAAOC,kBAAWhC,oCAA2BA,yBAA2B,IAEtEwB,EAAEO,OAAOG,UAAUC,SAASnC,+BAIZwB,EAAEO,OAAOC,QAAQhC,uBAIrBkC,UAAUC,SAASnC,yBAC/B2B,QAAUH,EAAEO,OACZD,YAAcL,cAEdG,OAASJ,EAAEO,OACXD,YAAcJ,YAElBU,gBAAgBN,YAAaN,MAI7BA,EAAEO,OAAOC,kBAAWhC,0BAA6BwB,EAAEO,OAAO9B,OAASD,oBAAoBC,KAAM,IACzFuB,EAAEO,OAAOM,QAAQC,SAAWtC,oBAAoBE,KAAM,OAGhDqC,OAAS,IACLf,EAAEO,OAAOC,QAAQhC,wBAAwBqC,QAAQG,oBAC1B,OAAflC,WAAsBA,WAAa,WACtCqB,QAAQc,cAAcP,UAAUC,SAAS,iBAAmB,GAAKR,QAAQe,aAC1Ed,OAAOa,cAAcP,UAAUC,SAAS,iBAAmB,GAAKP,OAAOc,OAElE,OAAfrC,aACAkC,OAAOlC,WAAaA,YAExBsC,OAAOC,SAAWC,IAAIC,YAAY1C,YAAamC,QAE/Cf,EAAEO,OAAOM,QAAQC,SAAWtC,oBAAoBG,uCAC1CH,yBAA0B+C,SAAS,kBAYvDlB,iBAAmB,WAEfmB,WAAa,IAAIrC,SAASsC,4BAAqBjD,gCAC/CkD,UAAY,IAAIvC,SAASsC,4BAAqBjD,+BAC9CyB,YAAcuB,WAAWG,QAAQC,MAASA,KAAKlB,UAAUC,SAASnC,wBAAuB,GACzF0B,WAAawB,UAAUC,QAAQC,MAASA,KAAKlB,UAAUC,SAASnC,wBAAuB,OAEzF2B,QAAUF,YAAYb,cAAcZ,6BACpC4B,OAASF,WAAWd,cAAcZ,mCAC/B,CAACyB,YAAAA,YAAaC,WAAAA,WAAYC,QAAAA,QAASC,OAAAA,SASxCQ,gBAAkB,CAACN,YAAaN,KAClCM,YAAYI,UAAUmB,OAAOrD,sBAC7B8B,YAAYlB,cAAcZ,6BAA6BsD,aAAc,EAG7C9B,EAAEO,OAAOU,cACjBP,UAAUqB,IAAIvD,sBAC9BwB,EAAEO,OAAOuB,aAAc"} \ No newline at end of file diff --git a/grade/amd/src/searchwidget/initials.js b/grade/amd/src/searchwidget/initials.js index 0d3c5bc6dc5..32f2739d987 100644 --- a/grade/amd/src/searchwidget/initials.js +++ b/grade/amd/src/searchwidget/initials.js @@ -19,6 +19,8 @@ * @module core_grades/searchwidget/initials * @copyright 2022 Mathew May * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @deprecated since Moodle 4.5 - please use core_course/actionbar/initials instead. + * @todo Final deprecation in Moodle 6.0. See MDL-82421. */ import Pending from 'core/pending'; diff --git a/grade/renderer.php b/grade/renderer.php index d3f772d7f7b..f52e246a521 100644 --- a/grade/renderer.php +++ b/grade/renderer.php @@ -124,13 +124,22 @@ class core_grades_renderer extends plugin_renderer_base { * @param context $context Our current context. * @param string $slug The slug for the report that called this function. * @return stdClass The data to output. + * @deprecated since 4.5. Use \core_course\output\actionbar\renderer' instead. + * @todo Final deprecation in Moodle 6.0. See MDL-82421. */ + #[\core\attribute\deprecated( + replacement: null, + since: '4.5', + reason: 'Moved to \core_course\output\actionbar\renderer.' + )] public function initials_selector( object $course, context $context, string $slug ): stdClass { global $SESSION, $COURSE; + + \core\deprecation::emit_deprecation_if_present([$this, __FUNCTION__]); // User search. $searchvalue = optional_param('gpr_search', null, PARAM_NOTAGS); $userid = optional_param('grp_userid', null, PARAM_INT); diff --git a/grade/templates/initials_dropdown_form.mustache b/grade/templates/initials_dropdown_form.mustache index a363213fc6e..f2abfa832c5 100644 --- a/grade/templates/initials_dropdown_form.mustache +++ b/grade/templates/initials_dropdown_form.mustache @@ -17,6 +17,9 @@ {{! @template core_grades/initials_dropdown_form + @deprecated since Moodle 4.5 - please use core_course/initials_dropdown_form instead. + @todo Final deprecation on Moodle 6.0. See MDL-82421. + The form component of our dropdown within the tertiary navigation. Context variables required for this template: diff --git a/lang/en/course.php b/lang/en/course.php index b3da22e6ef2..66db85dba71 100644 --- a/lang/en/course.php +++ b/lang/en/course.php @@ -96,6 +96,10 @@ The setting has no effect on mobile app content download for offline usage.'; $string['enabledownloadcoursecontent'] = 'Enable download course content'; $string['errorendbeforestart'] = 'The end date ({$a}) is before the course start date.'; $string['favourite'] = 'Starred course'; +$string['filterbothactive'] = 'First ({$a->first}) Last ({$a->last})'; +$string['filterbyname'] = 'Filter by name'; +$string['filterfirstactive'] = 'First ({$a->first})'; +$string['filterlastactive'] = 'Last ({$a->last})'; $string['gradetopassnotset'] = 'This course does not have a grade to pass set. It may be set in the grade item of the course (Gradebook setup).'; $string['hideendedcoursestask'] = 'Hide courses on end date'; $string['informationformodule'] = 'Information about the {$a} activity'; diff --git a/lang/en/deprecated.txt b/lang/en/deprecated.txt index f8121dbed38..5d8b2e53361 100644 --- a/lang/en/deprecated.txt +++ b/lang/en/deprecated.txt @@ -130,3 +130,7 @@ cannotsetpassword,core_error registerwithmoodleorginfoapp,core_hub registration_help,core_admin coursemanagementoptions,core_my +filterbothactive,core_grades +filterbyname,core_grades +filterfirstactive,core_grades +filterlastactive,core_grades diff --git a/lang/en/grades.php b/lang/en/grades.php index ac1620e57c3..015628ef864 100644 --- a/lang/en/grades.php +++ b/lang/en/grades.php @@ -241,10 +241,6 @@ $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.'; @@ -917,3 +913,9 @@ $string['gradeitemadvanced'] = 'Advanced grade item options'; $string['gradeitemadvanced_help'] = 'Select all elements that should be displayed as advanced when editing grade items.'; $string['aria-toggledropdown'] = 'Toggle the following dropdown'; $string['aria:dropdowngrades'] = 'Grade items found'; + +// Deprecated since Moodle 4.5. +$string['filterbothactive'] = 'First ({$a->first}) Last ({$a->last})'; +$string['filterbyname'] = 'Filter by name'; +$string['filterfirstactive'] = 'First ({$a->first})'; +$string['filterlastactive'] = 'Last ({$a->last})';