diff --git a/grade/amd/build/grades/grader/gradingpanel/comparison.min.js b/grade/amd/build/grades/grader/gradingpanel/comparison.min.js new file mode 100644 index 00000000000..b5c2fefd781 --- /dev/null +++ b/grade/amd/build/grades/grader/gradingpanel/comparison.min.js @@ -0,0 +1,2 @@ +define ("core_grades/grades/grader/gradingpanel/comparison",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.compareData=a.fillInitialValues=void 0;var b=function(a){Array.prototype.forEach.call(a.elements,function(a){if("submit"===a.type||"button"===a.type){}else if("radio"===a.type||"checkbox"===a.type){a.dataset.initialValue=JSON.stringify(a.checked)}else if("undefined"!=typeof a.value){a.dataset.initialValue=JSON.stringify(a.value)}else if("select-one"===a.type){Array.prototype.forEach.call(a.options,function(a){a.dataset.initialValue=JSON.stringify(a.selected)})}})};a.fillInitialValues=b;a.compareData=function compareData(a){var c=Array.prototype.some.call(a.elements,function(a){if("submit"===a.type||"button"===a.type){return!1}else if("radio"===a.type||"checkbox"===a.type){if("undefined"!=typeof a.dataset.initialValue){return a.dataset.initialValue!==JSON.stringify(a.checked)}}else if("undefined"!=typeof a.value){if("undefined"!=typeof a.dataset.initialValue){return a.dataset.initialValue!==JSON.stringify(a.value)}}else if("select-one"===a.type){return Array.prototype.some.call(a.options,function(a){if("undefined"!=typeof a.dataset.initialValue){return a.dataset.initialValue!==JSON.stringify(a.selected)}return!1})}return!0});b(a);return c}}); +//# sourceMappingURL=comparison.min.js.map diff --git a/grade/amd/build/grades/grader/gradingpanel/comparison.min.js.map b/grade/amd/build/grades/grader/gradingpanel/comparison.min.js.map new file mode 100644 index 00000000000..0f76f2eff8b --- /dev/null +++ b/grade/amd/build/grades/grader/gradingpanel/comparison.min.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../../../src/grades/grader/gradingpanel/comparison.js"],"names":["fillInitialValues","form","Array","prototype","forEach","call","elements","input","type","dataset","initialValue","JSON","stringify","checked","value","options","option","selected","compareData","result","some"],"mappings":"2LAwBO,GAAMA,CAAAA,CAAiB,CAAG,SAACC,CAAD,CAAU,CACvCC,KAAK,CAACC,SAAN,CAAgBC,OAAhB,CAAwBC,IAAxB,CAA6BJ,CAAI,CAACK,QAAlC,CAA4C,SAACC,CAAD,CAAW,CACnD,GAAmB,QAAf,GAAAA,CAAK,CAACC,IAAN,EAA0C,QAAf,GAAAD,CAAK,CAACC,IAArC,CAAwD,CAEvD,CAFD,IAEO,IAAmB,OAAf,GAAAD,CAAK,CAACC,IAAN,EAAyC,UAAf,GAAAD,CAAK,CAACC,IAApC,CAAyD,CAC5DD,CAAK,CAACE,OAAN,CAAcC,YAAd,CAA6BC,IAAI,CAACC,SAAL,CAAeL,CAAK,CAACM,OAArB,CAChC,CAFM,IAEA,IAA2B,WAAvB,QAAON,CAAAA,CAAK,CAACO,KAAjB,CAAwC,CAC3CP,CAAK,CAACE,OAAN,CAAcC,YAAd,CAA6BC,IAAI,CAACC,SAAL,CAAeL,CAAK,CAACO,KAArB,CAChC,CAFM,IAEA,IAAmB,YAAf,GAAAP,CAAK,CAACC,IAAV,CAAiC,CACpCN,KAAK,CAACC,SAAN,CAAgBC,OAAhB,CAAwBC,IAAxB,CAA6BE,CAAK,CAACQ,OAAnC,CAA4C,SAACC,CAAD,CAAY,CACpDA,CAAM,CAACP,OAAP,CAAeC,YAAf,CAA8BC,IAAI,CAACC,SAAL,CAAeI,CAAM,CAACC,QAAtB,CACjC,CAFD,CAGH,CACL,CAZA,CAaH,CAdM,C,oCAyBoB,QAAdC,CAAAA,WAAc,CAACjB,CAAD,CAAU,CACjC,GAAMkB,CAAAA,CAAM,CAAGjB,KAAK,CAACC,SAAN,CAAgBiB,IAAhB,CAAqBf,IAArB,CAA0BJ,CAAI,CAACK,QAA/B,CAAyC,SAACC,CAAD,CAAW,CAC/D,GAAmB,QAAf,GAAAA,CAAK,CAACC,IAAN,EAA0C,QAAf,GAAAD,CAAK,CAACC,IAArC,CAAwD,CACpD,QACH,CAFD,IAEO,IAAmB,OAAf,GAAAD,CAAK,CAACC,IAAN,EAAyC,UAAf,GAAAD,CAAK,CAACC,IAApC,CAAyD,CAC5D,GAA0C,WAAtC,QAAOD,CAAAA,CAAK,CAACE,OAAN,CAAcC,YAAzB,CAAuD,CACnD,MAAOH,CAAAA,CAAK,CAACE,OAAN,CAAcC,YAAd,GAA+BC,IAAI,CAACC,SAAL,CAAeL,CAAK,CAACM,OAArB,CACzC,CACJ,CAJM,IAIA,IAA2B,WAAvB,QAAON,CAAAA,CAAK,CAACO,KAAjB,CAAwC,CAC3C,GAA0C,WAAtC,QAAOP,CAAAA,CAAK,CAACE,OAAN,CAAcC,YAAzB,CAAuD,CACnD,MAAOH,CAAAA,CAAK,CAACE,OAAN,CAAcC,YAAd,GAA+BC,IAAI,CAACC,SAAL,CAAeL,CAAK,CAACO,KAArB,CACzC,CACJ,CAJM,IAIA,IAAmB,YAAf,GAAAP,CAAK,CAACC,IAAV,CAAiC,CACpC,MAAON,CAAAA,KAAK,CAACC,SAAN,CAAgBiB,IAAhB,CAAqBf,IAArB,CAA0BE,CAAK,CAACQ,OAAhC,CAAyC,SAACC,CAAD,CAAY,CACxD,GAA2C,WAAvC,QAAOA,CAAAA,CAAM,CAACP,OAAP,CAAeC,YAA1B,CAAwD,CACpD,MAAOM,CAAAA,CAAM,CAACP,OAAP,CAAeC,YAAf,GAAgCC,IAAI,CAACC,SAAL,CAAeI,CAAM,CAACC,QAAtB,CAC1C,CAED,QACH,CANM,CAOV,CAGD,QACH,CAvBc,CAAf,CA0BAjB,CAAiB,CAACC,CAAD,CAAjB,CAEA,MAAOkB,CAAAA,CACV,C","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 * Compare a given form's values and its previously set data attributes.\n *\n * @module core_grades/grades/grader/gradingpanel/comparison\n * @package core_grades\n * @copyright 2019 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport const fillInitialValues = (form) => {\n Array.prototype.forEach.call(form.elements, (input) => {\n if (input.type === 'submit' || input.type === 'button') {\n return;\n } else if (input.type === 'radio' || input.type === 'checkbox') {\n input.dataset.initialValue = JSON.stringify(input.checked);\n } else if (typeof input.value !== 'undefined') {\n input.dataset.initialValue = JSON.stringify(input.value);\n } else if (input.type === 'select-one') {\n Array.prototype.forEach.call(input.options, (option) => {\n option.dataset.initialValue = JSON.stringify(option.selected);\n });\n }\n });\n};\n\n\n/**\n * Compare the form data with the initial form data from when the form was set up.\n *\n * If values have changed, return a truthy value.\n *\n * @param {HTMLElement} form\n * @return {Boolean}\n */\nexport const compareData = (form) => {\n const result = Array.prototype.some.call(form.elements, (input) => {\n if (input.type === 'submit' || input.type === 'button') {\n return false;\n } else if (input.type === 'radio' || input.type === 'checkbox') {\n if (typeof input.dataset.initialValue !== 'undefined') {\n return input.dataset.initialValue !== JSON.stringify(input.checked);\n }\n } else if (typeof input.value !== 'undefined') {\n if (typeof input.dataset.initialValue !== 'undefined') {\n return input.dataset.initialValue !== JSON.stringify(input.value);\n }\n } else if (input.type === 'select-one') {\n return Array.prototype.some.call(input.options, (option) => {\n if (typeof option.dataset.initialValue !== 'undefined') {\n return option.dataset.initialValue !== JSON.stringify(option.selected);\n }\n\n return false;\n });\n }\n\n // No value found to check. Assume that there were changes.\n return true;\n });\n\n // Fill the initial values again as the form may not be reloaded.\n fillInitialValues(form);\n\n return result;\n};\n"],"file":"comparison.min.js"} \ No newline at end of file diff --git a/grade/amd/src/grades/grader/gradingpanel/comparison.js b/grade/amd/src/grades/grader/gradingpanel/comparison.js new file mode 100644 index 00000000000..2507d8bd346 --- /dev/null +++ b/grade/amd/src/grades/grader/gradingpanel/comparison.js @@ -0,0 +1,80 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Compare a given form's values and its previously set data attributes. + * + * @module core_grades/grades/grader/gradingpanel/comparison + * @package core_grades + * @copyright 2019 Mathew May + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +export const fillInitialValues = (form) => { + Array.prototype.forEach.call(form.elements, (input) => { + if (input.type === 'submit' || input.type === 'button') { + return; + } else if (input.type === 'radio' || input.type === 'checkbox') { + input.dataset.initialValue = JSON.stringify(input.checked); + } else if (typeof input.value !== 'undefined') { + input.dataset.initialValue = JSON.stringify(input.value); + } else if (input.type === 'select-one') { + Array.prototype.forEach.call(input.options, (option) => { + option.dataset.initialValue = JSON.stringify(option.selected); + }); + } + }); +}; + + +/** + * Compare the form data with the initial form data from when the form was set up. + * + * If values have changed, return a truthy value. + * + * @param {HTMLElement} form + * @return {Boolean} + */ +export const compareData = (form) => { + const result = Array.prototype.some.call(form.elements, (input) => { + if (input.type === 'submit' || input.type === 'button') { + return false; + } else if (input.type === 'radio' || input.type === 'checkbox') { + if (typeof input.dataset.initialValue !== 'undefined') { + return input.dataset.initialValue !== JSON.stringify(input.checked); + } + } else if (typeof input.value !== 'undefined') { + if (typeof input.dataset.initialValue !== 'undefined') { + return input.dataset.initialValue !== JSON.stringify(input.value); + } + } else if (input.type === 'select-one') { + return Array.prototype.some.call(input.options, (option) => { + if (typeof option.dataset.initialValue !== 'undefined') { + return option.dataset.initialValue !== JSON.stringify(option.selected); + } + + return false; + }); + } + + // No value found to check. Assume that there were changes. + return true; + }); + + // Fill the initial values again as the form may not be reloaded. + fillInitialValues(form); + + return result; +}; diff --git a/mod/forum/amd/build/local/grades/grader.min.js b/mod/forum/amd/build/local/grades/grader.min.js index b6cee866677..a3857854bf7 100644 --- a/mod/forum/amd/build/local/grades/grader.min.js +++ b/mod/forum/amd/build/local/grades/grader.min.js @@ -1,2 +1,2 @@ -define ("mod_forum/local/grades/grader",["exports","core/templates","./local/grader/selectors","./local/grader/user_picker","mod_forum/local/layout/fullscreen","./local/grader/gradingpanel","core/toast","core/str","core_grades/grades/grader/gradingpanel/normalise","core/loadingicon","core/utils"],function(a,b,c,d,e,f,g,h,i,j,k){"use strict";Object.defineProperty(a,"__esModule",{value:!0});Object.defineProperty(a,"getGradingPanelFunctions",{enumerable:!0,get:function get(){return f.default}});a.launch=void 0;b=l(b);c=l(c);d=l(d);f=l(f);function l(a){return a&&a.__esModule?a:{default:a}}function m(a){for(var b=1;b.\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 * @package mod_forum\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 {get_string as getString} from 'core/str';\nimport {failedUpdate} from 'core_grades/grades/grader/gradingpanel/normalise';\nimport {addIconToContainerWithPromise} from 'core/loadingicon';\nimport {debounce} from 'core/utils';\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 * @return {[*, *]}\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 * @return {Function}\n */\nconst getUpdateUserContentFunction = (root, getContentForUser, getGradeForUser) => {\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 panelContainer.scrollTop = 0;\n firstLoad = false;\n\n if (spinner) {\n spinner.resolve();\n }\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 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 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\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(user.id, root.querySelector(Selectors.regions.gradingPanel));\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);\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 */\nexport const launch = async(getListOfUsers, getContentForUser, getGradeForUser, setGradeForUser, {\n initialUserId = null, moduleName, courseName, courseUrl\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 const [\n graderLayout,\n {html, js},\n userList,\n ] = await Promise.all([\n createFullScreenWindow({fullscreen: false, showLoader: false}),\n Templates.renderForPromise(templateNames.grader.app, {\n moduleName,\n courseName,\n courseUrl,\n drawer: {show: true}\n }),\n getListOfUsers(),\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);\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 user => {\n const renderContext = {\n status: null,\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 updateUserContent(user);\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\nexport {getGradingPanelFunctions};\n"],"file":"grader.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/grades/grader.js"],"names":["templateNames","grader","app","gradingPanel","error","searchResults","status","displayUserPicker","root","html","pickerRegion","querySelector","Selectors","regions","Templates","replaceNodeContents","fetchContentFromRender","js","getUpdateUserContentFunction","getContentForUser","getGradeForUser","firstLoad","user","spinner","Promise","all","id","then","userGrade","moduleReplace","render","templatename","grade","gradingPanelHtml","gradingPanelJS","panelContainer","gradingPanelContainer","panel","scrollTop","resolve","showSearchResultContainer","bodyContainer","userPickerContainer","searchResultsContainer","classList","add","remove","hideSearchResultContainer","showUserSearchInput","toggleSearchButton","searchContainer","searchInput","setAttribute","focus","hideUserSearchInput","value","searchForUsers","userList","searchTerm","toLowerCase","filter","fullname","includes","renderSearchResults","users","renderForPromise","registerEventListeners","graderLayout","userPicker","saveGradeFunction","graderContainer","getContainer","buttons","toggleSearch","searchInputContainer","userSearchContainer","userSearchInput","addEventListener","e","target","closest","toggleFullscreen","stopImmediatePropagation","preventDefault","closeGrader","close","saveGrade","currentUser","getAttribute","innerHTML","selectUserButton","selectUser","userId","find","setUserId","showUser","getSaveUserGradeFunction","setGradeForUser","gradingPanelErrors","result","success","addToast","failed","displayGradingError","err","message","errorString","launch","getListOfUsers","initialUserId","moduleName","courseName","courseUrl","fullscreen","showLoader","drawer","show","updateUserContent","userIds","map","statusContainer","renderContext","index","indexOf","total","length","catch","rootNode"],"mappings":"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","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 * @package mod_forum\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 {get_string as 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';\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 * @return {[*, *]}\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 * @return {Function}\n */\nconst getUpdateUserContentFunction = (root, getContentForUser, getGradeForUser) => {\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 fillInitialValues(panel.querySelector('form'));\n\n panelContainer.scrollTop = 0;\n firstLoad = false;\n\n if (spinner) {\n spinner.resolve();\n }\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 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 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\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(user.id, root.querySelector(Selectors.regions.gradingPanel));\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);\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 */\nexport const launch = async(getListOfUsers, getContentForUser, getGradeForUser, setGradeForUser, {\n initialUserId = null, moduleName, courseName, courseUrl\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 const [\n graderLayout,\n {html, js},\n userList,\n ] = await Promise.all([\n createFullScreenWindow({fullscreen: false, showLoader: false}),\n Templates.renderForPromise(templateNames.grader.app, {\n moduleName,\n courseName,\n courseUrl,\n drawer: {show: true}\n }),\n getListOfUsers(),\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);\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 user => {\n const renderContext = {\n status: null,\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 updateUserContent(user);\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\nexport {getGradingPanelFunctions};\n"],"file":"grader.min.js"} \ 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 d681414ab5e..c9166db8aac 100644 --- a/mod/forum/amd/src/local/grades/grader.js +++ b/mod/forum/amd/src/local/grades/grader.js @@ -31,6 +31,7 @@ import {get_string as getString} from 'core/str'; import {failedUpdate} from 'core_grades/grades/grader/gradingpanel/normalise'; import {addIconToContainerWithPromise} from 'core/loadingicon'; import {debounce} from 'core/utils'; +import {fillInitialValues} from 'core_grades/grades/grader/gradingpanel/comparison'; const templateNames = { grader: { @@ -95,6 +96,8 @@ const getUpdateUserContentFunction = (root, getContentForUser, getGradeForUser) const panelContainer = root.querySelector(Selectors.regions.gradingPanelContainer); const panel = panelContainer.querySelector(Selectors.regions.gradingPanel); Templates.replaceNodeContents(panel, gradingPanelHtml, gradingPanelJS); + fillInitialValues(panel.querySelector('form')); + panelContainer.scrollTop = 0; firstLoad = false;