From 1d8dd426aa2659e32ca7548a43eced3e3302bc5c Mon Sep 17 00:00:00 2001 From: Jake Dallimore Date: Fri, 19 Jan 2024 16:14:32 +0800 Subject: [PATCH] MDL-80673 mod_forum: fix modal creation when viewing student grades Fixes a regression caused by MDL-78324. --- .../amd/build/local/grades/grader.min.js | 2 +- .../amd/build/local/grades/grader.min.js.map | 2 +- mod/forum/amd/src/local/grades/grader.js | 19 +++++++++---------- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/mod/forum/amd/build/local/grades/grader.min.js b/mod/forum/amd/build/local/grades/grader.min.js index 53631d1beea..1389248dacf 100644 --- a/mod/forum/amd/build/local/grades/grader.min.js +++ b/mod/forum/amd/build/local/grades/grader.min.js @@ -5,6 +5,6 @@ define("mod_forum/local/grades/grader",["exports","core/templates","./local/grad * @module mod_forum/local/grades/grader * @copyright 2019 Mathew May * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),Object.defineProperty(_exports,"getGradingPanelFunctions",{enumerable:!0,get:function(){return _gradingpanel.default}}),_exports.view=_exports.launch=void 0,_templates=_interopRequireDefault(_templates),_selectors=_interopRequireDefault(_selectors),_user_picker=_interopRequireDefault(_user_picker),_gradingpanel=_interopRequireDefault(_gradingpanel),_modal_cancel=_interopRequireDefault(_modal_cancel),_drawer_events=_interopRequireDefault(_drawer_events);const templateNames_grader={app:"mod_forum/local/grades/grader",gradingPanel:{error:"mod_forum/local/grades/local/grader/gradingpanel/error"},searchResults:"mod_forum/local/grades/local/grader/user_picker/user_search",status:"mod_forum/local/grades/local/grader/status"},displayUserPicker=(root,html)=>{const pickerRegion=root.querySelector(_selectors.default.regions.pickerRegion);_templates.default.replaceNodeContents(pickerRegion,html,"")},fetchContentFromRender=(html,js)=>[html,js],getUpdateUserContentFunction=(root,getContentForUser,getGradeForUser,saveGradeForUser)=>{let firstLoad=!0;return async user=>{const spinner=firstLoad?null:(0,_loadingicon.addIconToContainerWithPromise)(root),[[html,js],userGrade]=await Promise.all([getContentForUser(user.id).then(fetchContentFromRender),getGradeForUser(user.id)]);_templates.default.replaceNodeContents(root.querySelector(_selectors.default.regions.moduleReplace),html,js);const[gradingPanelHtml,gradingPanelJS]=await _templates.default.render(userGrade.templatename,userGrade.grade).then(fetchContentFromRender),panelContainer=root.querySelector(_selectors.default.regions.gradingPanelContainer),panel=panelContainer.querySelector(_selectors.default.regions.gradingPanel);_templates.default.replaceNodeContents(panel,gradingPanelHtml,gradingPanelJS);const form=panel.querySelector("form");return(0,_comparison.fillInitialValues)(form),form.addEventListener("submit",(event=>{saveGradeForUser(user),event.preventDefault()})),panelContainer.scrollTop=0,firstLoad=!1,spinner&&spinner.resolve(),userGrade}},hideSearchResultContainer=(bodyContainer,userPickerContainer,searchResultsContainer)=>{bodyContainer.classList.remove("hidden"),userPickerContainer.classList.remove("hidden"),searchResultsContainer.classList.add("hidden")},hideUserSearchInput=(toggleSearchButton,searchContainer,searchInput)=>{searchContainer.classList.add("collapsed"),toggleSearchButton.setAttribute("aria-expanded","false"),toggleSearchButton.classList.add("collapse"),toggleSearchButton.classList.remove("expand"),toggleSearchButton.focus();searchContainer.parentElement.querySelector(_selectors.default.regions.gradingInfoContainer).removeAttribute("aria-hidden");const collapseGradingDrawer=searchContainer.parentElement.querySelector(_selectors.default.buttons.collapseGradingDrawer);collapseGradingDrawer.removeAttribute("aria-hidden"),collapseGradingDrawer.setAttribute("tabindex","0"),searchInput.value=""},renderSearchResults=async(searchResultsContainer,users)=>{const{html:html,js:js}=await _templates.default.renderForPromise(templateNames_grader.searchResults,{users:users});_templates.default.replaceNodeContents(searchResultsContainer,html,js)},registerEventListeners=(graderLayout,userPicker,saveGradeFunction,userList)=>{const graderContainer=graderLayout.getContainer(),toggleSearchButton=graderContainer.querySelector(_selectors.default.buttons.toggleSearch),searchInputContainer=graderContainer.querySelector(_selectors.default.regions.userSearchContainer),searchInput=searchInputContainer.querySelector(_selectors.default.regions.userSearchInput),bodyContainer=graderContainer.querySelector(_selectors.default.regions.bodyContainer),userPickerContainer=graderContainer.querySelector(_selectors.default.regions.pickerRegion),searchResultsContainer=graderContainer.querySelector(_selectors.default.regions.searchResultsContainer);graderContainer.addEventListener("click",(e=>{if(e.target.closest(_selectors.default.buttons.toggleFullscreen))return e.stopImmediatePropagation(),e.preventDefault(),void graderLayout.toggleFullscreen();if(e.target.closest(_selectors.default.buttons.closeGrader))return e.stopImmediatePropagation(),e.preventDefault(),void graderLayout.close();if(e.target.closest(_selectors.default.buttons.saveGrade)&&saveGradeFunction(userPicker.currentUser),e.target.closest(_selectors.default.buttons.toggleSearch))return void("true"===toggleSearchButton.getAttribute("aria-expanded")?(hideUserSearchInput(toggleSearchButton,searchInputContainer,searchInput),hideSearchResultContainer(bodyContainer,userPickerContainer,searchResultsContainer),searchResultsContainer.innerHTML=""):(((toggleSearchButton,searchContainer,searchInput)=>{searchContainer.classList.remove("collapsed"),toggleSearchButton.setAttribute("aria-expanded","true"),toggleSearchButton.classList.add("expand"),toggleSearchButton.classList.remove("collapse"),searchContainer.parentElement.querySelector(_selectors.default.regions.gradingInfoContainer).setAttribute("aria-hidden","true");const collapseGradingDrawer=searchContainer.parentElement.querySelector(_selectors.default.buttons.collapseGradingDrawer);collapseGradingDrawer.setAttribute("aria-hidden","true"),collapseGradingDrawer.setAttribute("tabindex","-1"),searchInput.focus()})(toggleSearchButton,searchInputContainer,searchInput),((bodyContainer,userPickerContainer,searchResultsContainer)=>{bodyContainer.classList.add("hidden"),userPickerContainer.classList.add("hidden"),searchResultsContainer.classList.remove("hidden")})(bodyContainer,userPickerContainer,searchResultsContainer),renderSearchResults(searchResultsContainer,userList)));const selectUserButton=e.target.closest(_selectors.default.buttons.selectUser);if(selectUserButton){const userId=selectUserButton.getAttribute("data-userid"),user=userList.find((user=>user.id==userId));userPicker.setUserId(userId),userPicker.showUser(user),hideUserSearchInput(toggleSearchButton,searchInputContainer,searchInput),hideSearchResultContainer(bodyContainer,userPickerContainer,searchResultsContainer),searchResultsContainer.innerHTML=""}})),searchInput.addEventListener("input",(0,_utils.debounce)((()=>{const users=((userList,searchTerm)=>""===searchTerm?userList:(searchTerm=searchTerm.toLowerCase(),userList.filter((user=>user.fullname.toLowerCase().includes(searchTerm)))))(userList,searchInput.value);renderSearchResults(searchResultsContainer,users)}),300)),(0,_pubsub.subscribe)(_drawer_events.default.DRAWER_HIDDEN,(drawerRoot=>{drawerRoot[0].querySelector(_selectors.default.regions.gradingPanel)&&setContentContainerMargin(graderContainer,0)})),(0,_pubsub.subscribe)(_drawer_events.default.DRAWER_SHOWN,(drawerRoot=>{const gradingPanel=drawerRoot[0];gradingPanel.querySelector(_selectors.default.regions.gradingPanel)&&setContentContainerMargin(graderContainer,gradingPanel.offsetWidth)}))},setContentContainerMargin=(graderContainer,rightMargin)=>{const contentContainer=graderContainer.querySelector(_selectors.default.regions.moduleContainer);contentContainer&&(contentContainer.style.marginRight="".concat(rightMargin,"px"))},getSaveUserGradeFunction=(root,setGradeForUser)=>async user=>{try{root.querySelector(_selectors.default.regions.gradingPanelErrors).innerHTML="";const result=await setGradeForUser(user.id,root.querySelector(_selectors.default.values.sendStudentNotifications).value,root.querySelector(_selectors.default.regions.gradingPanel));return result.success&&(0,_toast.add)(await(0,_str.getString)("grades:gradesavedfor","mod_forum",user)),result.failed&&displayGradingError(root,user,result.error),result}catch(err){return displayGradingError(root,user,err),(0,_normalise.failedUpdate)(err)}},displayGradingError=async(root,user,err)=>{const[{html:html,js:js},errorString]=await Promise.all([_templates.default.renderForPromise(templateNames_grader.gradingPanel.error,{error:err}),await(0,_str.getString)("grades:gradesavefailed","mod_forum",{error:err.message,...user})]);_templates.default.replaceNodeContents(root.querySelector(_selectors.default.regions.gradingPanelErrors),html,js),(0,_toast.add)(errorString,{type:"warning"})};_exports.launch=async function(getListOfUsers,getContentForUser,getGradeForUser,setGradeForUser){let{initialUserId:initialUserId=null,moduleName:moduleName,courseName:courseName,courseUrl:courseUrl,sendStudentNotifications:sendStudentNotifications,focusOnClose:focusOnClose=null}=arguments.length>4&&void 0!==arguments[4]?arguments[4]:{};const userList=await getListOfUsers();if(!userList.length)return void(0,_notification.addNotification)({message:await(0,_str.getString)("nouserstograde","core_grades"),type:"error"});const[graderLayout,{html:html,js:js}]=await Promise.all([(0,_fullscreen.createLayout)({fullscreen:!1,showLoader:!1,focusOnClose:focusOnClose}),_templates.default.renderForPromise(templateNames_grader.app,{moduleName:moduleName,courseName:courseName,courseUrl:courseUrl,drawer:{show:!0},defaultsendnotifications:sendStudentNotifications})]),graderContainer=graderLayout.getContainer(),saveGradeFunction=getSaveUserGradeFunction(graderContainer,setGradeForUser);_templates.default.replaceNodeContents(graderContainer,html,js);const updateUserContent=getUpdateUserContentFunction(graderContainer,getContentForUser,getGradeForUser,saveGradeFunction),userIds=userList.map((user=>user.id)),statusContainer=graderContainer.querySelector(_selectors.default.regions.statusContainer),userPicker=await(0,_user_picker.default)(userList,(async user=>{const renderContext={status:(await updateUserContent(user)).hasgrade,index:userIds.indexOf(user.id)+1,total:userList.length};_templates.default.render(templateNames_grader.status,renderContext).then((html=>(statusContainer.innerHTML=html,html))).catch()}),saveGradeFunction,{initialUserId:initialUserId});registerEventListeners(graderLayout,userPicker,saveGradeFunction,userList),displayUserPicker(graderContainer,userPicker.rootNode)};_exports.view=async function(getGradeForUser,userid,moduleName){let{focusOnClose:focusOnClose=null}=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{};const[userGrade,modal]=await Promise.all([getGradeForUser(userid),_modal_cancel.default.create({title:moduleName,large:!0,removeOnClose:!0,returnElement:focusOnClose,show:!0,body:(0,_loadingicon.getIcon)()})]);modal.setBodyContent(_templates.default.renderForPromise("mod_forum/local/grades/view_grade",userGrade));const[{html:html,js:js}]=await Promise.all([modal.getBodyPromise(),renderGradeTemplate(userGrade)]),gradeReplace=modal.getRoot()[0].querySelector('[data-region="grade-template"]');_templates.default.replaceNodeContents(gradeReplace,html,js)};const renderGradeTemplate=userGrade=>_templates.default.renderForPromise(userGrade.templatename,userGrade.grade)})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),Object.defineProperty(_exports,"getGradingPanelFunctions",{enumerable:!0,get:function(){return _gradingpanel.default}}),_exports.view=_exports.launch=void 0,_templates=_interopRequireDefault(_templates),_selectors=_interopRequireDefault(_selectors),_user_picker=_interopRequireDefault(_user_picker),_gradingpanel=_interopRequireDefault(_gradingpanel),_modal_cancel=_interopRequireDefault(_modal_cancel),_drawer_events=_interopRequireDefault(_drawer_events);const templateNames_grader={app:"mod_forum/local/grades/grader",gradingPanel:{error:"mod_forum/local/grades/local/grader/gradingpanel/error"},searchResults:"mod_forum/local/grades/local/grader/user_picker/user_search",status:"mod_forum/local/grades/local/grader/status"},displayUserPicker=(root,html)=>{const pickerRegion=root.querySelector(_selectors.default.regions.pickerRegion);_templates.default.replaceNodeContents(pickerRegion,html,"")},fetchContentFromRender=(html,js)=>[html,js],getUpdateUserContentFunction=(root,getContentForUser,getGradeForUser,saveGradeForUser)=>{let firstLoad=!0;return async user=>{const spinner=firstLoad?null:(0,_loadingicon.addIconToContainerWithPromise)(root),[[html,js],userGrade]=await Promise.all([getContentForUser(user.id).then(fetchContentFromRender),getGradeForUser(user.id)]);_templates.default.replaceNodeContents(root.querySelector(_selectors.default.regions.moduleReplace),html,js);const[gradingPanelHtml,gradingPanelJS]=await _templates.default.render(userGrade.templatename,userGrade.grade).then(fetchContentFromRender),panelContainer=root.querySelector(_selectors.default.regions.gradingPanelContainer),panel=panelContainer.querySelector(_selectors.default.regions.gradingPanel);_templates.default.replaceNodeContents(panel,gradingPanelHtml,gradingPanelJS);const form=panel.querySelector("form");return(0,_comparison.fillInitialValues)(form),form.addEventListener("submit",(event=>{saveGradeForUser(user),event.preventDefault()})),panelContainer.scrollTop=0,firstLoad=!1,spinner&&spinner.resolve(),userGrade}},hideSearchResultContainer=(bodyContainer,userPickerContainer,searchResultsContainer)=>{bodyContainer.classList.remove("hidden"),userPickerContainer.classList.remove("hidden"),searchResultsContainer.classList.add("hidden")},hideUserSearchInput=(toggleSearchButton,searchContainer,searchInput)=>{searchContainer.classList.add("collapsed"),toggleSearchButton.setAttribute("aria-expanded","false"),toggleSearchButton.classList.add("collapse"),toggleSearchButton.classList.remove("expand"),toggleSearchButton.focus();searchContainer.parentElement.querySelector(_selectors.default.regions.gradingInfoContainer).removeAttribute("aria-hidden");const collapseGradingDrawer=searchContainer.parentElement.querySelector(_selectors.default.buttons.collapseGradingDrawer);collapseGradingDrawer.removeAttribute("aria-hidden"),collapseGradingDrawer.setAttribute("tabindex","0"),searchInput.value=""},renderSearchResults=async(searchResultsContainer,users)=>{const{html:html,js:js}=await _templates.default.renderForPromise(templateNames_grader.searchResults,{users:users});_templates.default.replaceNodeContents(searchResultsContainer,html,js)},registerEventListeners=(graderLayout,userPicker,saveGradeFunction,userList)=>{const graderContainer=graderLayout.getContainer(),toggleSearchButton=graderContainer.querySelector(_selectors.default.buttons.toggleSearch),searchInputContainer=graderContainer.querySelector(_selectors.default.regions.userSearchContainer),searchInput=searchInputContainer.querySelector(_selectors.default.regions.userSearchInput),bodyContainer=graderContainer.querySelector(_selectors.default.regions.bodyContainer),userPickerContainer=graderContainer.querySelector(_selectors.default.regions.pickerRegion),searchResultsContainer=graderContainer.querySelector(_selectors.default.regions.searchResultsContainer);graderContainer.addEventListener("click",(e=>{if(e.target.closest(_selectors.default.buttons.toggleFullscreen))return e.stopImmediatePropagation(),e.preventDefault(),void graderLayout.toggleFullscreen();if(e.target.closest(_selectors.default.buttons.closeGrader))return e.stopImmediatePropagation(),e.preventDefault(),void graderLayout.close();if(e.target.closest(_selectors.default.buttons.saveGrade)&&saveGradeFunction(userPicker.currentUser),e.target.closest(_selectors.default.buttons.toggleSearch))return void("true"===toggleSearchButton.getAttribute("aria-expanded")?(hideUserSearchInput(toggleSearchButton,searchInputContainer,searchInput),hideSearchResultContainer(bodyContainer,userPickerContainer,searchResultsContainer),searchResultsContainer.innerHTML=""):(((toggleSearchButton,searchContainer,searchInput)=>{searchContainer.classList.remove("collapsed"),toggleSearchButton.setAttribute("aria-expanded","true"),toggleSearchButton.classList.add("expand"),toggleSearchButton.classList.remove("collapse"),searchContainer.parentElement.querySelector(_selectors.default.regions.gradingInfoContainer).setAttribute("aria-hidden","true");const collapseGradingDrawer=searchContainer.parentElement.querySelector(_selectors.default.buttons.collapseGradingDrawer);collapseGradingDrawer.setAttribute("aria-hidden","true"),collapseGradingDrawer.setAttribute("tabindex","-1"),searchInput.focus()})(toggleSearchButton,searchInputContainer,searchInput),((bodyContainer,userPickerContainer,searchResultsContainer)=>{bodyContainer.classList.add("hidden"),userPickerContainer.classList.add("hidden"),searchResultsContainer.classList.remove("hidden")})(bodyContainer,userPickerContainer,searchResultsContainer),renderSearchResults(searchResultsContainer,userList)));const selectUserButton=e.target.closest(_selectors.default.buttons.selectUser);if(selectUserButton){const userId=selectUserButton.getAttribute("data-userid"),user=userList.find((user=>user.id==userId));userPicker.setUserId(userId),userPicker.showUser(user),hideUserSearchInput(toggleSearchButton,searchInputContainer,searchInput),hideSearchResultContainer(bodyContainer,userPickerContainer,searchResultsContainer),searchResultsContainer.innerHTML=""}})),searchInput.addEventListener("input",(0,_utils.debounce)((()=>{const users=((userList,searchTerm)=>""===searchTerm?userList:(searchTerm=searchTerm.toLowerCase(),userList.filter((user=>user.fullname.toLowerCase().includes(searchTerm)))))(userList,searchInput.value);renderSearchResults(searchResultsContainer,users)}),300)),(0,_pubsub.subscribe)(_drawer_events.default.DRAWER_HIDDEN,(drawerRoot=>{drawerRoot[0].querySelector(_selectors.default.regions.gradingPanel)&&setContentContainerMargin(graderContainer,0)})),(0,_pubsub.subscribe)(_drawer_events.default.DRAWER_SHOWN,(drawerRoot=>{const gradingPanel=drawerRoot[0];gradingPanel.querySelector(_selectors.default.regions.gradingPanel)&&setContentContainerMargin(graderContainer,gradingPanel.offsetWidth)}))},setContentContainerMargin=(graderContainer,rightMargin)=>{const contentContainer=graderContainer.querySelector(_selectors.default.regions.moduleContainer);contentContainer&&(contentContainer.style.marginRight="".concat(rightMargin,"px"))},getSaveUserGradeFunction=(root,setGradeForUser)=>async user=>{try{root.querySelector(_selectors.default.regions.gradingPanelErrors).innerHTML="";const result=await setGradeForUser(user.id,root.querySelector(_selectors.default.values.sendStudentNotifications).value,root.querySelector(_selectors.default.regions.gradingPanel));return result.success&&(0,_toast.add)(await(0,_str.getString)("grades:gradesavedfor","mod_forum",user)),result.failed&&displayGradingError(root,user,result.error),result}catch(err){return displayGradingError(root,user,err),(0,_normalise.failedUpdate)(err)}},displayGradingError=async(root,user,err)=>{const[{html:html,js:js},errorString]=await Promise.all([_templates.default.renderForPromise(templateNames_grader.gradingPanel.error,{error:err}),await(0,_str.getString)("grades:gradesavefailed","mod_forum",{error:err.message,...user})]);_templates.default.replaceNodeContents(root.querySelector(_selectors.default.regions.gradingPanelErrors),html,js),(0,_toast.add)(errorString,{type:"warning"})};_exports.launch=async function(getListOfUsers,getContentForUser,getGradeForUser,setGradeForUser){let{initialUserId:initialUserId=null,moduleName:moduleName,courseName:courseName,courseUrl:courseUrl,sendStudentNotifications:sendStudentNotifications,focusOnClose:focusOnClose=null}=arguments.length>4&&void 0!==arguments[4]?arguments[4]:{};const userList=await getListOfUsers();if(!userList.length)return void(0,_notification.addNotification)({message:await(0,_str.getString)("nouserstograde","core_grades"),type:"error"});const[graderLayout,{html:html,js:js}]=await Promise.all([(0,_fullscreen.createLayout)({fullscreen:!1,showLoader:!1,focusOnClose:focusOnClose}),_templates.default.renderForPromise(templateNames_grader.app,{moduleName:moduleName,courseName:courseName,courseUrl:courseUrl,drawer:{show:!0},defaultsendnotifications:sendStudentNotifications})]),graderContainer=graderLayout.getContainer(),saveGradeFunction=getSaveUserGradeFunction(graderContainer,setGradeForUser);_templates.default.replaceNodeContents(graderContainer,html,js);const updateUserContent=getUpdateUserContentFunction(graderContainer,getContentForUser,getGradeForUser,saveGradeFunction),userIds=userList.map((user=>user.id)),statusContainer=graderContainer.querySelector(_selectors.default.regions.statusContainer),userPicker=await(0,_user_picker.default)(userList,(async user=>{const renderContext={status:(await updateUserContent(user)).hasgrade,index:userIds.indexOf(user.id)+1,total:userList.length};_templates.default.render(templateNames_grader.status,renderContext).then((html=>(statusContainer.innerHTML=html,html))).catch()}),saveGradeFunction,{initialUserId:initialUserId});registerEventListeners(graderLayout,userPicker,saveGradeFunction,userList),displayUserPicker(graderContainer,userPicker.rootNode)};_exports.view=async function(getGradeForUser,userid,moduleName){let{focusOnClose:focusOnClose=null}=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{};const userGrade=await getGradeForUser(userid),[modal,gradeTemplateData]=await Promise.all([_modal_cancel.default.create({title:moduleName,large:!0,removeOnClose:!0,returnElement:focusOnClose,show:!0,body:_templates.default.render("mod_forum/local/grades/view_grade",userGrade)}),renderGradeTemplate(userGrade)]),bodyPromise=await modal.getBodyPromise(),gradeReplace=bodyPromise[0].querySelector('[data-region="grade-template"]');_templates.default.replaceNodeContents(gradeReplace,gradeTemplateData.html,gradeTemplateData.js)};const renderGradeTemplate=userGrade=>_templates.default.renderForPromise(userGrade.templatename,userGrade.grade)})); //# sourceMappingURL=grader.min.js.map \ No newline at end of file diff --git a/mod/forum/amd/build/local/grades/grader.min.js.map b/mod/forum/amd/build/local/grades/grader.min.js.map index 695e38ccdb4..8c63830a456 100644 --- a/mod/forum/amd/build/local/grades/grader.min.js.map +++ b/mod/forum/amd/build/local/grades/grader.min.js.map @@ -1 +1 @@ -{"version":3,"file":"grader.min.js","sources":["../../../src/local/grades/grader.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 * This module will tie together all of the different calls the gradable module will make.\n *\n * @module mod_forum/local/grades/grader\n * @copyright 2019 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport Templates from 'core/templates';\nimport Selectors from './local/grader/selectors';\nimport getUserPicker from './local/grader/user_picker';\nimport {createLayout as createFullScreenWindow} from 'mod_forum/local/layout/fullscreen';\nimport getGradingPanelFunctions from './local/grader/gradingpanel';\nimport {add as addToast} from 'core/toast';\nimport {addNotification} from 'core/notification';\nimport {getString} from 'core/str';\nimport {failedUpdate} from 'core_grades/grades/grader/gradingpanel/normalise';\nimport {addIconToContainerWithPromise, getIcon as getSpinner} from 'core/loadingicon';\nimport {debounce} from 'core/utils';\nimport {fillInitialValues} from 'core_grades/grades/grader/gradingpanel/comparison';\nimport Modal from 'core/modal_cancel';\nimport {subscribe} from 'core/pubsub';\nimport DrawerEvents from 'core/drawer_events';\n\nconst templateNames = {\n grader: {\n app: 'mod_forum/local/grades/grader',\n gradingPanel: {\n error: 'mod_forum/local/grades/local/grader/gradingpanel/error',\n },\n searchResults: 'mod_forum/local/grades/local/grader/user_picker/user_search',\n status: 'mod_forum/local/grades/local/grader/status',\n },\n};\n\n/**\n * Helper function that replaces the user picker placeholder with what we get back from the user picker class.\n *\n * @param {HTMLElement} root\n * @param {String} html\n */\nconst displayUserPicker = (root, html) => {\n const pickerRegion = root.querySelector(Selectors.regions.pickerRegion);\n Templates.replaceNodeContents(pickerRegion, html, '');\n};\n\n/**\n * To be removed, this is now done as a part of Templates.renderForPromise()\n *\n * @param {String} html\n * @param {String} js\n * @returns {array} An array containing the HTML, and JS.\n */\nconst fetchContentFromRender = (html, js) => {\n return [html, js];\n};\n\n/**\n * Here we build the function that is passed to the user picker that'll handle updating the user content area\n * of the grading interface.\n *\n * @param {HTMLElement} root\n * @param {Function} getContentForUser\n * @param {Function} getGradeForUser\n * @param {Function} saveGradeForUser\n * @return {Function}\n */\nconst getUpdateUserContentFunction = (root, getContentForUser, getGradeForUser, saveGradeForUser) => {\n let firstLoad = true;\n\n return async(user) => {\n const spinner = firstLoad ? null : addIconToContainerWithPromise(root);\n const [\n [html, js],\n userGrade,\n ] = await Promise.all([\n getContentForUser(user.id).then(fetchContentFromRender),\n getGradeForUser(user.id),\n ]);\n Templates.replaceNodeContents(root.querySelector(Selectors.regions.moduleReplace), html, js);\n\n const [\n gradingPanelHtml,\n gradingPanelJS\n ] = await Templates.render(userGrade.templatename, userGrade.grade).then(fetchContentFromRender);\n const panelContainer = root.querySelector(Selectors.regions.gradingPanelContainer);\n const panel = panelContainer.querySelector(Selectors.regions.gradingPanel);\n Templates.replaceNodeContents(panel, gradingPanelHtml, gradingPanelJS);\n\n const form = panel.querySelector('form');\n fillInitialValues(form);\n\n form.addEventListener('submit', event => {\n saveGradeForUser(user);\n event.preventDefault();\n });\n\n panelContainer.scrollTop = 0;\n firstLoad = false;\n\n if (spinner) {\n spinner.resolve();\n }\n return userGrade;\n };\n};\n\n/**\n * Show the search results container and hide the user picker and body content.\n *\n * @param {HTMLElement} bodyContainer The container element for the body content\n * @param {HTMLElement} userPickerContainer The container element for the user picker\n * @param {HTMLElement} searchResultsContainer The container element for the search results\n */\nconst showSearchResultContainer = (bodyContainer, userPickerContainer, searchResultsContainer) => {\n bodyContainer.classList.add('hidden');\n userPickerContainer.classList.add('hidden');\n searchResultsContainer.classList.remove('hidden');\n};\n\n/**\n * Hide the search results container and show the user picker and body content.\n *\n * @param {HTMLElement} bodyContainer The container element for the body content\n * @param {HTMLElement} userPickerContainer The container element for the user picker\n * @param {HTMLElement} searchResultsContainer The container element for the search results\n */\nconst hideSearchResultContainer = (bodyContainer, userPickerContainer, searchResultsContainer) => {\n bodyContainer.classList.remove('hidden');\n userPickerContainer.classList.remove('hidden');\n searchResultsContainer.classList.add('hidden');\n};\n\n/**\n * Toggles the visibility of the user search.\n *\n * @param {HTMLElement} toggleSearchButton The button that toggles the search\n * @param {HTMLElement} searchContainer The container element for the user search\n * @param {HTMLElement} searchInput The input element for searching\n */\nconst showUserSearchInput = (toggleSearchButton, searchContainer, searchInput) => {\n searchContainer.classList.remove('collapsed');\n toggleSearchButton.setAttribute('aria-expanded', 'true');\n toggleSearchButton.classList.add('expand');\n toggleSearchButton.classList.remove('collapse');\n\n // Hide the grading info container from screen reader.\n const gradingInfoContainer = searchContainer.parentElement.querySelector(Selectors.regions.gradingInfoContainer);\n gradingInfoContainer.setAttribute('aria-hidden', 'true');\n\n // Hide the collapse grading drawer button from screen reader.\n const collapseGradingDrawer = searchContainer.parentElement.querySelector(Selectors.buttons.collapseGradingDrawer);\n collapseGradingDrawer.setAttribute('aria-hidden', 'true');\n collapseGradingDrawer.setAttribute('tabindex', '-1');\n\n searchInput.focus();\n};\n\n/**\n * Toggles the visibility of the user search.\n *\n * @param {HTMLElement} toggleSearchButton The button that toggles the search\n * @param {HTMLElement} searchContainer The container element for the user search\n * @param {HTMLElement} searchInput The input element for searching\n */\nconst hideUserSearchInput = (toggleSearchButton, searchContainer, searchInput) => {\n searchContainer.classList.add('collapsed');\n toggleSearchButton.setAttribute('aria-expanded', 'false');\n toggleSearchButton.classList.add('collapse');\n toggleSearchButton.classList.remove('expand');\n toggleSearchButton.focus();\n\n // Show the grading info container to screen reader.\n const gradingInfoContainer = searchContainer.parentElement.querySelector(Selectors.regions.gradingInfoContainer);\n gradingInfoContainer.removeAttribute('aria-hidden');\n\n // Show the collapse grading drawer button from screen reader.\n const collapseGradingDrawer = searchContainer.parentElement.querySelector(Selectors.buttons.collapseGradingDrawer);\n collapseGradingDrawer.removeAttribute('aria-hidden');\n collapseGradingDrawer.setAttribute('tabindex', '0');\n\n searchInput.value = '';\n};\n\n/**\n * Find the list of users who's names include the given search term.\n *\n * @param {Array} userList List of users for the grader\n * @param {String} searchTerm The search term to match\n * @return {Array}\n */\nconst searchForUsers = (userList, searchTerm) => {\n if (searchTerm === '') {\n return userList;\n }\n\n searchTerm = searchTerm.toLowerCase();\n\n return userList.filter((user) => {\n return user.fullname.toLowerCase().includes(searchTerm);\n });\n};\n\n/**\n * Render the list of users in the search results area.\n *\n * @param {HTMLElement} searchResultsContainer The container element for search results\n * @param {Array} users The list of users to display\n */\nconst renderSearchResults = async(searchResultsContainer, users) => {\n const {html, js} = await Templates.renderForPromise(templateNames.grader.searchResults, {users});\n Templates.replaceNodeContents(searchResultsContainer, html, js);\n};\n\n/**\n * Add click handlers to the buttons in the header of the grading interface.\n *\n * @param {HTMLElement} graderLayout\n * @param {Object} userPicker\n * @param {Function} saveGradeFunction\n * @param {Array} userList List of users for the grader.\n */\nconst registerEventListeners = (graderLayout, userPicker, saveGradeFunction, userList) => {\n const graderContainer = graderLayout.getContainer();\n const toggleSearchButton = graderContainer.querySelector(Selectors.buttons.toggleSearch);\n const searchInputContainer = graderContainer.querySelector(Selectors.regions.userSearchContainer);\n const searchInput = searchInputContainer.querySelector(Selectors.regions.userSearchInput);\n const bodyContainer = graderContainer.querySelector(Selectors.regions.bodyContainer);\n const userPickerContainer = graderContainer.querySelector(Selectors.regions.pickerRegion);\n const searchResultsContainer = graderContainer.querySelector(Selectors.regions.searchResultsContainer);\n\n graderContainer.addEventListener('click', (e) => {\n if (e.target.closest(Selectors.buttons.toggleFullscreen)) {\n e.stopImmediatePropagation();\n e.preventDefault();\n graderLayout.toggleFullscreen();\n\n return;\n }\n\n if (e.target.closest(Selectors.buttons.closeGrader)) {\n e.stopImmediatePropagation();\n e.preventDefault();\n\n graderLayout.close();\n\n return;\n }\n\n if (e.target.closest(Selectors.buttons.saveGrade)) {\n saveGradeFunction(userPicker.currentUser);\n }\n\n if (e.target.closest(Selectors.buttons.toggleSearch)) {\n if (toggleSearchButton.getAttribute('aria-expanded') === 'true') {\n // Search is open so let's close it.\n hideUserSearchInput(toggleSearchButton, searchInputContainer, searchInput);\n hideSearchResultContainer(bodyContainer, userPickerContainer, searchResultsContainer);\n searchResultsContainer.innerHTML = '';\n } else {\n // Search is closed so let's open it.\n showUserSearchInput(toggleSearchButton, searchInputContainer, searchInput);\n showSearchResultContainer(bodyContainer, userPickerContainer, searchResultsContainer);\n renderSearchResults(searchResultsContainer, userList);\n }\n\n return;\n }\n\n const selectUserButton = e.target.closest(Selectors.buttons.selectUser);\n if (selectUserButton) {\n const userId = selectUserButton.getAttribute('data-userid');\n const user = userList.find(user => user.id == userId);\n userPicker.setUserId(userId);\n userPicker.showUser(user);\n hideUserSearchInput(toggleSearchButton, searchInputContainer, searchInput);\n hideSearchResultContainer(bodyContainer, userPickerContainer, searchResultsContainer);\n searchResultsContainer.innerHTML = '';\n }\n });\n\n // Debounce the search input so that it only executes 300 milliseconds after the user has finished typing.\n searchInput.addEventListener('input', debounce(() => {\n const users = searchForUsers(userList, searchInput.value);\n renderSearchResults(searchResultsContainer, users);\n }, 300));\n\n // Remove the right margin of the content container when the grading panel is hidden so that it expands to full-width.\n subscribe(DrawerEvents.DRAWER_HIDDEN, (drawerRoot) => {\n const gradingPanel = drawerRoot[0];\n if (gradingPanel.querySelector(Selectors.regions.gradingPanel)) {\n setContentContainerMargin(graderContainer, 0);\n }\n });\n\n // Bring back the right margin of the content container when the grading panel is shown to give space for the grading panel.\n subscribe(DrawerEvents.DRAWER_SHOWN, (drawerRoot) => {\n const gradingPanel = drawerRoot[0];\n if (gradingPanel.querySelector(Selectors.regions.gradingPanel)) {\n setContentContainerMargin(graderContainer, gradingPanel.offsetWidth);\n }\n });\n};\n\n/**\n * Adjusts the right margin of the content container.\n *\n * @param {HTMLElement} graderContainer The container for the grader app.\n * @param {Number} rightMargin The right margin value.\n */\nconst setContentContainerMargin = (graderContainer, rightMargin) => {\n const contentContainer = graderContainer.querySelector(Selectors.regions.moduleContainer);\n if (contentContainer) {\n contentContainer.style.marginRight = `${rightMargin}px`;\n }\n};\n\n/**\n * Get the function used to save a user grade.\n *\n * @param {HTMLElement} root The container for the grader\n * @param {Function} setGradeForUser The function that will be called.\n * @return {Function}\n */\nconst getSaveUserGradeFunction = (root, setGradeForUser) => {\n return async(user) => {\n try {\n root.querySelector(Selectors.regions.gradingPanelErrors).innerHTML = '';\n const result = await setGradeForUser(\n user.id,\n root.querySelector(Selectors.values.sendStudentNotifications).value,\n root.querySelector(Selectors.regions.gradingPanel)\n );\n if (result.success) {\n addToast(await getString('grades:gradesavedfor', 'mod_forum', user));\n }\n if (result.failed) {\n displayGradingError(root, user, result.error);\n }\n\n return result;\n } catch (err) {\n displayGradingError(root, user, err);\n\n return failedUpdate(err);\n }\n };\n};\n\n/**\n * Display a grading error, typically from a failed save.\n *\n * @param {HTMLElement} root The container for the grader\n * @param {Object} user The user who was errored\n * @param {Object} err The details of the error\n */\nconst displayGradingError = async(root, user, err) => {\n const [\n {html, js},\n errorString\n ] = await Promise.all([\n Templates.renderForPromise(templateNames.grader.gradingPanel.error, {error: err}),\n await getString('grades:gradesavefailed', 'mod_forum', {error: err.message, ...user}),\n ]);\n\n Templates.replaceNodeContents(root.querySelector(Selectors.regions.gradingPanelErrors), html, js);\n addToast(errorString, {type: 'warning'});\n};\n\n/**\n * Launch the grader interface with the specified parameters.\n *\n * @param {Function} getListOfUsers A function to get the list of users\n * @param {Function} getContentForUser A function to get the content for a specific user\n * @param {Function} getGradeForUser A function get the grade details for a specific user\n * @param {Function} setGradeForUser A function to set the grade for a specific user\n * @param {Object} preferences Preferences for the launch function\n * @param {Number} preferences.initialUserId\n * @param {string} preferences.moduleName\n * @param {string} preferences.courseName\n * @param {string} preferences.courseUrl\n * @param {boolean} preferences.sendStudentNotifications\n * @param {null|HTMLElement} preferences.focusOnClose\n */\nexport const launch = async(getListOfUsers, getContentForUser, getGradeForUser, setGradeForUser, {\n initialUserId = null,\n moduleName,\n courseName,\n courseUrl,\n sendStudentNotifications,\n focusOnClose = null,\n} = {}) => {\n\n // We need all of these functions to be executed in series, if one step runs before another the interface\n // will not work.\n\n // We need this promise to resolve separately so that we can avoid loading the whole interface if there are no users.\n const userList = await getListOfUsers();\n if (!userList.length) {\n addNotification({\n message: await getString('nouserstograde', 'core_grades'),\n type: \"error\",\n });\n return;\n }\n\n // Now that we have confirmed there are at least some users let's boot up the grader interface.\n const [\n graderLayout,\n {html, js},\n ] = await Promise.all([\n createFullScreenWindow({\n fullscreen: false,\n showLoader: false,\n focusOnClose,\n }),\n Templates.renderForPromise(templateNames.grader.app, {\n moduleName,\n courseName,\n courseUrl,\n drawer: {show: true},\n defaultsendnotifications: sendStudentNotifications,\n }),\n ]);\n\n const graderContainer = graderLayout.getContainer();\n\n const saveGradeFunction = getSaveUserGradeFunction(graderContainer, setGradeForUser);\n\n Templates.replaceNodeContents(graderContainer, html, js);\n const updateUserContent = getUpdateUserContentFunction(graderContainer, getContentForUser, getGradeForUser, saveGradeFunction);\n\n const userIds = userList.map(user => user.id);\n const statusContainer = graderContainer.querySelector(Selectors.regions.statusContainer);\n // Fetch the userpicker for display.\n const userPicker = await getUserPicker(\n userList,\n async(user) => {\n const userGrade = await updateUserContent(user);\n const renderContext = {\n status: userGrade.hasgrade,\n index: userIds.indexOf(user.id) + 1,\n total: userList.length\n };\n Templates.render(templateNames.grader.status, renderContext).then(html => {\n statusContainer.innerHTML = html;\n return html;\n }).catch();\n },\n saveGradeFunction,\n {\n initialUserId,\n },\n );\n\n // Register all event listeners.\n registerEventListeners(graderLayout, userPicker, saveGradeFunction, userList);\n\n // Display the newly created user picker.\n displayUserPicker(graderContainer, userPicker.rootNode);\n};\n\n/**\n * Show the grade for a specific user.\n *\n * @param {Function} getGradeForUser A function get the grade details for a specific user\n * @param {Number} userid The ID of a specific user\n * @param {String} moduleName the name of the module\n * @param {object} param\n * @param {null|HTMLElement} param.focusOnClose\n */\nexport const view = async(getGradeForUser, userid, moduleName, {\n focusOnClose = null,\n} = {}) => {\n\n const [\n userGrade,\n modal,\n ] = await Promise.all([\n getGradeForUser(userid),\n Modal.create({\n title: moduleName,\n large: true,\n removeOnClose: true,\n returnElement: focusOnClose,\n show: true,\n body: getSpinner(),\n }),\n ]);\n\n modal.setBodyContent(Templates.renderForPromise('mod_forum/local/grades/view_grade', userGrade));\n\n // Note: We do not use await here because it messes with the Modal transitions.\n const [{html, js}] = await Promise.all([modal.getBodyPromise(), renderGradeTemplate(userGrade)]);\n const gradeReplace = modal.getRoot()[0].querySelector('[data-region=\"grade-template\"]');\n Templates.replaceNodeContents(gradeReplace, html, js);\n};\n\nconst renderGradeTemplate = (userGrade) => Templates.renderForPromise(userGrade.templatename, userGrade.grade);\n\nexport {getGradingPanelFunctions};\n"],"names":["templateNames","app","gradingPanel","error","searchResults","status","displayUserPicker","root","html","pickerRegion","querySelector","Selectors","regions","replaceNodeContents","fetchContentFromRender","js","getUpdateUserContentFunction","getContentForUser","getGradeForUser","saveGradeForUser","firstLoad","async","spinner","userGrade","Promise","all","user","id","then","moduleReplace","gradingPanelHtml","gradingPanelJS","Templates","render","templatename","grade","panelContainer","gradingPanelContainer","panel","form","addEventListener","event","preventDefault","scrollTop","resolve","hideSearchResultContainer","bodyContainer","userPickerContainer","searchResultsContainer","classList","remove","add","hideUserSearchInput","toggleSearchButton","searchContainer","searchInput","setAttribute","focus","parentElement","gradingInfoContainer","removeAttribute","collapseGradingDrawer","buttons","value","renderSearchResults","users","renderForPromise","registerEventListeners","graderLayout","userPicker","saveGradeFunction","userList","graderContainer","getContainer","toggleSearch","searchInputContainer","userSearchContainer","userSearchInput","e","target","closest","toggleFullscreen","stopImmediatePropagation","closeGrader","close","saveGrade","currentUser","getAttribute","innerHTML","showUserSearchInput","showSearchResultContainer","selectUserButton","selectUser","userId","find","setUserId","showUser","searchTerm","toLowerCase","filter","fullname","includes","searchForUsers","DrawerEvents","DRAWER_HIDDEN","drawerRoot","setContentContainerMargin","DRAWER_SHOWN","offsetWidth","rightMargin","contentContainer","moduleContainer","style","marginRight","getSaveUserGradeFunction","setGradeForUser","gradingPanelErrors","result","values","sendStudentNotifications","success","failed","displayGradingError","err","errorString","message","type","getListOfUsers","initialUserId","moduleName","courseName","courseUrl","focusOnClose","length","fullscreen","showLoader","drawer","show","defaultsendnotifications","updateUserContent","userIds","map","statusContainer","renderContext","hasgrade","index","indexOf","total","catch","rootNode","userid","modal","Modal","create","title","large","removeOnClose","returnElement","body","setBodyContent","getBodyPromise","renderGradeTemplate","gradeReplace","getRoot"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"grader.min.js","sources":["../../../src/local/grades/grader.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 * This module will tie together all of the different calls the gradable module will make.\n *\n * @module mod_forum/local/grades/grader\n * @copyright 2019 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport Templates from 'core/templates';\nimport Selectors from './local/grader/selectors';\nimport getUserPicker from './local/grader/user_picker';\nimport {createLayout as createFullScreenWindow} from 'mod_forum/local/layout/fullscreen';\nimport getGradingPanelFunctions from './local/grader/gradingpanel';\nimport {add as addToast} from 'core/toast';\nimport {addNotification} from 'core/notification';\nimport {getString} from 'core/str';\nimport {failedUpdate} from 'core_grades/grades/grader/gradingpanel/normalise';\nimport {addIconToContainerWithPromise} from 'core/loadingicon';\nimport {debounce} from 'core/utils';\nimport {fillInitialValues} from 'core_grades/grades/grader/gradingpanel/comparison';\nimport Modal from 'core/modal_cancel';\nimport {subscribe} from 'core/pubsub';\nimport DrawerEvents from 'core/drawer_events';\n\nconst templateNames = {\n grader: {\n app: 'mod_forum/local/grades/grader',\n gradingPanel: {\n error: 'mod_forum/local/grades/local/grader/gradingpanel/error',\n },\n searchResults: 'mod_forum/local/grades/local/grader/user_picker/user_search',\n status: 'mod_forum/local/grades/local/grader/status',\n },\n};\n\n/**\n * Helper function that replaces the user picker placeholder with what we get back from the user picker class.\n *\n * @param {HTMLElement} root\n * @param {String} html\n */\nconst displayUserPicker = (root, html) => {\n const pickerRegion = root.querySelector(Selectors.regions.pickerRegion);\n Templates.replaceNodeContents(pickerRegion, html, '');\n};\n\n/**\n * To be removed, this is now done as a part of Templates.renderForPromise()\n *\n * @param {String} html\n * @param {String} js\n * @returns {array} An array containing the HTML, and JS.\n */\nconst fetchContentFromRender = (html, js) => {\n return [html, js];\n};\n\n/**\n * Here we build the function that is passed to the user picker that'll handle updating the user content area\n * of the grading interface.\n *\n * @param {HTMLElement} root\n * @param {Function} getContentForUser\n * @param {Function} getGradeForUser\n * @param {Function} saveGradeForUser\n * @return {Function}\n */\nconst getUpdateUserContentFunction = (root, getContentForUser, getGradeForUser, saveGradeForUser) => {\n let firstLoad = true;\n\n return async(user) => {\n const spinner = firstLoad ? null : addIconToContainerWithPromise(root);\n const [\n [html, js],\n userGrade,\n ] = await Promise.all([\n getContentForUser(user.id).then(fetchContentFromRender),\n getGradeForUser(user.id),\n ]);\n Templates.replaceNodeContents(root.querySelector(Selectors.regions.moduleReplace), html, js);\n\n const [\n gradingPanelHtml,\n gradingPanelJS\n ] = await Templates.render(userGrade.templatename, userGrade.grade).then(fetchContentFromRender);\n const panelContainer = root.querySelector(Selectors.regions.gradingPanelContainer);\n const panel = panelContainer.querySelector(Selectors.regions.gradingPanel);\n Templates.replaceNodeContents(panel, gradingPanelHtml, gradingPanelJS);\n\n const form = panel.querySelector('form');\n fillInitialValues(form);\n\n form.addEventListener('submit', event => {\n saveGradeForUser(user);\n event.preventDefault();\n });\n\n panelContainer.scrollTop = 0;\n firstLoad = false;\n\n if (spinner) {\n spinner.resolve();\n }\n return userGrade;\n };\n};\n\n/**\n * Show the search results container and hide the user picker and body content.\n *\n * @param {HTMLElement} bodyContainer The container element for the body content\n * @param {HTMLElement} userPickerContainer The container element for the user picker\n * @param {HTMLElement} searchResultsContainer The container element for the search results\n */\nconst showSearchResultContainer = (bodyContainer, userPickerContainer, searchResultsContainer) => {\n bodyContainer.classList.add('hidden');\n userPickerContainer.classList.add('hidden');\n searchResultsContainer.classList.remove('hidden');\n};\n\n/**\n * Hide the search results container and show the user picker and body content.\n *\n * @param {HTMLElement} bodyContainer The container element for the body content\n * @param {HTMLElement} userPickerContainer The container element for the user picker\n * @param {HTMLElement} searchResultsContainer The container element for the search results\n */\nconst hideSearchResultContainer = (bodyContainer, userPickerContainer, searchResultsContainer) => {\n bodyContainer.classList.remove('hidden');\n userPickerContainer.classList.remove('hidden');\n searchResultsContainer.classList.add('hidden');\n};\n\n/**\n * Toggles the visibility of the user search.\n *\n * @param {HTMLElement} toggleSearchButton The button that toggles the search\n * @param {HTMLElement} searchContainer The container element for the user search\n * @param {HTMLElement} searchInput The input element for searching\n */\nconst showUserSearchInput = (toggleSearchButton, searchContainer, searchInput) => {\n searchContainer.classList.remove('collapsed');\n toggleSearchButton.setAttribute('aria-expanded', 'true');\n toggleSearchButton.classList.add('expand');\n toggleSearchButton.classList.remove('collapse');\n\n // Hide the grading info container from screen reader.\n const gradingInfoContainer = searchContainer.parentElement.querySelector(Selectors.regions.gradingInfoContainer);\n gradingInfoContainer.setAttribute('aria-hidden', 'true');\n\n // Hide the collapse grading drawer button from screen reader.\n const collapseGradingDrawer = searchContainer.parentElement.querySelector(Selectors.buttons.collapseGradingDrawer);\n collapseGradingDrawer.setAttribute('aria-hidden', 'true');\n collapseGradingDrawer.setAttribute('tabindex', '-1');\n\n searchInput.focus();\n};\n\n/**\n * Toggles the visibility of the user search.\n *\n * @param {HTMLElement} toggleSearchButton The button that toggles the search\n * @param {HTMLElement} searchContainer The container element for the user search\n * @param {HTMLElement} searchInput The input element for searching\n */\nconst hideUserSearchInput = (toggleSearchButton, searchContainer, searchInput) => {\n searchContainer.classList.add('collapsed');\n toggleSearchButton.setAttribute('aria-expanded', 'false');\n toggleSearchButton.classList.add('collapse');\n toggleSearchButton.classList.remove('expand');\n toggleSearchButton.focus();\n\n // Show the grading info container to screen reader.\n const gradingInfoContainer = searchContainer.parentElement.querySelector(Selectors.regions.gradingInfoContainer);\n gradingInfoContainer.removeAttribute('aria-hidden');\n\n // Show the collapse grading drawer button from screen reader.\n const collapseGradingDrawer = searchContainer.parentElement.querySelector(Selectors.buttons.collapseGradingDrawer);\n collapseGradingDrawer.removeAttribute('aria-hidden');\n collapseGradingDrawer.setAttribute('tabindex', '0');\n\n searchInput.value = '';\n};\n\n/**\n * Find the list of users who's names include the given search term.\n *\n * @param {Array} userList List of users for the grader\n * @param {String} searchTerm The search term to match\n * @return {Array}\n */\nconst searchForUsers = (userList, searchTerm) => {\n if (searchTerm === '') {\n return userList;\n }\n\n searchTerm = searchTerm.toLowerCase();\n\n return userList.filter((user) => {\n return user.fullname.toLowerCase().includes(searchTerm);\n });\n};\n\n/**\n * Render the list of users in the search results area.\n *\n * @param {HTMLElement} searchResultsContainer The container element for search results\n * @param {Array} users The list of users to display\n */\nconst renderSearchResults = async(searchResultsContainer, users) => {\n const {html, js} = await Templates.renderForPromise(templateNames.grader.searchResults, {users});\n Templates.replaceNodeContents(searchResultsContainer, html, js);\n};\n\n/**\n * Add click handlers to the buttons in the header of the grading interface.\n *\n * @param {HTMLElement} graderLayout\n * @param {Object} userPicker\n * @param {Function} saveGradeFunction\n * @param {Array} userList List of users for the grader.\n */\nconst registerEventListeners = (graderLayout, userPicker, saveGradeFunction, userList) => {\n const graderContainer = graderLayout.getContainer();\n const toggleSearchButton = graderContainer.querySelector(Selectors.buttons.toggleSearch);\n const searchInputContainer = graderContainer.querySelector(Selectors.regions.userSearchContainer);\n const searchInput = searchInputContainer.querySelector(Selectors.regions.userSearchInput);\n const bodyContainer = graderContainer.querySelector(Selectors.regions.bodyContainer);\n const userPickerContainer = graderContainer.querySelector(Selectors.regions.pickerRegion);\n const searchResultsContainer = graderContainer.querySelector(Selectors.regions.searchResultsContainer);\n\n graderContainer.addEventListener('click', (e) => {\n if (e.target.closest(Selectors.buttons.toggleFullscreen)) {\n e.stopImmediatePropagation();\n e.preventDefault();\n graderLayout.toggleFullscreen();\n\n return;\n }\n\n if (e.target.closest(Selectors.buttons.closeGrader)) {\n e.stopImmediatePropagation();\n e.preventDefault();\n\n graderLayout.close();\n\n return;\n }\n\n if (e.target.closest(Selectors.buttons.saveGrade)) {\n saveGradeFunction(userPicker.currentUser);\n }\n\n if (e.target.closest(Selectors.buttons.toggleSearch)) {\n if (toggleSearchButton.getAttribute('aria-expanded') === 'true') {\n // Search is open so let's close it.\n hideUserSearchInput(toggleSearchButton, searchInputContainer, searchInput);\n hideSearchResultContainer(bodyContainer, userPickerContainer, searchResultsContainer);\n searchResultsContainer.innerHTML = '';\n } else {\n // Search is closed so let's open it.\n showUserSearchInput(toggleSearchButton, searchInputContainer, searchInput);\n showSearchResultContainer(bodyContainer, userPickerContainer, searchResultsContainer);\n renderSearchResults(searchResultsContainer, userList);\n }\n\n return;\n }\n\n const selectUserButton = e.target.closest(Selectors.buttons.selectUser);\n if (selectUserButton) {\n const userId = selectUserButton.getAttribute('data-userid');\n const user = userList.find(user => user.id == userId);\n userPicker.setUserId(userId);\n userPicker.showUser(user);\n hideUserSearchInput(toggleSearchButton, searchInputContainer, searchInput);\n hideSearchResultContainer(bodyContainer, userPickerContainer, searchResultsContainer);\n searchResultsContainer.innerHTML = '';\n }\n });\n\n // Debounce the search input so that it only executes 300 milliseconds after the user has finished typing.\n searchInput.addEventListener('input', debounce(() => {\n const users = searchForUsers(userList, searchInput.value);\n renderSearchResults(searchResultsContainer, users);\n }, 300));\n\n // Remove the right margin of the content container when the grading panel is hidden so that it expands to full-width.\n subscribe(DrawerEvents.DRAWER_HIDDEN, (drawerRoot) => {\n const gradingPanel = drawerRoot[0];\n if (gradingPanel.querySelector(Selectors.regions.gradingPanel)) {\n setContentContainerMargin(graderContainer, 0);\n }\n });\n\n // Bring back the right margin of the content container when the grading panel is shown to give space for the grading panel.\n subscribe(DrawerEvents.DRAWER_SHOWN, (drawerRoot) => {\n const gradingPanel = drawerRoot[0];\n if (gradingPanel.querySelector(Selectors.regions.gradingPanel)) {\n setContentContainerMargin(graderContainer, gradingPanel.offsetWidth);\n }\n });\n};\n\n/**\n * Adjusts the right margin of the content container.\n *\n * @param {HTMLElement} graderContainer The container for the grader app.\n * @param {Number} rightMargin The right margin value.\n */\nconst setContentContainerMargin = (graderContainer, rightMargin) => {\n const contentContainer = graderContainer.querySelector(Selectors.regions.moduleContainer);\n if (contentContainer) {\n contentContainer.style.marginRight = `${rightMargin}px`;\n }\n};\n\n/**\n * Get the function used to save a user grade.\n *\n * @param {HTMLElement} root The container for the grader\n * @param {Function} setGradeForUser The function that will be called.\n * @return {Function}\n */\nconst getSaveUserGradeFunction = (root, setGradeForUser) => {\n return async(user) => {\n try {\n root.querySelector(Selectors.regions.gradingPanelErrors).innerHTML = '';\n const result = await setGradeForUser(\n user.id,\n root.querySelector(Selectors.values.sendStudentNotifications).value,\n root.querySelector(Selectors.regions.gradingPanel)\n );\n if (result.success) {\n addToast(await getString('grades:gradesavedfor', 'mod_forum', user));\n }\n if (result.failed) {\n displayGradingError(root, user, result.error);\n }\n\n return result;\n } catch (err) {\n displayGradingError(root, user, err);\n\n return failedUpdate(err);\n }\n };\n};\n\n/**\n * Display a grading error, typically from a failed save.\n *\n * @param {HTMLElement} root The container for the grader\n * @param {Object} user The user who was errored\n * @param {Object} err The details of the error\n */\nconst displayGradingError = async(root, user, err) => {\n const [\n {html, js},\n errorString\n ] = await Promise.all([\n Templates.renderForPromise(templateNames.grader.gradingPanel.error, {error: err}),\n await getString('grades:gradesavefailed', 'mod_forum', {error: err.message, ...user}),\n ]);\n\n Templates.replaceNodeContents(root.querySelector(Selectors.regions.gradingPanelErrors), html, js);\n addToast(errorString, {type: 'warning'});\n};\n\n/**\n * Launch the grader interface with the specified parameters.\n *\n * @param {Function} getListOfUsers A function to get the list of users\n * @param {Function} getContentForUser A function to get the content for a specific user\n * @param {Function} getGradeForUser A function get the grade details for a specific user\n * @param {Function} setGradeForUser A function to set the grade for a specific user\n * @param {Object} preferences Preferences for the launch function\n * @param {Number} preferences.initialUserId\n * @param {string} preferences.moduleName\n * @param {string} preferences.courseName\n * @param {string} preferences.courseUrl\n * @param {boolean} preferences.sendStudentNotifications\n * @param {null|HTMLElement} preferences.focusOnClose\n */\nexport const launch = async(getListOfUsers, getContentForUser, getGradeForUser, setGradeForUser, {\n initialUserId = null,\n moduleName,\n courseName,\n courseUrl,\n sendStudentNotifications,\n focusOnClose = null,\n} = {}) => {\n\n // We need all of these functions to be executed in series, if one step runs before another the interface\n // will not work.\n\n // We need this promise to resolve separately so that we can avoid loading the whole interface if there are no users.\n const userList = await getListOfUsers();\n if (!userList.length) {\n addNotification({\n message: await getString('nouserstograde', 'core_grades'),\n type: \"error\",\n });\n return;\n }\n\n // Now that we have confirmed there are at least some users let's boot up the grader interface.\n const [\n graderLayout,\n {html, js},\n ] = await Promise.all([\n createFullScreenWindow({\n fullscreen: false,\n showLoader: false,\n focusOnClose,\n }),\n Templates.renderForPromise(templateNames.grader.app, {\n moduleName,\n courseName,\n courseUrl,\n drawer: {show: true},\n defaultsendnotifications: sendStudentNotifications,\n }),\n ]);\n\n const graderContainer = graderLayout.getContainer();\n\n const saveGradeFunction = getSaveUserGradeFunction(graderContainer, setGradeForUser);\n\n Templates.replaceNodeContents(graderContainer, html, js);\n const updateUserContent = getUpdateUserContentFunction(graderContainer, getContentForUser, getGradeForUser, saveGradeFunction);\n\n const userIds = userList.map(user => user.id);\n const statusContainer = graderContainer.querySelector(Selectors.regions.statusContainer);\n // Fetch the userpicker for display.\n const userPicker = await getUserPicker(\n userList,\n async(user) => {\n const userGrade = await updateUserContent(user);\n const renderContext = {\n status: userGrade.hasgrade,\n index: userIds.indexOf(user.id) + 1,\n total: userList.length\n };\n Templates.render(templateNames.grader.status, renderContext).then(html => {\n statusContainer.innerHTML = html;\n return html;\n }).catch();\n },\n saveGradeFunction,\n {\n initialUserId,\n },\n );\n\n // Register all event listeners.\n registerEventListeners(graderLayout, userPicker, saveGradeFunction, userList);\n\n // Display the newly created user picker.\n displayUserPicker(graderContainer, userPicker.rootNode);\n};\n\n/**\n * Show the grade for a specific user.\n *\n * @param {Function} getGradeForUser A function get the grade details for a specific user\n * @param {Number} userid The ID of a specific user\n * @param {String} moduleName the name of the module\n * @param {object} param\n * @param {null|HTMLElement} param.focusOnClose\n */\nexport const view = async(getGradeForUser, userid, moduleName, {\n focusOnClose = null,\n} = {}) => {\n\n const userGrade = await getGradeForUser(userid);\n\n const [\n modal,\n gradeTemplateData\n ] = await Promise.all([\n Modal.create({\n title: moduleName,\n large: true,\n removeOnClose: true,\n returnElement: focusOnClose,\n show: true,\n body: Templates.render('mod_forum/local/grades/view_grade', userGrade),\n }),\n renderGradeTemplate(userGrade)\n ]);\n\n const bodyPromise = await modal.getBodyPromise();\n const gradeReplace = bodyPromise[0].querySelector('[data-region=\"grade-template\"]');\n Templates.replaceNodeContents(gradeReplace, gradeTemplateData.html, gradeTemplateData.js);\n};\n\nconst renderGradeTemplate = (userGrade) => Templates.renderForPromise(userGrade.templatename, userGrade.grade);\n\nexport {getGradingPanelFunctions};\n"],"names":["templateNames","app","gradingPanel","error","searchResults","status","displayUserPicker","root","html","pickerRegion","querySelector","Selectors","regions","replaceNodeContents","fetchContentFromRender","js","getUpdateUserContentFunction","getContentForUser","getGradeForUser","saveGradeForUser","firstLoad","async","spinner","userGrade","Promise","all","user","id","then","moduleReplace","gradingPanelHtml","gradingPanelJS","Templates","render","templatename","grade","panelContainer","gradingPanelContainer","panel","form","addEventListener","event","preventDefault","scrollTop","resolve","hideSearchResultContainer","bodyContainer","userPickerContainer","searchResultsContainer","classList","remove","add","hideUserSearchInput","toggleSearchButton","searchContainer","searchInput","setAttribute","focus","parentElement","gradingInfoContainer","removeAttribute","collapseGradingDrawer","buttons","value","renderSearchResults","users","renderForPromise","registerEventListeners","graderLayout","userPicker","saveGradeFunction","userList","graderContainer","getContainer","toggleSearch","searchInputContainer","userSearchContainer","userSearchInput","e","target","closest","toggleFullscreen","stopImmediatePropagation","closeGrader","close","saveGrade","currentUser","getAttribute","innerHTML","showUserSearchInput","showSearchResultContainer","selectUserButton","selectUser","userId","find","setUserId","showUser","searchTerm","toLowerCase","filter","fullname","includes","searchForUsers","DrawerEvents","DRAWER_HIDDEN","drawerRoot","setContentContainerMargin","DRAWER_SHOWN","offsetWidth","rightMargin","contentContainer","moduleContainer","style","marginRight","getSaveUserGradeFunction","setGradeForUser","gradingPanelErrors","result","values","sendStudentNotifications","success","failed","displayGradingError","err","errorString","message","type","getListOfUsers","initialUserId","moduleName","courseName","courseUrl","focusOnClose","length","fullscreen","showLoader","drawer","show","defaultsendnotifications","updateUserContent","userIds","map","statusContainer","renderContext","hasgrade","index","indexOf","total","catch","rootNode","userid","modal","gradeTemplateData","Modal","create","title","large","removeOnClose","returnElement","body","renderGradeTemplate","bodyPromise","getBodyPromise","gradeReplace"],"mappings":"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"} \ No newline at end of file diff --git a/mod/forum/amd/src/local/grades/grader.js b/mod/forum/amd/src/local/grades/grader.js index 23dea33f722..a7948f2048a 100644 --- a/mod/forum/amd/src/local/grades/grader.js +++ b/mod/forum/amd/src/local/grades/grader.js @@ -29,7 +29,7 @@ import {add as addToast} from 'core/toast'; import {addNotification} from 'core/notification'; import {getString} from 'core/str'; import {failedUpdate} from 'core_grades/grades/grader/gradingpanel/normalise'; -import {addIconToContainerWithPromise, getIcon as getSpinner} from 'core/loadingicon'; +import {addIconToContainerWithPromise} from 'core/loadingicon'; import {debounce} from 'core/utils'; import {fillInitialValues} from 'core_grades/grades/grader/gradingpanel/comparison'; import Modal from 'core/modal_cancel'; @@ -487,27 +487,26 @@ export const view = async(getGradeForUser, userid, moduleName, { focusOnClose = null, } = {}) => { + const userGrade = await getGradeForUser(userid); + const [ - userGrade, modal, + gradeTemplateData ] = await Promise.all([ - getGradeForUser(userid), Modal.create({ title: moduleName, large: true, removeOnClose: true, returnElement: focusOnClose, show: true, - body: getSpinner(), + body: Templates.render('mod_forum/local/grades/view_grade', userGrade), }), + renderGradeTemplate(userGrade) ]); - modal.setBodyContent(Templates.renderForPromise('mod_forum/local/grades/view_grade', userGrade)); - - // Note: We do not use await here because it messes with the Modal transitions. - const [{html, js}] = await Promise.all([modal.getBodyPromise(), renderGradeTemplate(userGrade)]); - const gradeReplace = modal.getRoot()[0].querySelector('[data-region="grade-template"]'); - Templates.replaceNodeContents(gradeReplace, html, js); + const bodyPromise = await modal.getBodyPromise(); + const gradeReplace = bodyPromise[0].querySelector('[data-region="grade-template"]'); + Templates.replaceNodeContents(gradeReplace, gradeTemplateData.html, gradeTemplateData.js); }; const renderGradeTemplate = (userGrade) => Templates.renderForPromise(userGrade.templatename, userGrade.grade);