From b1063f58911c5188d7a3d612671957d551dbb543 Mon Sep 17 00:00:00 2001 From: Mathew May Date: Fri, 19 May 2023 09:27:08 +0800 Subject: [PATCH] MDL-78097 grade: Allow the initials bar to pass current page params --- grade/amd/build/searchwidget/initials.min.js | 2 +- .../build/searchwidget/initials.min.js.map | 2 +- grade/amd/src/searchwidget/initials.js | 19 +++++-- grade/renderer.php | 4 +- .../tests/behat/tertiary_name_filter.feature | 56 +++++++++++++++++++ 5 files changed, 75 insertions(+), 8 deletions(-) diff --git a/grade/amd/build/searchwidget/initials.min.js b/grade/amd/build/searchwidget/initials.min.js index b22b67b1fb4..cd5060ccc6f 100644 --- a/grade/amd/build/searchwidget/initials.min.js +++ b/grade/amd/build/searchwidget/initials.min.js @@ -5,6 +5,6 @@ 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 - */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.closest("".concat(selectors_formDropdown))&&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}})); + */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 userid=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,searchvalue=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;if(registered)return;const pendingPromise=new _pending.default;registerListenerEvents(callingLink,userid,searchvalue),(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 userid=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,searchvalue=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,searchvalue:null!==searchvalue?searchvalue:"",sifirst:sifirst.parentElement.classList.contains("initialbarall")?"":sifirst.value,silast:silast.parentElement.classList.contains("initialbarall")?"":silast.value};null!==userid&&(params.userid=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 a9773d75475..66d30771d2a 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 */\nexport const init = (callingLink) => {\n if (registered) {\n return;\n }\n const pendingPromise = new Pending();\n registerListenerEvents(callingLink);\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 */\nconst registerListenerEvents = (callingLink) => {\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 window.location = Url.relativeUrl(callingLink, {\n 'id': e.target.closest(selectors.formDropdown).dataset.courseid,\n 'sifirst': sifirst.parentElement.classList.contains('initialbarall') ? '' : sifirst.value,\n 'silast': silast.parentElement.classList.contains('initialbarall') ? '' : silast.value,\n });\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","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","window","location","Url","relativeUrl","courseid","parentElement","value","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,wBASKC,iBACbL,wBAGEM,eAAiB,IAAIC,iBAC3BC,uBAAuBH,iCAErBJ,yBAAyBQ,GAAG,qBAAqB,KAC/CC,SAASC,cAAcV,6BAA6BW,MAAM,CAACC,eAAe,OAE9EP,eAAeQ,UACfd,YAAa,SAQXQ,uBAA0BH,oBACtBU,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,QAAQ9B,yBACjBsB,EAAES,iBAIFT,EAAEO,OAAOC,kBAAW9B,oCAA2BA,yBAA2B,IAEtEsB,EAAEO,OAAOG,UAAUC,SAASjC,+BAIZsB,EAAEO,OAAOC,QAAQ9B,uBAIrBgC,UAAUC,SAASjC,yBAC/ByB,QAAUH,EAAEO,OACZD,YAAcL,cAEdG,OAASJ,EAAEO,OACXD,YAAcJ,YAElBU,gBAAgBN,YAAaN,GAI7BA,EAAEO,OAAOC,kBAAW9B,0BAA6BsB,EAAEO,OAAO5B,OAASD,oBAAoBC,OACnFqB,EAAEO,OAAOM,QAAQC,SAAWpC,oBAAoBE,OAGhDmC,OAAOC,SAAWC,IAAIC,YAAYpC,YAAa,IACrCkB,EAAEO,OAAOC,QAAQ9B,wBAAwBmC,QAAQM,iBAC5ChB,QAAQiB,cAAcV,UAAUC,SAAS,iBAAmB,GAAKR,QAAQkB,aAC1EjB,OAAOgB,cAAcV,UAAUC,SAAS,iBAAmB,GAAKP,OAAOiB,SAGrFrB,EAAEO,OAAOM,QAAQC,SAAWpC,oBAAoBG,uCAC1CH,yBAA0B4C,SAAS,kBAYvDjB,iBAAmB,WAEfkB,WAAa,IAAIpC,SAASqC,4BAAqB9C,gCAC/C+C,UAAY,IAAItC,SAASqC,4BAAqB9C,+BAC9CuB,YAAcsB,WAAWG,QAAQC,MAASA,KAAKjB,UAAUC,SAASjC,wBAAuB,GACzFwB,WAAauB,UAAUC,QAAQC,MAASA,KAAKjB,UAAUC,SAASjC,wBAAuB,OAEzFyB,QAAUF,YAAYb,cAAcV,6BACpC0B,OAASF,WAAWd,cAAcV,mCAC/B,CAACuB,YAAAA,YAAaC,WAAAA,WAAYC,QAAAA,QAASC,OAAAA,SASxCQ,gBAAkB,CAACN,YAAaN,KAClCM,YAAYI,UAAUkB,OAAOlD,sBAC7B4B,YAAYlB,cAAcV,6BAA6BmD,aAAc,EAG7C7B,EAAEO,OAAOa,cACjBV,UAAUoB,IAAIpD,sBAC9BsB,EAAEO,OAAOsB,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 */\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} userid The user id to filter by.\n * @param {Null|String} searchvalue The search value to filter by.\n */\nexport const init = (callingLink, userid = null, searchvalue = null) => {\n if (registered) {\n return;\n }\n const pendingPromise = new Pending();\n registerListenerEvents(callingLink, userid, searchvalue);\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} userid The user id to filter by.\n * @param {Null|String} searchvalue The search value to filter by.\n */\nconst registerListenerEvents = (callingLink, userid = null, searchvalue = 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 'searchvalue': searchvalue !== null ? searchvalue : '',\n 'sifirst': sifirst.parentElement.classList.contains('initialbarall') ? '' : sifirst.value,\n 'silast': silast.parentElement.classList.contains('initialbarall') ? '' : silast.value,\n };\n if (userid !== null) {\n params.userid = 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","userid","searchvalue","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,8DAAS,KAAMC,mEAAc,QACvDP,wBAGEQ,eAAiB,IAAIC,iBAC3BC,uBAAuBL,YAAaC,OAAQC,iCAE1CN,yBAAyBU,GAAG,qBAAqB,KAC/CC,SAASC,cAAcZ,6BAA6Ba,MAAM,CAACC,eAAe,OAE9EP,eAAeQ,UACfhB,YAAa,SAUXU,uBAAyB,SAACL,iBAAaC,8DAAS,KAAMC,mEAAc,WAChEU,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,qBACxB,OAAhBlC,YAAuBA,YAAc,WACzCqB,QAAQc,cAAcP,UAAUC,SAAS,iBAAmB,GAAKR,QAAQe,aAC1Ed,OAAOa,cAAcP,UAAUC,SAAS,iBAAmB,GAAKP,OAAOc,OAEtE,OAAXrC,SACAkC,OAAOlC,OAASA,QAEpBsC,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 19fa2a830c4..abb4650e7e8 100644 --- a/grade/amd/src/searchwidget/initials.js +++ b/grade/amd/src/searchwidget/initials.js @@ -55,13 +55,15 @@ const selectors = { * 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 {Null|Number} userid The user id to filter by. + * @param {Null|String} searchvalue The search value to filter by. */ -export const init = (callingLink) => { +export const init = (callingLink, userid = null, searchvalue = null) => { if (registered) { return; } const pendingPromise = new Pending(); - registerListenerEvents(callingLink); + registerListenerEvents(callingLink, userid, searchvalue); // 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}); @@ -74,8 +76,10 @@ export const init = (callingLink) => { * Register event listeners. * * @param {String} callingLink The link to redirect upon form submission. + * @param {Null|Number} userid The user id to filter by. + * @param {Null|String} searchvalue The search value to filter by. */ -const registerListenerEvents = (callingLink) => { +const registerListenerEvents = (callingLink, userid = null, searchvalue = null) => { const events = [ 'click', CustomEvents.events.activate, @@ -121,11 +125,16 @@ const registerListenerEvents = (callingLink) => { 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, { + const params = { 'id': e.target.closest(selectors.formDropdown).dataset.courseid, + 'searchvalue': searchvalue !== null ? searchvalue : '', 'sifirst': sifirst.parentElement.classList.contains('initialbarall') ? '' : sifirst.value, 'silast': silast.parentElement.classList.contains('initialbarall') ? '' : silast.value, - }); + }; + if (userid !== null) { + params.userid = userid; + } + window.location = Url.relativeUrl(callingLink, params); } if (e.target.dataset.action === selectors.formItems.cancel) { $(`.${selectors.targetButton}`).dropdown('toggle'); diff --git a/grade/renderer.php b/grade/renderer.php index 287b8a6c1bb..634cadeb08a 100644 --- a/grade/renderer.php +++ b/grade/renderer.php @@ -117,6 +117,8 @@ class core_grades_renderer extends plugin_renderer_base { ): stdClass { global $SESSION, $COURSE; // User search. + $searchvalue = optional_param('searchvalue', null, PARAM_NOTAGS); + $userid = optional_param('userid', null, PARAM_INT); $url = new moodle_url($slug, ['id' => $course->id]); $firstinitial = $SESSION->gradereport["filterfirstname-{$context->id}"] ?? ''; $lastinitial = $SESSION->gradereport["filtersurname-{$context->id}"] ?? ''; @@ -133,7 +135,7 @@ class core_grades_renderer extends plugin_renderer_base { $currentfilter = get_string('filterlastactive', 'grades', ['last' => $lastinitial]); } - $this->page->requires->js_call_amd('core_grades/searchwidget/initials', 'init', [$slug]); + $this->page->requires->js_call_amd('core_grades/searchwidget/initials', 'init', [$slug, $userid, $searchvalue]); $formdata = (object) [ 'courseid' => $COURSE->id, diff --git a/grade/report/grader/tests/behat/tertiary_name_filter.feature b/grade/report/grader/tests/behat/tertiary_name_filter.feature index 5404046a25d..78c91974e12 100644 --- a/grade/report/grader/tests/behat/tertiary_name_filter.feature +++ b/grade/report/grader/tests/behat/tertiary_name_filter.feature @@ -149,6 +149,62 @@ Feature: Within the grader report, test that we can open our generic filter drop | User Test | | Turtle Manatee | + Scenario: A teacher can search and then filter by first or last name + Given I set the field "Search users" to "Student 1" + And I click on "Student 1" in the "user" search widget + And I click on "Filter by name" "combobox" + And I select "S" in the "First name" "core_grades > initials bar" + When I press "Apply" + And the field "Search users" matches value "Student 1" + Then the following should exist in the "user-grades" table: + | -1- | + | Student 1 | + And the following should not exist in the "user-grades" table: + | -1- | + | Teacher 1 | + | Dummy User | + | User Example | + | User Test | + | Turtle Manatee | + And I click on "First (S)" "combobox" + And I select "M" in the "First name" "core_grades > initials bar" + And I press "Apply" + And the following should not exist in the "user-grades" table: + | -1- | + | Teacher 1 | + | Student 1 | + | Dummy User | + | User Example | + | User Test | + | Turtle Manatee | + + Scenario: A teacher can search for all users then filter with the initials bar + Given I set the field "Search users" to "User" + And I click on "View all results (3)" "option_role" + And the following should exist in the "user-grades" table: + | -1- | + | Dummy User | + | User Example | + | User Test | + And the following should not exist in the "user-grades" table: + | -1- | + | Teacher 1 | + | Student 1 | + | Turtle Manatee | + When I click on "Filter by name" "combobox" + And I select "E" in the "Last name" "core_grades > initials bar" + And I press "Apply" + Then the following should exist in the "user-grades" table: + | -1- | + | User Example | + And the following should not exist in the "user-grades" table: + | -1- | + | Teacher 1 | + | Student 1 | + | Dummy User | + | User Test | + | Turtle Manatee | + # This can be expanded for left/right/home & end keys but will have to be done in conjunction with the non mini render. @accessibility Scenario: A teacher can set focus and navigate the filter with the keyboard