diff --git a/mod/quiz/amd/build/edit_multiple_grades.min.js b/mod/quiz/amd/build/edit_multiple_grades.min.js index a17922c5347..43a7ddbb34c 100644 --- a/mod/quiz/amd/build/edit_multiple_grades.min.js +++ b/mod/quiz/amd/build/edit_multiple_grades.min.js @@ -5,6 +5,6 @@ define("mod_quiz/edit_multiple_grades",["exports","core/ajax","core/config","cor * @module mod_quiz/edit_multiple_grades * @copyright 2023 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_config=_interopRequireDefault(_config),_notification=_interopRequireDefault(_notification),_pending=_interopRequireDefault(_pending);const SELECTORS_addGradeItemButton="#mod_quiz-add_grade_item",SELECTORS_editingPageContents="#edit_grading_page-contents",SELECTORS_gradeItemList="table#mod_quiz-grade-item-list",SELECTORS_gradeItemSelect="select[data-slot-id]",SELECTORS_gradeItemSelectId=id=>"select#grade-item-choice-"+id,SELECTORS_gradeItemTr="table#mod_quiz-grade-item-list tr[data-quiz-grade-item-id]",SELECTORS_inplaceEditable="span.inplaceeditable",SELECTORS_inplaceEditableOn="span.inplaceeditable.inplaceeditingon",SELECTORS_resetAllButton="#mod_quiz-grades_reset_all",SELECTORS_slotList="table#mod_quiz-slot-list",SELECTORS_updateGradeItemLink=id=>'tr[data-quiz-grade-item-id="'+id+'"] .quickeditlink',callServiceAndReturnRenderingData=methodCall=>callServicesAndReturnRenderingData([methodCall]),callServicesAndReturnRenderingData=methodCalls=>(methodCalls.push({methodname:"mod_quiz_get_edit_grading_page_data",args:{quizid:methodCalls[0].args.quizid}}),Promise.all((0,_ajax.call)(methodCalls)).then((results=>JSON.parse(results.at(-1))))),handleGradeItemDelete=e=>{e.preventDefault();const pending=new _pending.default("delete-quiz-grade-item"),tableCell=e.target.closest("td");(0,_loadingicon.addIconToContainer)(tableCell,pending);const tableRow=tableCell.closest("tr"),quizId=tableRow.closest("table").dataset.quizId,gradeItemId=tableRow.dataset.quizGradeItemId;let nextItemToFocus;nextItemToFocus=tableRow.nextElementSibling?SELECTORS_updateGradeItemLink(tableRow.nextElementSibling.dataset.quizGradeItemId):SELECTORS_addGradeItemButton,((quizId,gradeItemId)=>callServiceAndReturnRenderingData({methodname:"mod_quiz_delete_grade_items",args:{quizid:quizId,quizgradeitems:[{id:gradeItemId}]}}))(quizId,gradeItemId).then(reRenderPage).then((()=>{pending.resolve(),document.querySelector(nextItemToFocus).focus()})).catch(_notification.default.exception)},stopEditingGadeItem=editableSpan=>{editableSpan.innerHTML=editableSpan.dataset.oldContent,delete editableSpan.dataset.oldContent,editableSpan.classList.remove("inplaceeditingon"),editableSpan.querySelector("[data-action-edit]").focus()},handleGradeItemKeyDown=e=>{if(13!==e.keyCode)return;const editableSpan=e.target.closest(SELECTORS_inplaceEditableOn);if(!editableSpan||!editableSpan.closest(SELECTORS_gradeItemList))return;e.preventDefault();const pending=new _pending.default("edit-quiz-grade-item-save"),newName=editableSpan.querySelector("input").value,tableCell=e.target.closest("th");(0,_loadingicon.addIconToContainer)(tableCell);const tableRow=tableCell.closest("tr"),quizId=tableRow.closest("table").dataset.quizId,gradeItemId=tableRow.dataset.quizGradeItemId;((quizId,gradeItemId,newName)=>callServiceAndReturnRenderingData({methodname:"mod_quiz_update_grade_items",args:{quizid:quizId,quizgradeitems:[{id:gradeItemId,name:newName}]}}))(quizId,gradeItemId,newName).then(reRenderPage).then((()=>{pending.resolve(),document.querySelector(SELECTORS_updateGradeItemLink(gradeItemId)).focus({focusVisible:!0})})).catch(_notification.default.exception)},reRenderPage=editGradingPageData=>(0,_templates.render)("mod_quiz/edit_grading_page",editGradingPageData).then(((html,js)=>(0,_templates.replaceNode)(document.querySelector(SELECTORS_editingPageContents),html,js||""))),handleGradeItemKeyUp=e=>{if(27!==e.keyCode)return;const editableSpan=e.target.closest(SELECTORS_inplaceEditableOn);editableSpan&&editableSpan.closest(SELECTORS_gradeItemList)&&(e.preventDefault(),stopEditingGadeItem(editableSpan))},handleGradeItemFocusOut=e=>{if(_config.default.behatsiterunning)return;const editableSpan=e.target.closest(SELECTORS_inplaceEditableOn);editableSpan&&editableSpan.closest(SELECTORS_gradeItemList)&&(e.preventDefault(),stopEditingGadeItem(editableSpan))},handleSlotGradeItemChanged=e=>{const select=e.target.closest(SELECTORS_gradeItemSelect);if(!select||!select.closest(SELECTORS_slotList))return;e.preventDefault();const pending=new _pending.default("edit-slot-grade-item-updated"),slotId=select.dataset.slotId,newGradeItemId=select.value?select.value:null,tableCell=e.target.closest("td");(0,_loadingicon.addIconToContainer)(tableCell,pending);((quizId,slotId,gradeItemId)=>callServiceAndReturnRenderingData({methodname:"mod_quiz_update_slots",args:{quizid:quizId,slots:[{id:slotId,quizgradeitemid:gradeItemId}]}}))(tableCell.closest("table").dataset.quizId,slotId,newGradeItemId).then(reRenderPage).then((()=>{pending.resolve(),document.querySelector(SELECTORS_gradeItemSelectId(slotId)).focus()})).catch(_notification.default.exception)},handleGradeItemClick=e=>{const link=e.target.closest("a");link&&link.closest(SELECTORS_gradeItemList)&&(link.dataset.actionDelete&&handleGradeItemDelete(e),link.dataset.actionEdit&&(e=>{e.preventDefault();const pending=new _pending.default("edit-quiz-grade-item-start"),editableSpan=e.target.closest(SELECTORS_inplaceEditable);document.querySelectorAll(SELECTORS_inplaceEditableOn).forEach(stopEditingGadeItem),editableSpan.dataset.oldContent=editableSpan.innerHTML,(0,_str.get_string)("edittitleinstructions").then((instructions=>{const uniqueId="gi-edit-input-"+editableSpan.closest("tr").dataset.quizGradeItemId;editableSpan.innerHTML=''+instructions+'';const inputElement=editableSpan.querySelector("input");return inputElement.focus(),inputElement.select(),editableSpan.classList.add("inplaceeditingon"),pending.resolve(),null})).catch(_notification.default.exception)})(e))},handleButtonClick=e=>{e.target.closest(SELECTORS_addGradeItemButton)&&handleAddGradeItemClick(e),e.target.closest(SELECTORS_resetAllButton)&&handleResetAllClick(e)},handleAddGradeItemClick=e=>{e.preventDefault();const pending=new _pending.default("create-quiz-grade-item");(0,_loadingicon.addIconToContainer)(e.target.parentNode,pending);const quizId=e.target.dataset.quizId;(0,_str.get_string)("gradeitemdefaultname","quiz").then((name=>((quizId,name)=>callServiceAndReturnRenderingData({methodname:"mod_quiz_create_grade_items",args:{quizid:quizId,quizgradeitems:[{name:name}]}}))(quizId,name))).then(reRenderPage).then((()=>{pending.resolve(),document.querySelector(SELECTORS_addGradeItemButton).focus()})).catch(_notification.default.exception)},handleResetAllClick=e=>{e.preventDefault();const button=e.target;_notification.default.deleteCancelPromise((0,_str.get_string)("gradeitemsremoveallconfirm","quiz"),(0,_str.get_string)("gradeitemsremoveallmessage","quiz"),(0,_str.get_string)("reset"),button).then((()=>reallyResetAll(button))).catch((()=>button.focus()))},reallyResetAll=button=>{const pending=new _pending.default("reset-quiz-grading");(0,_loadingicon.addIconToContainer)(button.parentNode,pending);const quizId=button.dataset.quizId;let methodCalls=[];const slotResets=[...document.querySelectorAll(SELECTORS_gradeItemSelect)].map((select=>({id:select.dataset.slotId,quizgradeitemid:0})));slotResets.length&&methodCalls.push({methodname:"mod_quiz_update_slots",args:{quizid:quizId,slots:slotResets}}),methodCalls.push({methodname:"mod_quiz_delete_grade_items",args:{quizid:quizId,quizgradeitems:[...document.querySelectorAll(SELECTORS_gradeItemTr)].map((tr=>({id:tr.dataset.quizGradeItemId})))}}),callServicesAndReturnRenderingData(methodCalls).then(reRenderPage).then((()=>{pending.resolve(),document.querySelector(SELECTORS_addGradeItemButton).focus()})).catch(_notification.default.exception)};_exports.init=()=>{document.body.addEventListener("click",handleGradeItemClick),document.body.addEventListener("keydown",handleGradeItemKeyDown),document.body.addEventListener("keyup",handleGradeItemKeyUp),document.body.addEventListener("focusout",handleGradeItemFocusOut),document.body.addEventListener("click",handleButtonClick),document.body.addEventListener("change",handleSlotGradeItemChanged)}})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_config=_interopRequireDefault(_config),_notification=_interopRequireDefault(_notification),_pending=_interopRequireDefault(_pending);const SELECTORS_addGradeItemButton="#mod_quiz-add_grade_item",SELECTORS_autoSetupButton="#mod_quiz-grades_auto_setup",SELECTORS_editingPageContents="#edit_grading_page-contents",SELECTORS_gradeItemList="table#mod_quiz-grade-item-list",SELECTORS_gradeItemSelect="select[data-slot-id]",SELECTORS_gradeItemSelectId=id=>"select#grade-item-choice-"+id,SELECTORS_gradeItemTr="table#mod_quiz-grade-item-list tr[data-quiz-grade-item-id]",SELECTORS_inplaceEditable="span.inplaceeditable",SELECTORS_inplaceEditableOn="span.inplaceeditable.inplaceeditingon",SELECTORS_resetAllButton="#mod_quiz-grades_reset_all",SELECTORS_slotList="table#mod_quiz-slot-list",SELECTORS_updateGradeItemLink=id=>'tr[data-quiz-grade-item-id="'+id+'"] .quickeditlink',callServiceAndReturnRenderingData=methodCall=>callServicesAndReturnRenderingData([methodCall]),callServicesAndReturnRenderingData=methodCalls=>(methodCalls.push({methodname:"mod_quiz_get_edit_grading_page_data",args:{quizid:methodCalls[0].args.quizid}}),Promise.all((0,_ajax.call)(methodCalls)).then((results=>JSON.parse(results.at(-1))))),handleGradeItemDelete=e=>{e.preventDefault();const pending=new _pending.default("delete-quiz-grade-item"),tableCell=e.target.closest("td");(0,_loadingicon.addIconToContainer)(tableCell,pending);const tableRow=tableCell.closest("tr"),quizId=tableRow.closest("table").dataset.quizId,gradeItemId=tableRow.dataset.quizGradeItemId;let nextItemToFocus;nextItemToFocus=tableRow.nextElementSibling?SELECTORS_updateGradeItemLink(tableRow.nextElementSibling.dataset.quizGradeItemId):SELECTORS_addGradeItemButton,((quizId,gradeItemId)=>callServiceAndReturnRenderingData({methodname:"mod_quiz_delete_grade_items",args:{quizid:quizId,quizgradeitems:[{id:gradeItemId}]}}))(quizId,gradeItemId).then(reRenderPage).then((()=>{pending.resolve(),document.querySelector(nextItemToFocus).focus()})).catch(_notification.default.exception)},stopEditingGadeItem=editableSpan=>{editableSpan.innerHTML=editableSpan.dataset.oldContent,delete editableSpan.dataset.oldContent,editableSpan.classList.remove("inplaceeditingon"),editableSpan.querySelector("[data-action-edit]").focus()},handleGradeItemKeyDown=e=>{if(13!==e.keyCode)return;const editableSpan=e.target.closest(SELECTORS_inplaceEditableOn);if(!editableSpan||!editableSpan.closest(SELECTORS_gradeItemList))return;e.preventDefault();const pending=new _pending.default("edit-quiz-grade-item-save"),newName=editableSpan.querySelector("input").value,tableCell=e.target.closest("th");(0,_loadingicon.addIconToContainer)(tableCell);const tableRow=tableCell.closest("tr"),quizId=tableRow.closest("table").dataset.quizId,gradeItemId=tableRow.dataset.quizGradeItemId;((quizId,gradeItemId,newName)=>callServiceAndReturnRenderingData({methodname:"mod_quiz_update_grade_items",args:{quizid:quizId,quizgradeitems:[{id:gradeItemId,name:newName}]}}))(quizId,gradeItemId,newName).then(reRenderPage).then((()=>{pending.resolve(),document.querySelector(SELECTORS_updateGradeItemLink(gradeItemId)).focus({focusVisible:!0})})).catch(_notification.default.exception)},reRenderPage=editGradingPageData=>(0,_templates.render)("mod_quiz/edit_grading_page",editGradingPageData).then(((html,js)=>(0,_templates.replaceNode)(document.querySelector(SELECTORS_editingPageContents),html,js||""))),handleGradeItemKeyUp=e=>{if(27!==e.keyCode)return;const editableSpan=e.target.closest(SELECTORS_inplaceEditableOn);editableSpan&&editableSpan.closest(SELECTORS_gradeItemList)&&(e.preventDefault(),stopEditingGadeItem(editableSpan))},handleGradeItemFocusOut=e=>{if(_config.default.behatsiterunning)return;const editableSpan=e.target.closest(SELECTORS_inplaceEditableOn);editableSpan&&editableSpan.closest(SELECTORS_gradeItemList)&&(e.preventDefault(),stopEditingGadeItem(editableSpan))},handleSlotGradeItemChanged=e=>{const select=e.target.closest(SELECTORS_gradeItemSelect);if(!select||!select.closest(SELECTORS_slotList))return;e.preventDefault();const pending=new _pending.default("edit-slot-grade-item-updated"),slotId=select.dataset.slotId,newGradeItemId=select.value?select.value:null,tableCell=e.target.closest("td");(0,_loadingicon.addIconToContainer)(tableCell,pending);((quizId,slotId,gradeItemId)=>callServiceAndReturnRenderingData({methodname:"mod_quiz_update_slots",args:{quizid:quizId,slots:[{id:slotId,quizgradeitemid:gradeItemId}]}}))(tableCell.closest("table").dataset.quizId,slotId,newGradeItemId).then(reRenderPage).then((()=>{pending.resolve(),document.querySelector(SELECTORS_gradeItemSelectId(slotId)).focus()})).catch(_notification.default.exception)},handleGradeItemClick=e=>{const link=e.target.closest("a");link&&link.closest(SELECTORS_gradeItemList)&&(link.dataset.actionDelete&&handleGradeItemDelete(e),link.dataset.actionEdit&&(e=>{e.preventDefault();const pending=new _pending.default("edit-quiz-grade-item-start"),editableSpan=e.target.closest(SELECTORS_inplaceEditable);document.querySelectorAll(SELECTORS_inplaceEditableOn).forEach(stopEditingGadeItem),editableSpan.dataset.oldContent=editableSpan.innerHTML,(0,_str.get_string)("edittitleinstructions").then((instructions=>{const uniqueId="gi-edit-input-"+editableSpan.closest("tr").dataset.quizGradeItemId;editableSpan.innerHTML=''+instructions+'';const inputElement=editableSpan.querySelector("input");return inputElement.focus(),inputElement.select(),editableSpan.classList.add("inplaceeditingon"),pending.resolve(),null})).catch(_notification.default.exception)})(e))},handleButtonClick=e=>{e.target.closest(SELECTORS_addGradeItemButton)&&handleAddGradeItemClick(e),e.target.closest(SELECTORS_autoSetupButton)&&handleAutoSetup(e),e.target.closest(SELECTORS_resetAllButton)&&handleResetAllClick(e)},handleAddGradeItemClick=e=>{e.preventDefault();const pending=new _pending.default("create-quiz-grade-item");(0,_loadingicon.addIconToContainer)(e.target.parentNode,pending);const quizId=e.target.dataset.quizId;(0,_str.get_string)("gradeitemdefaultname","quiz").then((name=>((quizId,name)=>callServiceAndReturnRenderingData({methodname:"mod_quiz_create_grade_items",args:{quizid:quizId,quizgradeitems:[{name:name}]}}))(quizId,name))).then(reRenderPage).then((()=>{pending.resolve(),document.querySelector(SELECTORS_addGradeItemButton).focus()})).catch(_notification.default.exception)},handleAutoSetup=e=>{e.preventDefault();const pending=new _pending.default("setup-quiz-grade-items");(quizId=>callServiceAndReturnRenderingData({methodname:"mod_quiz_create_grade_item_per_section",args:{quizid:quizId}}))(e.target.dataset.quizId).then(reRenderPage).then((()=>{pending.resolve(),document.querySelector(SELECTORS_resetAllButton).focus()})).catch(_notification.default.exception)},handleResetAllClick=e=>{e.preventDefault();const button=e.target;_notification.default.deleteCancelPromise((0,_str.get_string)("gradeitemsremoveallconfirm","quiz"),(0,_str.get_string)("gradeitemsremoveallmessage","quiz"),(0,_str.get_string)("reset"),button).then((()=>reallyResetAll(button))).catch((()=>button.focus()))},reallyResetAll=button=>{const pending=new _pending.default("reset-quiz-grading");(0,_loadingicon.addIconToContainer)(button.parentNode,pending);const quizId=button.dataset.quizId;let methodCalls=[];const slotResets=[...document.querySelectorAll(SELECTORS_gradeItemSelect)].map((select=>({id:select.dataset.slotId,quizgradeitemid:0})));slotResets.length&&methodCalls.push({methodname:"mod_quiz_update_slots",args:{quizid:quizId,slots:slotResets}}),methodCalls.push({methodname:"mod_quiz_delete_grade_items",args:{quizid:quizId,quizgradeitems:[...document.querySelectorAll(SELECTORS_gradeItemTr)].map((tr=>({id:tr.dataset.quizGradeItemId})))}}),callServicesAndReturnRenderingData(methodCalls).then(reRenderPage).then((()=>{pending.resolve(),document.querySelector(SELECTORS_addGradeItemButton).focus()})).catch(_notification.default.exception)};_exports.init=()=>{document.body.addEventListener("click",handleGradeItemClick),document.body.addEventListener("keydown",handleGradeItemKeyDown),document.body.addEventListener("keyup",handleGradeItemKeyUp),document.body.addEventListener("focusout",handleGradeItemFocusOut),document.body.addEventListener("click",handleButtonClick),document.body.addEventListener("change",handleSlotGradeItemChanged)}})); //# sourceMappingURL=edit_multiple_grades.min.js.map \ No newline at end of file diff --git a/mod/quiz/amd/build/edit_multiple_grades.min.js.map b/mod/quiz/amd/build/edit_multiple_grades.min.js.map index beaafd4c2f7..8014faa91c8 100644 --- a/mod/quiz/amd/build/edit_multiple_grades.min.js.map +++ b/mod/quiz/amd/build/edit_multiple_grades.min.js.map @@ -1 +1 @@ -{"version":3,"file":"edit_multiple_grades.min.js","sources":["../src/edit_multiple_grades.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 * JavaScript for managing multiple grade items for a quiz.\n *\n * @module mod_quiz/edit_multiple_grades\n * @copyright 2023 The Open University\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {call as fetchMany} from 'core/ajax';\nimport MoodleConfig from 'core/config';\nimport {addIconToContainer} from 'core/loadingicon';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport {get_string as getString} from 'core/str';\nimport {render as renderTemplate} from 'core/templates';\nimport {replaceNode} from 'core/templates';\n\n/**\n * @type {Object} selectors used in this code.\n */\nconst SELECTORS = {\n 'addGradeItemButton': '#mod_quiz-add_grade_item',\n 'editingPageContents': '#edit_grading_page-contents',\n 'gradeItemList': 'table#mod_quiz-grade-item-list',\n 'gradeItemSelect': 'select[data-slot-id]',\n 'gradeItemSelectId': (id) => 'select#grade-item-choice-' + id,\n 'gradeItemTr': 'table#mod_quiz-grade-item-list tr[data-quiz-grade-item-id]',\n 'inplaceEditable': 'span.inplaceeditable',\n 'inplaceEditableOn': 'span.inplaceeditable.inplaceeditingon',\n 'resetAllButton': '#mod_quiz-grades_reset_all',\n 'slotList': 'table#mod_quiz-slot-list',\n 'updateGradeItemLink': (id) => 'tr[data-quiz-grade-item-id=\"' + id + '\"] .quickeditlink',\n};\n\n/**\n * Call the Ajax service to create a quiz grade item.\n *\n * @param {Number} quizId id of the quiz to update.\n * @param {String} name name of the grade item to create.\n * @returns {Promise} a promise that resolves to the template context required to re-render the page.\n */\nconst createGradeItem = (\n quizId,\n name\n) => callServiceAndReturnRenderingData({\n methodname: 'mod_quiz_create_grade_items',\n args: {\n quizid: quizId,\n quizgradeitems: [{name: name}],\n }\n});\n\n/**\n * Call the Ajax service to update a quiz grade item.\n *\n * @param {Number} quizId id of the quiz to update.\n * @param {Number} gradeItemId id of the grade item to update.\n * @param {String} newName the new name to set.\n * @return {Promise} Promise that resolves to the context required to re-render the page.\n */\nconst updateGradeItem = (\n quizId,\n gradeItemId,\n newName\n) => callServiceAndReturnRenderingData({\n methodname: 'mod_quiz_update_grade_items',\n args: {\n quizid: quizId,\n quizgradeitems: [{id: gradeItemId, name: newName}],\n }\n});\n\n/**\n * Call the Ajax service to delete a quiz grade item.\n *\n * @param {Number} quizId id of the quiz to update.\n * @param {Number} gradeItemId id of the grade item to delete.\n * @return {Promise} Promise that resolves to the context required to re-render the page.\n */\nconst deleteGradeItem = (\n quizId,\n gradeItemId\n) => callServiceAndReturnRenderingData({\n methodname: 'mod_quiz_delete_grade_items',\n args: {\n quizid: quizId,\n quizgradeitems: [{id: gradeItemId}],\n }\n});\n\n/**\n * Call the Ajax service to update the quiz grade item used by a slot.\n *\n * @param {Number} quizId id of the quiz to update.\n * @param {Number} slotId id of the slot to update.\n * @param {Number|null} gradeItemId new grade item ot set, or null to un-set.\n * @return {Promise} Promise that resolves to the context required to re-render the page.\n */\nconst updateSlotGradeItem = (\n quizId,\n slotId,\n gradeItemId\n) => callServiceAndReturnRenderingData({\n methodname: 'mod_quiz_update_slots',\n args: {\n quizid: quizId,\n slots: [{id: slotId, quizgradeitemid: gradeItemId}],\n }\n});\n\n/**\n * Make a web service call, and also call mod_quiz_get_edit_grading_page_data to get the date to re-render the page.\n *\n * @param {Object} methodCall a web service call to pass to fetchMany. Must include methodCall.args.quizid.\n * @returns {Promise} a promise that resolves to the template context required to re-render the page.\n */\nconst callServiceAndReturnRenderingData = (methodCall) => callServicesAndReturnRenderingData([methodCall]);\n\n/**\n * Make a web service call, and also call mod_quiz_get_edit_grading_page_data to get the date to re-render the page.\n *\n * @param {Object[]} methodCalls web service calls to pass to fetchMany. Must include methodCalls[0].args.quizid.\n * @returns {Promise} a promise that resolves to the template context required to re-render the page.\n */\nconst callServicesAndReturnRenderingData = (methodCalls) => {\n methodCalls.push({\n methodname: 'mod_quiz_get_edit_grading_page_data',\n args: {\n quizid: methodCalls[0].args.quizid,\n }\n });\n return Promise.all(fetchMany(methodCalls))\n .then(results => JSON.parse(results.at(-1)));\n};\n\n/**\n * Handle click events on the delete icon.\n *\n * @param {Event} e click event.\n */\nconst handleGradeItemDelete = (e) => {\n e.preventDefault();\n const pending = new Pending('delete-quiz-grade-item');\n\n const tableCell = e.target.closest('td');\n addIconToContainer(tableCell, pending);\n\n const tableRow = tableCell.closest('tr');\n const quizId = tableRow.closest('table').dataset.quizId;\n const gradeItemId = tableRow.dataset.quizGradeItemId;\n\n let nextItemToFocus;\n if (tableRow.nextElementSibling) {\n nextItemToFocus = SELECTORS.updateGradeItemLink(tableRow.nextElementSibling.dataset.quizGradeItemId);\n } else {\n nextItemToFocus = SELECTORS.addGradeItemButton;\n }\n\n deleteGradeItem(quizId, gradeItemId)\n .then(reRenderPage)\n .then(() => {\n pending.resolve();\n document.querySelector(nextItemToFocus).focus();\n })\n .catch(Notification.exception);\n};\n\n/**\n *\n * @param {HTMLElement} editableSpan the editable to turn off.\n */\nconst stopEditingGadeItem = (editableSpan) => {\n editableSpan.innerHTML = editableSpan.dataset.oldContent;\n delete editableSpan.dataset.oldContent;\n\n editableSpan.classList.remove('inplaceeditingon');\n editableSpan.querySelector('[data-action-edit]').focus();\n};\n\n/**\n * Handle click events on the start rename icon.\n *\n * @param {Event} e click event.\n */\nconst handleGradeItemEditStart = (e) => {\n e.preventDefault();\n const pending = new Pending('edit-quiz-grade-item-start');\n const editableSpan = e.target.closest(SELECTORS.inplaceEditable);\n\n document.querySelectorAll(SELECTORS.inplaceEditableOn).forEach(stopEditingGadeItem);\n\n editableSpan.dataset.oldContent = editableSpan.innerHTML;\n getString('edittitleinstructions')\n .then((instructions) => {\n const uniqueId = 'gi-edit-input-' + editableSpan.closest('tr').dataset.quizGradeItemId;\n editableSpan.innerHTML = '' + instructions + '' +\n '' +\n '';\n\n const inputElement = editableSpan.querySelector('input');\n inputElement.focus();\n inputElement.select();\n editableSpan.classList.add('inplaceeditingon');\n pending.resolve();\n return null;\n })\n .catch(Notification.exception);\n};\n\n/**\n * Handle key down in the editable.\n *\n * @param {Event} e key event.\n */\nconst handleGradeItemKeyDown = (e) => {\n if (e.keyCode !== 13) {\n return;\n }\n\n const editableSpan = e.target.closest(SELECTORS.inplaceEditableOn);\n\n // Check this click is on a relevant element.\n if (!editableSpan || !editableSpan.closest(SELECTORS.gradeItemList)) {\n return;\n }\n\n e.preventDefault();\n const pending = new Pending('edit-quiz-grade-item-save');\n\n const newName = editableSpan.querySelector('input').value;\n const tableCell = e.target.closest('th');\n addIconToContainer(tableCell);\n\n const tableRow = tableCell.closest('tr');\n const quizId = tableRow.closest('table').dataset.quizId;\n const gradeItemId = tableRow.dataset.quizGradeItemId;\n\n updateGradeItem(quizId, gradeItemId, newName)\n .then(reRenderPage)\n .then(() => {\n pending.resolve();\n document.querySelector(SELECTORS.updateGradeItemLink(gradeItemId)).focus({'focusVisible': true});\n })\n .catch(Notification.exception);\n};\n\n/**\n * Replace the contents of the page with the page re-rendered from the provided data, once that promise resolves.\n *\n * @param {Object} editGradingPageData the template context data required to re-render the page.\n * @returns {Promise} a promise that will resolve when the page is updated.\n */\nconst reRenderPage = (editGradingPageData) =>\n renderTemplate('mod_quiz/edit_grading_page', editGradingPageData)\n .then((html, js) => replaceNode(document.querySelector(SELECTORS.editingPageContents), html, js || ''));\n\n/**\n * Handle key up in the editable.\n *\n * @param {Event} e key event.\n */\nconst handleGradeItemKeyUp = (e) => {\n if (e.keyCode !== 27) {\n return;\n }\n\n const editableSpan = e.target.closest(SELECTORS.inplaceEditableOn);\n\n // Check this click is on a relevant element.\n if (!editableSpan || !editableSpan.closest(SELECTORS.gradeItemList)) {\n return;\n }\n\n e.preventDefault();\n stopEditingGadeItem(editableSpan);\n};\n\n/**\n * Handle focus out of the editable.\n *\n * @param {Event} e event.\n */\nconst handleGradeItemFocusOut = (e) => {\n if (MoodleConfig.behatsiterunning) {\n // Behat triggers focusout too often so ignore.\n return;\n }\n\n const editableSpan = e.target.closest(SELECTORS.inplaceEditableOn);\n\n // Check this click is on a relevant element.\n if (!editableSpan || !editableSpan.closest(SELECTORS.gradeItemList)) {\n return;\n }\n\n e.preventDefault();\n stopEditingGadeItem(editableSpan);\n};\n\n/**\n * Handle when the selected grade item for a slot is changed.\n *\n * @param {Event} e event.\n */\nconst handleSlotGradeItemChanged = (e) => {\n const select = e.target.closest(SELECTORS.gradeItemSelect);\n\n // Check this click is on a relevant element.\n if (!select || !select.closest(SELECTORS.slotList)) {\n return;\n }\n\n e.preventDefault();\n const pending = new Pending('edit-slot-grade-item-updated');\n\n const slotId = select.dataset.slotId;\n const newGradeItemId = select.value ? select.value : null;\n const tableCell = e.target.closest('td');\n addIconToContainer(tableCell, pending);\n\n const quizId = tableCell.closest('table').dataset.quizId;\n\n updateSlotGradeItem(quizId, slotId, newGradeItemId)\n .then(reRenderPage)\n .then(() => {\n pending.resolve();\n document.querySelector(SELECTORS.gradeItemSelectId(slotId)).focus();\n })\n .catch(Notification.exception);\n};\n\n/**\n * Handle clicks in the table the shows the grade items.\n *\n * @param {Event} e click event.\n */\nconst handleGradeItemClick = (e) => {\n const link = e.target.closest('a');\n\n // Check this click is on a relevant element.\n if (!link || !link.closest(SELECTORS.gradeItemList)) {\n return;\n }\n\n if (link.dataset.actionDelete) {\n handleGradeItemDelete(e);\n }\n\n if (link.dataset.actionEdit) {\n handleGradeItemEditStart(e);\n }\n};\n\n/**\n * Handle clicks on the buttons.\n *\n * @param {Event} e click event.\n */\n\nconst handleButtonClick = (e) => {\n if (e.target.closest(SELECTORS.addGradeItemButton)) {\n handleAddGradeItemClick(e);\n }\n if (e.target.closest(SELECTORS.resetAllButton)) {\n handleResetAllClick(e);\n }\n};\n\n/**\n * Handle clicks on the 'Add grade item' button.\n *\n * @param {Event} e click event.\n */\nconst handleAddGradeItemClick = (e) => {\n e.preventDefault();\n const pending = new Pending('create-quiz-grade-item');\n addIconToContainer(e.target.parentNode, pending);\n\n const quizId = e.target.dataset.quizId;\n\n getString('gradeitemdefaultname', 'quiz')\n .then((name) => createGradeItem(quizId, name))\n .then(reRenderPage)\n .then(() => {\n pending.resolve();\n document.querySelector(SELECTORS.addGradeItemButton).focus();\n })\n .catch(Notification.exception);\n};\n\n/**\n * Handle clicks on the reset button - show a confirmation.\n *\n * @param {Event} e click event.\n */\nconst handleResetAllClick = (e) => {\n e.preventDefault();\n const button = e.target;\n\n Notification.deleteCancelPromise(\n getString('gradeitemsremoveallconfirm', 'quiz'),\n getString('gradeitemsremoveallmessage', 'quiz'),\n getString('reset'),\n button\n ).then(() => reallyResetAll(button))\n .catch(() => button.focus());\n};\n\n/**\n * Really reset all if the confirmation is OKed.\n *\n * @param {HTMLElement} button the reset button.\n */\nconst reallyResetAll = (button) => {\n const pending = new Pending('reset-quiz-grading');\n addIconToContainer(button.parentNode, pending);\n\n const quizId = button.dataset.quizId;\n\n let methodCalls = [];\n\n // Call to clear any assignments of grade items to slots (if required).\n const slotResets = [...document.querySelectorAll(SELECTORS.gradeItemSelect)].map(\n (select) => ({\n id: select.dataset.slotId,\n quizgradeitemid: 0,\n }));\n if (slotResets.length) {\n methodCalls.push({\n methodname: 'mod_quiz_update_slots',\n args: {\n quizid: quizId,\n slots: slotResets\n }\n });\n }\n\n // Request to delete all the grade items.\n methodCalls.push({\n methodname: 'mod_quiz_delete_grade_items',\n args: {\n quizid: quizId,\n quizgradeitems: [...document.querySelectorAll(SELECTORS.gradeItemTr)].map((tr) => {\n return {id: tr.dataset.quizGradeItemId};\n })\n }\n });\n\n callServicesAndReturnRenderingData(methodCalls)\n .then(reRenderPage)\n .then(() => {\n pending.resolve();\n document.querySelector(SELECTORS.addGradeItemButton).focus();\n })\n .catch(Notification.exception);\n};\n\n/**\n * Replace the container with a new version.\n */\nconst registerEventListeners = () => {\n document.body.addEventListener('click', handleGradeItemClick);\n document.body.addEventListener('keydown', handleGradeItemKeyDown);\n document.body.addEventListener('keyup', handleGradeItemKeyUp);\n document.body.addEventListener('focusout', handleGradeItemFocusOut);\n\n document.body.addEventListener('click', handleButtonClick);\n\n document.body.addEventListener('change', handleSlotGradeItemChanged);\n};\n\n/**\n * Entry point.\n */\nexport const init = () => {\n registerEventListeners();\n};\n"],"names":["SELECTORS","id","callServiceAndReturnRenderingData","methodCall","callServicesAndReturnRenderingData","methodCalls","push","methodname","args","quizid","Promise","all","then","results","JSON","parse","at","handleGradeItemDelete","e","preventDefault","pending","Pending","tableCell","target","closest","tableRow","quizId","dataset","gradeItemId","quizGradeItemId","nextItemToFocus","nextElementSibling","quizgradeitems","deleteGradeItem","reRenderPage","resolve","document","querySelector","focus","catch","Notification","exception","stopEditingGadeItem","editableSpan","innerHTML","oldContent","classList","remove","handleGradeItemKeyDown","keyCode","newName","value","name","updateGradeItem","editGradingPageData","html","js","handleGradeItemKeyUp","handleGradeItemFocusOut","MoodleConfig","behatsiterunning","handleSlotGradeItemChanged","select","slotId","newGradeItemId","slots","quizgradeitemid","updateSlotGradeItem","handleGradeItemClick","link","actionDelete","actionEdit","querySelectorAll","forEach","instructions","uniqueId","editLabel","rawName","inputElement","add","handleGradeItemEditStart","handleButtonClick","handleAddGradeItemClick","handleResetAllClick","parentNode","createGradeItem","button","deleteCancelPromise","reallyResetAll","slotResets","map","length","tr","body","addEventListener"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"edit_multiple_grades.min.js","sources":["../src/edit_multiple_grades.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 * JavaScript for managing multiple grade items for a quiz.\n *\n * @module mod_quiz/edit_multiple_grades\n * @copyright 2023 The Open University\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {call as fetchMany} from 'core/ajax';\nimport MoodleConfig from 'core/config';\nimport {addIconToContainer} from 'core/loadingicon';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport {get_string as getString} from 'core/str';\nimport {render as renderTemplate} from 'core/templates';\nimport {replaceNode} from 'core/templates';\n\n/**\n * @type {Object} selectors used in this code.\n */\nconst SELECTORS = {\n 'addGradeItemButton': '#mod_quiz-add_grade_item',\n 'autoSetupButton': '#mod_quiz-grades_auto_setup',\n 'editingPageContents': '#edit_grading_page-contents',\n 'gradeItemList': 'table#mod_quiz-grade-item-list',\n 'gradeItemSelect': 'select[data-slot-id]',\n 'gradeItemSelectId': (id) => 'select#grade-item-choice-' + id,\n 'gradeItemTr': 'table#mod_quiz-grade-item-list tr[data-quiz-grade-item-id]',\n 'inplaceEditable': 'span.inplaceeditable',\n 'inplaceEditableOn': 'span.inplaceeditable.inplaceeditingon',\n 'resetAllButton': '#mod_quiz-grades_reset_all',\n 'slotList': 'table#mod_quiz-slot-list',\n 'updateGradeItemLink': (id) => 'tr[data-quiz-grade-item-id=\"' + id + '\"] .quickeditlink',\n};\n\n/**\n * Call the Ajax service to create a quiz grade item.\n *\n * @param {Number} quizId id of the quiz to update.\n * @param {String} name name of the grade item to create.\n * @returns {Promise} a promise that resolves to the template context required to re-render the page.\n */\nconst createGradeItem = (\n quizId,\n name\n) => callServiceAndReturnRenderingData({\n methodname: 'mod_quiz_create_grade_items',\n args: {\n quizid: quizId,\n quizgradeitems: [{name: name}],\n }\n});\n\n/**\n * Call the Ajax service to update a quiz grade item.\n *\n * @param {Number} quizId id of the quiz to update.\n * @param {Number} gradeItemId id of the grade item to update.\n * @param {String} newName the new name to set.\n * @return {Promise} Promise that resolves to the context required to re-render the page.\n */\nconst updateGradeItem = (\n quizId,\n gradeItemId,\n newName\n) => callServiceAndReturnRenderingData({\n methodname: 'mod_quiz_update_grade_items',\n args: {\n quizid: quizId,\n quizgradeitems: [{id: gradeItemId, name: newName}],\n }\n});\n\n/**\n * Call the Ajax service to delete a quiz grade item.\n *\n * @param {Number} quizId id of the quiz to update.\n * @param {Number} gradeItemId id of the grade item to delete.\n * @return {Promise} Promise that resolves to the context required to re-render the page.\n */\nconst deleteGradeItem = (\n quizId,\n gradeItemId\n) => callServiceAndReturnRenderingData({\n methodname: 'mod_quiz_delete_grade_items',\n args: {\n quizid: quizId,\n quizgradeitems: [{id: gradeItemId}],\n }\n});\n\n/**\n * Call the Ajax service to update the quiz grade item used by a slot.\n *\n * @param {Number} quizId id of the quiz to update.\n * @param {Number} slotId id of the slot to update.\n * @param {Number|null} gradeItemId new grade item ot set, or null to un-set.\n * @return {Promise} Promise that resolves to the context required to re-render the page.\n */\nconst updateSlotGradeItem = (\n quizId,\n slotId,\n gradeItemId\n) => callServiceAndReturnRenderingData({\n methodname: 'mod_quiz_update_slots',\n args: {\n quizid: quizId,\n slots: [{id: slotId, quizgradeitemid: gradeItemId}],\n }\n});\n\n/**\n * Call the Ajax service to setup one grade item for each quiz section.\n *\n * @param {Number} quizId id of the quiz to update.\n * @return {Promise} Promise that resolves to the context required to re-render the page.\n */\nconst autoSetupGradeItems = (\n quizId\n) => callServiceAndReturnRenderingData({\n methodname: 'mod_quiz_create_grade_item_per_section',\n args: {\n quizid: quizId\n }\n});\n\n/**\n * Make a web service call, and also call mod_quiz_get_edit_grading_page_data to get the date to re-render the page.\n *\n * @param {Object} methodCall a web service call to pass to fetchMany. Must include methodCall.args.quizid.\n * @returns {Promise} a promise that resolves to the template context required to re-render the page.\n */\nconst callServiceAndReturnRenderingData = (methodCall) => callServicesAndReturnRenderingData([methodCall]);\n\n/**\n * Make a web service call, and also call mod_quiz_get_edit_grading_page_data to get the date to re-render the page.\n *\n * @param {Object[]} methodCalls web service calls to pass to fetchMany. Must include methodCalls[0].args.quizid.\n * @returns {Promise} a promise that resolves to the template context required to re-render the page.\n */\nconst callServicesAndReturnRenderingData = (methodCalls) => {\n methodCalls.push({\n methodname: 'mod_quiz_get_edit_grading_page_data',\n args: {\n quizid: methodCalls[0].args.quizid,\n }\n });\n return Promise.all(fetchMany(methodCalls))\n .then(results => JSON.parse(results.at(-1)));\n};\n\n/**\n * Handle click events on the delete icon.\n *\n * @param {Event} e click event.\n */\nconst handleGradeItemDelete = (e) => {\n e.preventDefault();\n const pending = new Pending('delete-quiz-grade-item');\n\n const tableCell = e.target.closest('td');\n addIconToContainer(tableCell, pending);\n\n const tableRow = tableCell.closest('tr');\n const quizId = tableRow.closest('table').dataset.quizId;\n const gradeItemId = tableRow.dataset.quizGradeItemId;\n\n let nextItemToFocus;\n if (tableRow.nextElementSibling) {\n nextItemToFocus = SELECTORS.updateGradeItemLink(tableRow.nextElementSibling.dataset.quizGradeItemId);\n } else {\n nextItemToFocus = SELECTORS.addGradeItemButton;\n }\n\n deleteGradeItem(quizId, gradeItemId)\n .then(reRenderPage)\n .then(() => {\n pending.resolve();\n document.querySelector(nextItemToFocus).focus();\n })\n .catch(Notification.exception);\n};\n\n/**\n *\n * @param {HTMLElement} editableSpan the editable to turn off.\n */\nconst stopEditingGadeItem = (editableSpan) => {\n editableSpan.innerHTML = editableSpan.dataset.oldContent;\n delete editableSpan.dataset.oldContent;\n\n editableSpan.classList.remove('inplaceeditingon');\n editableSpan.querySelector('[data-action-edit]').focus();\n};\n\n/**\n * Handle click events on the start rename icon.\n *\n * @param {Event} e click event.\n */\nconst handleGradeItemEditStart = (e) => {\n e.preventDefault();\n const pending = new Pending('edit-quiz-grade-item-start');\n const editableSpan = e.target.closest(SELECTORS.inplaceEditable);\n\n document.querySelectorAll(SELECTORS.inplaceEditableOn).forEach(stopEditingGadeItem);\n\n editableSpan.dataset.oldContent = editableSpan.innerHTML;\n getString('edittitleinstructions')\n .then((instructions) => {\n const uniqueId = 'gi-edit-input-' + editableSpan.closest('tr').dataset.quizGradeItemId;\n editableSpan.innerHTML = '' + instructions + '' +\n '' +\n '';\n\n const inputElement = editableSpan.querySelector('input');\n inputElement.focus();\n inputElement.select();\n editableSpan.classList.add('inplaceeditingon');\n pending.resolve();\n return null;\n })\n .catch(Notification.exception);\n};\n\n/**\n * Handle key down in the editable.\n *\n * @param {Event} e key event.\n */\nconst handleGradeItemKeyDown = (e) => {\n if (e.keyCode !== 13) {\n return;\n }\n\n const editableSpan = e.target.closest(SELECTORS.inplaceEditableOn);\n\n // Check this click is on a relevant element.\n if (!editableSpan || !editableSpan.closest(SELECTORS.gradeItemList)) {\n return;\n }\n\n e.preventDefault();\n const pending = new Pending('edit-quiz-grade-item-save');\n\n const newName = editableSpan.querySelector('input').value;\n const tableCell = e.target.closest('th');\n addIconToContainer(tableCell);\n\n const tableRow = tableCell.closest('tr');\n const quizId = tableRow.closest('table').dataset.quizId;\n const gradeItemId = tableRow.dataset.quizGradeItemId;\n\n updateGradeItem(quizId, gradeItemId, newName)\n .then(reRenderPage)\n .then(() => {\n pending.resolve();\n document.querySelector(SELECTORS.updateGradeItemLink(gradeItemId)).focus({'focusVisible': true});\n })\n .catch(Notification.exception);\n};\n\n/**\n * Replace the contents of the page with the page re-rendered from the provided data, once that promise resolves.\n *\n * @param {Object} editGradingPageData the template context data required to re-render the page.\n * @returns {Promise} a promise that will resolve when the page is updated.\n */\nconst reRenderPage = (editGradingPageData) =>\n renderTemplate('mod_quiz/edit_grading_page', editGradingPageData)\n .then((html, js) => replaceNode(document.querySelector(SELECTORS.editingPageContents), html, js || ''));\n\n/**\n * Handle key up in the editable.\n *\n * @param {Event} e key event.\n */\nconst handleGradeItemKeyUp = (e) => {\n if (e.keyCode !== 27) {\n return;\n }\n\n const editableSpan = e.target.closest(SELECTORS.inplaceEditableOn);\n\n // Check this click is on a relevant element.\n if (!editableSpan || !editableSpan.closest(SELECTORS.gradeItemList)) {\n return;\n }\n\n e.preventDefault();\n stopEditingGadeItem(editableSpan);\n};\n\n/**\n * Handle focus out of the editable.\n *\n * @param {Event} e event.\n */\nconst handleGradeItemFocusOut = (e) => {\n if (MoodleConfig.behatsiterunning) {\n // Behat triggers focusout too often so ignore.\n return;\n }\n\n const editableSpan = e.target.closest(SELECTORS.inplaceEditableOn);\n\n // Check this click is on a relevant element.\n if (!editableSpan || !editableSpan.closest(SELECTORS.gradeItemList)) {\n return;\n }\n\n e.preventDefault();\n stopEditingGadeItem(editableSpan);\n};\n\n/**\n * Handle when the selected grade item for a slot is changed.\n *\n * @param {Event} e event.\n */\nconst handleSlotGradeItemChanged = (e) => {\n const select = e.target.closest(SELECTORS.gradeItemSelect);\n\n // Check this click is on a relevant element.\n if (!select || !select.closest(SELECTORS.slotList)) {\n return;\n }\n\n e.preventDefault();\n const pending = new Pending('edit-slot-grade-item-updated');\n\n const slotId = select.dataset.slotId;\n const newGradeItemId = select.value ? select.value : null;\n const tableCell = e.target.closest('td');\n addIconToContainer(tableCell, pending);\n\n const quizId = tableCell.closest('table').dataset.quizId;\n\n updateSlotGradeItem(quizId, slotId, newGradeItemId)\n .then(reRenderPage)\n .then(() => {\n pending.resolve();\n document.querySelector(SELECTORS.gradeItemSelectId(slotId)).focus();\n })\n .catch(Notification.exception);\n};\n\n/**\n * Handle clicks in the table the shows the grade items.\n *\n * @param {Event} e click event.\n */\nconst handleGradeItemClick = (e) => {\n const link = e.target.closest('a');\n\n // Check this click is on a relevant element.\n if (!link || !link.closest(SELECTORS.gradeItemList)) {\n return;\n }\n\n if (link.dataset.actionDelete) {\n handleGradeItemDelete(e);\n }\n\n if (link.dataset.actionEdit) {\n handleGradeItemEditStart(e);\n }\n};\n\n/**\n * Handle clicks on the buttons.\n *\n * @param {Event} e click event.\n */\n\nconst handleButtonClick = (e) => {\n if (e.target.closest(SELECTORS.addGradeItemButton)) {\n handleAddGradeItemClick(e);\n }\n if (e.target.closest(SELECTORS.autoSetupButton)) {\n handleAutoSetup(e);\n }\n if (e.target.closest(SELECTORS.resetAllButton)) {\n handleResetAllClick(e);\n }\n};\n\n/**\n * Handle clicks on the 'Add grade item' button.\n *\n * @param {Event} e click event.\n */\nconst handleAddGradeItemClick = (e) => {\n e.preventDefault();\n const pending = new Pending('create-quiz-grade-item');\n addIconToContainer(e.target.parentNode, pending);\n\n const quizId = e.target.dataset.quizId;\n\n getString('gradeitemdefaultname', 'quiz')\n .then((name) => createGradeItem(quizId, name))\n .then(reRenderPage)\n .then(() => {\n pending.resolve();\n document.querySelector(SELECTORS.addGradeItemButton).focus();\n })\n .catch(Notification.exception);\n};\n\n/**\n * Handle clicks on the reset button - show a confirmation.\n *\n * @param {Event} e click event.\n */\nconst handleAutoSetup = (e) => {\n e.preventDefault();\n const pending = new Pending('setup-quiz-grade-items');\n\n const quizId = e.target.dataset.quizId;\n\n autoSetupGradeItems(quizId)\n .then(reRenderPage)\n .then(() => {\n pending.resolve();\n document.querySelector(SELECTORS.resetAllButton).focus();\n })\n .catch(Notification.exception);\n};\n\n/**\n * Handle clicks on the reset button - show a confirmation.\n *\n * @param {Event} e click event.\n */\nconst handleResetAllClick = (e) => {\n e.preventDefault();\n const button = e.target;\n\n Notification.deleteCancelPromise(\n getString('gradeitemsremoveallconfirm', 'quiz'),\n getString('gradeitemsremoveallmessage', 'quiz'),\n getString('reset'),\n button\n ).then(() => reallyResetAll(button))\n .catch(() => button.focus());\n};\n\n/**\n * Really reset all if the confirmation is OKed.\n *\n * @param {HTMLElement} button the reset button.\n */\nconst reallyResetAll = (button) => {\n const pending = new Pending('reset-quiz-grading');\n addIconToContainer(button.parentNode, pending);\n\n const quizId = button.dataset.quizId;\n\n let methodCalls = [];\n\n // Call to clear any assignments of grade items to slots (if required).\n const slotResets = [...document.querySelectorAll(SELECTORS.gradeItemSelect)].map(\n (select) => ({\n id: select.dataset.slotId,\n quizgradeitemid: 0,\n }));\n if (slotResets.length) {\n methodCalls.push({\n methodname: 'mod_quiz_update_slots',\n args: {\n quizid: quizId,\n slots: slotResets\n }\n });\n }\n\n // Request to delete all the grade items.\n methodCalls.push({\n methodname: 'mod_quiz_delete_grade_items',\n args: {\n quizid: quizId,\n quizgradeitems: [...document.querySelectorAll(SELECTORS.gradeItemTr)].map((tr) => {\n return {id: tr.dataset.quizGradeItemId};\n })\n }\n });\n\n callServicesAndReturnRenderingData(methodCalls)\n .then(reRenderPage)\n .then(() => {\n pending.resolve();\n document.querySelector(SELECTORS.addGradeItemButton).focus();\n })\n .catch(Notification.exception);\n};\n\n/**\n * Replace the container with a new version.\n */\nconst registerEventListeners = () => {\n document.body.addEventListener('click', handleGradeItemClick);\n document.body.addEventListener('keydown', handleGradeItemKeyDown);\n document.body.addEventListener('keyup', handleGradeItemKeyUp);\n document.body.addEventListener('focusout', handleGradeItemFocusOut);\n\n document.body.addEventListener('click', handleButtonClick);\n\n document.body.addEventListener('change', handleSlotGradeItemChanged);\n};\n\n/**\n * Entry point.\n */\nexport const init = () => {\n registerEventListeners();\n};\n"],"names":["SELECTORS","id","callServiceAndReturnRenderingData","methodCall","callServicesAndReturnRenderingData","methodCalls","push","methodname","args","quizid","Promise","all","then","results","JSON","parse","at","handleGradeItemDelete","e","preventDefault","pending","Pending","tableCell","target","closest","tableRow","quizId","dataset","gradeItemId","quizGradeItemId","nextItemToFocus","nextElementSibling","quizgradeitems","deleteGradeItem","reRenderPage","resolve","document","querySelector","focus","catch","Notification","exception","stopEditingGadeItem","editableSpan","innerHTML","oldContent","classList","remove","handleGradeItemKeyDown","keyCode","newName","value","name","updateGradeItem","editGradingPageData","html","js","handleGradeItemKeyUp","handleGradeItemFocusOut","MoodleConfig","behatsiterunning","handleSlotGradeItemChanged","select","slotId","newGradeItemId","slots","quizgradeitemid","updateSlotGradeItem","handleGradeItemClick","link","actionDelete","actionEdit","querySelectorAll","forEach","instructions","uniqueId","editLabel","rawName","inputElement","add","handleGradeItemEditStart","handleButtonClick","handleAddGradeItemClick","handleAutoSetup","handleResetAllClick","parentNode","createGradeItem","autoSetupGradeItems","button","deleteCancelPromise","reallyResetAll","slotResets","map","length","tr","body","addEventListener"],"mappings":"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"} \ No newline at end of file diff --git a/mod/quiz/amd/src/edit_multiple_grades.js b/mod/quiz/amd/src/edit_multiple_grades.js index 9a6179b61f5..bc54037b70f 100644 --- a/mod/quiz/amd/src/edit_multiple_grades.js +++ b/mod/quiz/amd/src/edit_multiple_grades.js @@ -35,6 +35,7 @@ import {replaceNode} from 'core/templates'; */ const SELECTORS = { 'addGradeItemButton': '#mod_quiz-add_grade_item', + 'autoSetupButton': '#mod_quiz-grades_auto_setup', 'editingPageContents': '#edit_grading_page-contents', 'gradeItemList': 'table#mod_quiz-grade-item-list', 'gradeItemSelect': 'select[data-slot-id]', @@ -123,6 +124,21 @@ const updateSlotGradeItem = ( } }); +/** + * Call the Ajax service to setup one grade item for each quiz section. + * + * @param {Number} quizId id of the quiz to update. + * @return {Promise} Promise that resolves to the context required to re-render the page. + */ +const autoSetupGradeItems = ( + quizId +) => callServiceAndReturnRenderingData({ + methodname: 'mod_quiz_create_grade_item_per_section', + args: { + quizid: quizId + } +}); + /** * Make a web service call, and also call mod_quiz_get_edit_grading_page_data to get the date to re-render the page. * @@ -377,6 +393,9 @@ const handleButtonClick = (e) => { if (e.target.closest(SELECTORS.addGradeItemButton)) { handleAddGradeItemClick(e); } + if (e.target.closest(SELECTORS.autoSetupButton)) { + handleAutoSetup(e); + } if (e.target.closest(SELECTORS.resetAllButton)) { handleResetAllClick(e); } @@ -404,6 +423,26 @@ const handleAddGradeItemClick = (e) => { .catch(Notification.exception); }; +/** + * Handle clicks on the reset button - show a confirmation. + * + * @param {Event} e click event. + */ +const handleAutoSetup = (e) => { + e.preventDefault(); + const pending = new Pending('setup-quiz-grade-items'); + + const quizId = e.target.dataset.quizId; + + autoSetupGradeItems(quizId) + .then(reRenderPage) + .then(() => { + pending.resolve(); + document.querySelector(SELECTORS.resetAllButton).focus(); + }) + .catch(Notification.exception); +}; + /** * Handle clicks on the reset button - show a confirmation. * diff --git a/mod/quiz/classes/external/create_grade_item_per_section.php b/mod/quiz/classes/external/create_grade_item_per_section.php new file mode 100644 index 00000000000..630e8eee101 --- /dev/null +++ b/mod/quiz/classes/external/create_grade_item_per_section.php @@ -0,0 +1,110 @@ +. + +namespace mod_quiz\external; + +use coding_exception; +use core_external\external_api; +use core_external\external_description; +use core_external\external_function_parameters; +use core_external\external_multiple_structure; +use core_external\external_single_structure; +use core_external\external_value; +use mod_quiz\quiz_attempt; +use mod_quiz\quiz_settings; +use moodle_exception; +use stdClass; + +/** + * For a quiz with no grade items yet, create a grade item for each section. + * + * And, assign the questions in each section to the corresponding grade item. + * + * The user must have the 'mod/quiz:manage' capability for the quiz. + * + * @package mod_quiz + * @copyright 2024 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class create_grade_item_per_section extends external_api { + + /** + * Declare the method parameters. + * + * @return external_function_parameters + */ + public static function execute_parameters(): external_function_parameters { + return new external_function_parameters([ + 'quizid' => new external_value(PARAM_INT, 'The quiz to update slots for.'), + ]); + } + + /** + * For a quiz with no grade items yet, create a grade item for each section. + * + * And, assign the questions in each section to the corresponding grade item. + * + * The user must have the 'mod/quiz:manage' capability for the quiz. + * + * @param int $quizid the id of the quiz to setup grade items for. + */ + public static function execute(int $quizid): void { + global $DB; + + [ + 'quizid' => $quizid, + ] = self::validate_parameters(self::execute_parameters(), [ + 'quizid' => $quizid, + ]); + + // Check the request is valid. + $quizobj = quiz_settings::create($quizid); + require_capability('mod/quiz:manage', $quizobj->get_context()); + self::validate_context($quizobj->get_context()); + + $structure = $quizobj->get_structure(); + if ($structure->get_grade_items()) { + throw new coding_exception('Cannot use create_grade_item_per_section for a quiz ' . + 'that already has grade items.'); + } + + $transaction = $DB->start_delegated_transaction(); + + $gradeitemsids = []; + foreach ($structure->get_sections() as $section) { + $gradeitem = new stdClass(); + $gradeitem->quizid = $quizid; + $gradeitem->name = $section->heading; + $structure->create_grade_item($gradeitem); + $gradeitemsids[$section->id] = $gradeitem->id; + } + + foreach ($structure->get_slots() as $slot) { + $structure->update_slot_grade_item($slot, $gradeitemsids[$slot->section->id]); + } + + $transaction->allow_commit(); + } + + /** + * Define the webservice response. + * + * @return external_description|null always null. + */ + public static function execute_returns(): ?external_description { + return null; + } +} diff --git a/mod/quiz/classes/output/edit_grading_page.php b/mod/quiz/classes/output/edit_grading_page.php index fb0df917bea..f4ee5342077 100644 --- a/mod/quiz/classes/output/edit_grading_page.php +++ b/mod/quiz/classes/output/edit_grading_page.php @@ -112,6 +112,7 @@ class edit_grading_page implements renderable, templatable { 'gradeitems' => $gradeitems, 'hasslots' => $this->structure->has_questions(), 'sections' => array_values($sections), + 'hasmultiplesections' => count($sections) > 1, 'nogradeitems' => ['message' => get_string('gradeitemsnoneyet', 'quiz')], 'noslots' => ['message' => get_string('gradeitemnoslots', 'quiz')], ]; diff --git a/mod/quiz/classes/structure.php b/mod/quiz/classes/structure.php index 18097baa440..993a135a5ea 100644 --- a/mod/quiz/classes/structure.php +++ b/mod/quiz/classes/structure.php @@ -55,8 +55,7 @@ class structure { protected $slotsinorder = []; /** - * @var stdClass[] currently a dummy. Holds data that will match the - * quiz_sections, once it exists. + * @var stdClass[] this quiz's data from the quiz_sections table. Each item has a ->lastslot field too. */ protected $sections = []; @@ -1505,7 +1504,7 @@ class structure { * * The new grade item is added at the end of the order. * - * @param stdClass $gradeitemdata must have property name. + * @param stdClass $gradeitemdata must have property name - updated with the inserted data (sortorder and id). */ public function create_grade_item(stdClass $gradeitemdata): void { global $DB; diff --git a/mod/quiz/db/services.php b/mod/quiz/db/services.php index 491bb08462a..568570b9e0c 100644 --- a/mod/quiz/db/services.php +++ b/mod/quiz/db/services.php @@ -295,4 +295,12 @@ $functions = [ 'capabilities' => 'mod/quiz:manage', 'ajax' => true, ], + + 'mod_quiz_create_grade_item_per_section' => [ + 'classname' => 'mod_quiz\external\create_grade_item_per_section', + 'description' => 'For a quiz with no grade items yet, create a grade item for each section, with the questions in that section assigned.', + 'type' => 'write', + 'capabilities' => 'mod/quiz:manage', + 'ajax' => true, + ], ]; diff --git a/mod/quiz/lang/en/quiz.php b/mod/quiz/lang/en/quiz.php index d89f2dafdf7..6abe234cc4c 100644 --- a/mod/quiz/lang/en/quiz.php +++ b/mod/quiz/lang/en/quiz.php @@ -448,6 +448,7 @@ $string['gradeitemnewname'] = 'New name for grade item {$a}'; $string['gradeitemnoneselected'] = '[none]'; $string['gradeitemnoslots'] = 'No questions have been added to the quiz yet. Please add the questions to the quiz before setting up grading.'; $string['gradeitems'] = 'Grade items'; +$string['gradeitemsautosetup'] = 'Setup a grade for each section'; $string['gradeitemsetup'] = 'Quiz grading setup (advanced view)'; $string['gradeitemsnoneyet'] = 'This quiz does not yet have any grade items defined, just a simple overall score will be used.'; $string['gradeitemsremoveall'] = 'Reset advanced grading setup'; diff --git a/mod/quiz/templates/edit_grading_page.mustache b/mod/quiz/templates/edit_grading_page.mustache index 999a44de39e..5268b4cfbde 100644 --- a/mod/quiz/templates/edit_grading_page.mustache +++ b/mod/quiz/templates/edit_grading_page.mustache @@ -135,6 +135,12 @@ {{/hasgradeitems}} + {{^hasgradeitems}} + {{#hasmultiplesections}} + + {{/hasmultiplesections}} + {{/hasgradeitems}}

{{#str}} gradeitemmarkscheme, quiz {{/str}}

diff --git a/mod/quiz/tests/behat/attempt_multiple_grades.feature b/mod/quiz/tests/behat/attempt_multiple_grades.feature new file mode 100644 index 00000000000..80719d957df --- /dev/null +++ b/mod/quiz/tests/behat/attempt_multiple_grades.feature @@ -0,0 +1,53 @@ +@mod @mod_quiz +Feature: Attempt a quiz with multiple grades + As a student + In order to demonstrate multiple skills at once + I need to be able to attempt quizzes with multiple grades setup + + Background: + Given the following "users" exist: + | username | + | student | + And the following "courses" exist: + | fullname | shortname | + | Course 1 | C1 | + And the following "course enrolments" exist: + | user | course | role | + | student | C1 | student | + And the following "question categories" exist: + | contextlevel | reference | name | + | Course | C1 | Test questions | + And the following "activities" exist: + | activity | name | course | + | quiz | Quiz 1 | C1 | + + @javascript + Scenario: Navigation to, and display of, grading setup + Given the following "questions" exist: + | questioncategory | qtype | name | questiontext | + | Test questions | truefalse | Reading | Can you read this? | + | Test questions | truefalse | Listening | Can you hear this? | + And the following "mod_quiz > grade items" exist: + | quiz | name | + | Quiz 1 | Grade for reading | + | Quiz 1 | Grade for listening | + | Quiz 1 | Unused grade item | + And quiz "Quiz 1" contains the following questions: + | question | page | grade item | + | Reading | 1 | Grade for reading | + | Listening | 1 | Grade for listening | + + When I am on the "Quiz 1" "quiz activity" page logged in as "student" + And I click on "Attempt quiz" "button" + And I set the field "True" in the "Can you read this?" "question" to "1" + And I set the field "False" in the "Can you hear this?" "question" to "1" + And I press "Finish attempt ..." + And I press "Submit all and finish" + And I click on "Submit all and finish" "button" in the "Submit all your answers and finish?" "dialogue" + + Then I should see "1.00 out of 1.00" in the "Grade for reading" "table_row" + And I should see "0.00 out of 1.00" in the "Grade for listening" "table_row" + And I should not see "Unused grade item" + And I should see "1.00/2.00" in the "Marks" "table_row" + # Funny order because 'Grade' also appears in other rows. + And I should see "Grade" in the "50.00 out of 100.00" "table_row" diff --git a/mod/quiz/tests/behat/editing_multiple_grades.feature b/mod/quiz/tests/behat/editing_multiple_grades.feature index 14c1e95367c..fdb5bf17aa1 100644 --- a/mod/quiz/tests/behat/editing_multiple_grades.feature +++ b/mod/quiz/tests/behat/editing_multiple_grades.feature @@ -148,3 +148,24 @@ Feature: Setup multiple grades for a quiz And I press "Reset advanced grading setup" And I click on "Cancel" "button" in the "Reset grading setup?" "dialogue" Then I should see "Intuition" + + @javascript + Scenario: Automatically set up one grade item per section + Given quiz "Quiz 1" contains the following questions: + | question | page | + | Question A | 1 | + | Question B | 1 | + | Question C | 2 | + And quiz "Quiz 1" contains the following sections: + | heading | firstslot | shuffle | + | Reading | 1 | 0 | + | Listening | 3 | 0 | + + When I am on the "Quiz 1" "mod_quiz > multiple grades setup" page logged in as teacher + And I press "Setup a grade for each section" + + Then "Reading" "table_row" should exist in the "mod_quiz-grade-item-list" "table" + And "Listening" "table_row" should exist in the "mod_quiz-grade-item-list" "table" + And the field "Question A" matches value "Reading" + And the field "Question B" matches value "Reading" + And the field "Question C" matches value "Listening" diff --git a/mod/quiz/tests/external/grade_items_test.php b/mod/quiz/tests/external/grade_items_test.php index f03f7433d69..4a31eb1275c 100644 --- a/mod/quiz/tests/external/grade_items_test.php +++ b/mod/quiz/tests/external/grade_items_test.php @@ -200,4 +200,76 @@ final class grade_items_test extends externallib_advanced_testcase { return $quizobj; } + + public function test_create_grade_item_per_section_works(): void { + global $SITE; + $this->resetAfterTest(); + $this->setAdminUser(); + + // Create a quiz with no grade items yet, but two sections. + /** @var \mod_quiz_generator $quizgenerator */ + $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz'); + $quiz = $quizgenerator->create_instance(['course' => $SITE->id]); + + // Create three questions. + /** @var core_question_generator $questiongenerator */ + $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); + $cat = $questiongenerator->create_question_category(); + $saq1 = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]); + $saq2 = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]); + $saq3 = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]); + + // Add them to the quiz. + quiz_add_quiz_question($saq1->id, $quiz, 1, 1); + quiz_add_quiz_question($saq2->id, $quiz, 1, 1); + quiz_add_quiz_question($saq3->id, $quiz, 2, 1); + + // Create two sections. + $quizobj = quiz_settings::create($quiz->id); + $structure = $quizobj->get_structure(); + $defaultsection = array_values($structure->get_sections())[0]; + $structure->set_section_heading($defaultsection->id, 'Listening'); + $structure->add_section_heading(2, 'Reading'); + + // Call the method we are testing. + create_grade_item_per_section::execute($quizobj->get_quizid()); + + // Verify. + $structure = $quizobj->get_structure(); + + $gradeitems = array_values($structure->get_grade_items()); + $this->assertCount(2, $gradeitems); + $this->assertEquals('Listening', $gradeitems[0]->name); + $this->assertEquals(1, $gradeitems[0]->sortorder); + $this->assertEquals('Reading', $gradeitems[1]->name); + $this->assertEquals(2, $gradeitems[1]->sortorder); + + $this->assertEquals($gradeitems[0]->id, $structure->get_slot_by_number(1)->quizgradeitemid); + $this->assertEquals($gradeitems[0]->id, $structure->get_slot_by_number(2)->quizgradeitemid); + $this->assertEquals($gradeitems[1]->id, $structure->get_slot_by_number(3)->quizgradeitemid); + } + + public function test_create_grade_item_per_section_service_checks_permissions(): void { + global $SITE; + $this->resetAfterTest(); + + // Create a quiz. + /** @var \mod_quiz_generator $quizgenerator */ + $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz'); + $quiz = $quizgenerator->create_instance(['course' => $SITE->id]); + + $unprivilegeduser = $this->getDataGenerator()->create_user(); + $this->setUser($unprivilegeduser); + + $this->expectException(required_capability_exception::class); + create_grade_item_per_section::execute($quiz->id); + } + + public function test_cant_create_grade_item_per_section_if_grade_items_already_exist(): void { + $quizobj = $this->create_quiz_with_two_grade_items(); + + $this->expectException(coding_exception::class); + create_grade_item_per_section::execute($quizobj->get_quizid()); + } + }