From bbe11e1ceac69da6c189cf14fc9a74c9803a9ba2 Mon Sep 17 00:00:00 2001 From: Tim Hunt Date: Fri, 11 Aug 2023 18:22:08 +0100 Subject: [PATCH] MDL-74610 quiz: multiple grades - update slot grade items --- .../amd/build/edit_multiple_grades.min.js | 2 +- .../amd/build/edit_multiple_grades.min.js.map | 2 +- mod/quiz/amd/src/edit_multiple_grades.js | 48 +++++++++++++++++++ mod/quiz/templates/edit_grading_page.mustache | 4 +- .../behat/editing_multiple_grades.feature | 17 +++++++ 5 files changed, 69 insertions(+), 4 deletions(-) diff --git a/mod/quiz/amd/build/edit_multiple_grades.min.js b/mod/quiz/amd/build/edit_multiple_grades.min.js index c762f05a810..fa72fefa89e 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 handleGradeItemDelete=e=>{e.preventDefault();const pending=new _pending.default("delete-quiz-grade-item"),tableCell=e.target.closest("td");(0,_loadingicon.addIconToContainerRemoveOnCompletion)(tableCell,pending);const tableRow=tableCell.closest("tr");((quizId,gradeItemId)=>(0,_ajax.call)([{methodname:"mod_quiz_delete_grade_items",args:{quizid:quizId,quizgradeitems:[{id:gradeItemId}]}}])[0])(tableRow.closest("table").dataset.quizId,tableRow.dataset.quizGradeItemId).then((()=>pending.resolve())).then((()=>window.location.reload())).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("span.inplaceeditable.inplaceeditingon");if(!editableSpan)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.addIconToContainerRemoveOnCompletion)(tableCell,pending);const tableRow=tableCell.closest("tr");((quizId,gradeItemId,newName)=>(0,_ajax.call)([{methodname:"mod_quiz_update_grade_items",args:{quizid:quizId,quizgradeitems:[{id:gradeItemId,name:newName}]}}])[0])(tableRow.closest("table").dataset.quizId,tableRow.dataset.quizGradeItemId,newName).then((()=>pending.resolve())).then((()=>window.location.reload())).catch(_notification.default.exception)},handleGradeItemKeyUp=e=>{if(27!==e.keyCode)return;const editableSpan=e.target.closest("span.inplaceeditable.inplaceeditingon");editableSpan&&(e.preventDefault(),stopEditingGadeItem(editableSpan))},handleGradeItemFocusOut=e=>{if(_config.default.behatsiterunning)return;const editableSpan=e.target.closest("span.inplaceeditable.inplaceeditingon");editableSpan&&(e.preventDefault(),stopEditingGadeItem(editableSpan))},handleGradeItemClick=e=>{const link=e.target.closest("a");link&&(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("span.inplaceeditable");document.querySelectorAll("span.inplaceeditable.inplaceeditingon").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))},handleAddGradeItemClick=e=>{e.preventDefault();const pending=new _pending.default("create-quiz-grade-item");(0,_loadingicon.addIconToContainerRemoveOnCompletion)(e.target.parentNode,pending);const quizId=e.target.dataset.quizId;(0,_str.get_string)("gradeitemdefaultname","quiz").then((name=>((quizId,name)=>(0,_ajax.call)([{methodname:"mod_quiz_create_grade_items",args:{quizid:quizId,quizgradeitems:[{name:name}]}}])[0])(quizId,name))).then((()=>pending.resolve())).then((()=>window.location.reload())).catch(_notification.default.exception)};_exports.init=()=>{(()=>{const gradeItemTable=document.getElementById("mod_quiz-grade-item-list");gradeItemTable&&(gradeItemTable.addEventListener("click",handleGradeItemClick),gradeItemTable.addEventListener("keydown",handleGradeItemKeyDown),gradeItemTable.addEventListener("keyup",handleGradeItemKeyUp),gradeItemTable.addEventListener("focusout",handleGradeItemFocusOut)),document.getElementById("mod_quiz-add_grade_item").addEventListener("click",handleAddGradeItemClick)})()}})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_config=_interopRequireDefault(_config),_notification=_interopRequireDefault(_notification),_pending=_interopRequireDefault(_pending);const handleGradeItemDelete=e=>{e.preventDefault();const pending=new _pending.default("delete-quiz-grade-item"),tableCell=e.target.closest("td");(0,_loadingicon.addIconToContainerRemoveOnCompletion)(tableCell,pending);const tableRow=tableCell.closest("tr");((quizId,gradeItemId)=>(0,_ajax.call)([{methodname:"mod_quiz_delete_grade_items",args:{quizid:quizId,quizgradeitems:[{id:gradeItemId}]}}])[0])(tableRow.closest("table").dataset.quizId,tableRow.dataset.quizGradeItemId).then((()=>pending.resolve())).then((()=>window.location.reload())).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("span.inplaceeditable.inplaceeditingon");if(!editableSpan)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.addIconToContainerRemoveOnCompletion)(tableCell,pending);const tableRow=tableCell.closest("tr");((quizId,gradeItemId,newName)=>(0,_ajax.call)([{methodname:"mod_quiz_update_grade_items",args:{quizid:quizId,quizgradeitems:[{id:gradeItemId,name:newName}]}}])[0])(tableRow.closest("table").dataset.quizId,tableRow.dataset.quizGradeItemId,newName).then((()=>pending.resolve())).then((()=>window.location.reload())).catch(_notification.default.exception)},handleGradeItemKeyUp=e=>{if(27!==e.keyCode)return;const editableSpan=e.target.closest("span.inplaceeditable.inplaceeditingon");editableSpan&&(e.preventDefault(),stopEditingGadeItem(editableSpan))},handleGradeItemFocusOut=e=>{if(_config.default.behatsiterunning)return;const editableSpan=e.target.closest("span.inplaceeditable.inplaceeditingon");editableSpan&&(e.preventDefault(),stopEditingGadeItem(editableSpan))},handleSlotGradeItemChanged=e=>{const select=e.target.closest("select[data-slot-id]");if(!select)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.addIconToContainerRemoveOnCompletion)(tableCell,pending);((quizId,slotId,gradeItemId)=>(0,_ajax.call)([{methodname:"mod_quiz_update_slots",args:{quizid:quizId,slots:[{id:slotId,quizgradeitemid:gradeItemId}]}}])[0])(tableCell.closest("table").dataset.quizId,slotId,newGradeItemId).then((()=>pending.resolve())).then((()=>window.location.reload())).catch(_notification.default.exception)},handleGradeItemClick=e=>{const link=e.target.closest("a");link&&(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("span.inplaceeditable");document.querySelectorAll("span.inplaceeditable.inplaceeditingon").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))},handleAddGradeItemClick=e=>{e.preventDefault();const pending=new _pending.default("create-quiz-grade-item");(0,_loadingicon.addIconToContainerRemoveOnCompletion)(e.target.parentNode,pending);const quizId=e.target.dataset.quizId;(0,_str.get_string)("gradeitemdefaultname","quiz").then((name=>((quizId,name)=>(0,_ajax.call)([{methodname:"mod_quiz_create_grade_items",args:{quizid:quizId,quizgradeitems:[{name:name}]}}])[0])(quizId,name))).then((()=>pending.resolve())).then((()=>window.location.reload())).catch(_notification.default.exception)};_exports.init=()=>{(()=>{const gradeItemTable=document.getElementById("mod_quiz-grade-item-list");gradeItemTable&&(gradeItemTable.addEventListener("click",handleGradeItemClick),gradeItemTable.addEventListener("keydown",handleGradeItemKeyDown),gradeItemTable.addEventListener("keyup",handleGradeItemKeyUp),gradeItemTable.addEventListener("focusout",handleGradeItemFocusOut)),document.getElementById("mod_quiz-add_grade_item").addEventListener("click",handleAddGradeItemClick);const slotTable=document.getElementById("mod_quiz-slot-list");gradeItemTable&&slotTable.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 11c87093cd6..43b99965a20 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 {addIconToContainerRemoveOnCompletion} from 'core/loadingicon';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport {get_string as getString} from 'core/str';\n\n/**\n * Call the Ajax service to create a quiz grade item.\n *\n * @param {Number} quizId\n * @param {String} name\n * @return {Promise}\n */\nconst createGradeItem = (quizId, name) => fetchMany([{\n methodname: 'mod_quiz_create_grade_items',\n args: {\n quizid: quizId,\n quizgradeitems: [{name: name}],\n }\n}])[0];\n\n/**\n * Call the Ajax service to update a quiz grade item.\n *\n * @param {Number} quizId\n * @param {Number} gradeItemId\n * @param {String} newName\n * @return {Promise}\n */\nconst updateGradeItem = (quizId, gradeItemId, newName) => fetchMany([{\n methodname: 'mod_quiz_update_grade_items',\n args: {\n quizid: quizId,\n quizgradeitems: [{id: gradeItemId, name: newName}],\n }\n}])[0];\n\n/**\n * Call the Ajax service to delete a quiz grade item.\n *\n * @param {Number} quizId\n * @param {Number} gradeItemId\n * @return {Promise}\n */\nconst deleteGradeItem = (quizId, gradeItemId) => fetchMany([{\n methodname: 'mod_quiz_delete_grade_items',\n args: {\n quizid: quizId,\n quizgradeitems: [{id: gradeItemId}],\n }\n}])[0];\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 addIconToContainerRemoveOnCompletion(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 deleteGradeItem(quizId, gradeItemId)\n .then(() => pending.resolve())\n .then(() => window.location.reload())\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('span.inplaceeditable');\n\n document.querySelectorAll('span.inplaceeditable.inplaceeditingon').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('span.inplaceeditable.inplaceeditingon');\n if (!editableSpan) {\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 addIconToContainerRemoveOnCompletion(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 updateGradeItem(quizId, gradeItemId, newName)\n .then(() => pending.resolve())\n .then(() => window.location.reload())\n .catch(Notification.exception);\n};\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('span.inplaceeditable.inplaceeditingon');\n if (!editableSpan) {\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('span.inplaceeditable.inplaceeditingon');\n if (!editableSpan) {\n return;\n }\n\n e.preventDefault();\n stopEditingGadeItem(editableSpan);\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 if (!link) {\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 'Add grade item' table.\n *\n * @param {Event} e click event.\n */\nconst handleAddGradeItemClick = (e) => {\n e.preventDefault();\n const pending = new Pending('create-quiz-grade-item');\n addIconToContainerRemoveOnCompletion(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(() => pending.resolve())\n .then(() => window.location.reload())\n .catch(Notification.exception);\n};\n\n/**\n * Replace the container with a new version.\n */\nconst registerEventListeners = () => {\n const gradeItemTable = document.getElementById('mod_quiz-grade-item-list');\n if (gradeItemTable) {\n gradeItemTable.addEventListener('click', handleGradeItemClick);\n gradeItemTable.addEventListener('keydown', handleGradeItemKeyDown);\n gradeItemTable.addEventListener('keyup', handleGradeItemKeyUp);\n gradeItemTable.addEventListener('focusout', handleGradeItemFocusOut);\n }\n\n document.getElementById('mod_quiz-add_grade_item').addEventListener('click', handleAddGradeItemClick);\n};\n\n/**\n * Entry point.\n */\nexport const init = () => {\n registerEventListeners();\n};\n"],"names":["handleGradeItemDelete","e","preventDefault","pending","Pending","tableCell","target","closest","tableRow","quizId","gradeItemId","methodname","args","quizid","quizgradeitems","id","deleteGradeItem","dataset","quizGradeItemId","then","resolve","window","location","reload","catch","Notification","exception","stopEditingGadeItem","editableSpan","innerHTML","oldContent","classList","remove","querySelector","focus","handleGradeItemKeyDown","keyCode","newName","value","name","updateGradeItem","handleGradeItemKeyUp","handleGradeItemFocusOut","MoodleConfig","behatsiterunning","handleGradeItemClick","link","actionDelete","actionEdit","document","querySelectorAll","forEach","instructions","uniqueId","editLabel","rawName","inputElement","select","add","handleGradeItemEditStart","handleAddGradeItemClick","parentNode","createGradeItem","gradeItemTable","getElementById","addEventListener","registerEventListeners"],"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 {addIconToContainerRemoveOnCompletion} from 'core/loadingicon';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport {get_string as getString} from 'core/str';\n\n/**\n * Call the Ajax service to create a quiz grade item.\n *\n * @param {Number} quizId\n * @param {String} name\n * @return {Promise}\n */\nconst createGradeItem = (quizId, name) => fetchMany([{\n methodname: 'mod_quiz_create_grade_items',\n args: {\n quizid: quizId,\n quizgradeitems: [{name: name}],\n }\n}])[0];\n\n/**\n * Call the Ajax service to update a quiz grade item.\n *\n * @param {Number} quizId\n * @param {Number} gradeItemId\n * @param {String} newName\n * @return {Promise}\n */\nconst updateGradeItem = (quizId, gradeItemId, newName) => fetchMany([{\n methodname: 'mod_quiz_update_grade_items',\n args: {\n quizid: quizId,\n quizgradeitems: [{id: gradeItemId, name: newName}],\n }\n}])[0];\n\n/**\n * Call the Ajax service to delete a quiz grade item.\n *\n * @param {Number} quizId\n * @param {Number} gradeItemId\n * @return {Promise}\n */\nconst deleteGradeItem = (quizId, gradeItemId) => fetchMany([{\n methodname: 'mod_quiz_delete_grade_items',\n args: {\n quizid: quizId,\n quizgradeitems: [{id: gradeItemId}],\n }\n}])[0];\n\n/**\n * Call the Ajax service to update the quiz grade item used by a slot.\n *\n * @param {Number} quizId\n * @param {Number} slotId\n * @param {Number|null} gradeItemId\n * @return {Promise}\n */\nconst updateSlotGradeItem = (quizId, slotId, gradeItemId) => fetchMany([{\n methodname: 'mod_quiz_update_slots',\n args: {\n quizid: quizId,\n slots: [{id: slotId, quizgradeitemid: gradeItemId}],\n }\n}])[0];\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 addIconToContainerRemoveOnCompletion(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 deleteGradeItem(quizId, gradeItemId)\n .then(() => pending.resolve())\n .then(() => window.location.reload())\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('span.inplaceeditable');\n\n document.querySelectorAll('span.inplaceeditable.inplaceeditingon').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('span.inplaceeditable.inplaceeditingon');\n if (!editableSpan) {\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 addIconToContainerRemoveOnCompletion(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 updateGradeItem(quizId, gradeItemId, newName)\n .then(() => pending.resolve())\n .then(() => window.location.reload())\n .catch(Notification.exception);\n};\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('span.inplaceeditable.inplaceeditingon');\n if (!editableSpan) {\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('span.inplaceeditable.inplaceeditingon');\n if (!editableSpan) {\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('select[data-slot-id]');\n if (!select) {\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 addIconToContainerRemoveOnCompletion(tableCell, pending);\n\n const quizId = tableCell.closest('table').dataset.quizId;\n\n updateSlotGradeItem(quizId, slotId, newGradeItemId)\n .then(() => pending.resolve())\n .then(() => window.location.reload())\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 if (!link) {\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 'Add grade item' table.\n *\n * @param {Event} e click event.\n */\nconst handleAddGradeItemClick = (e) => {\n e.preventDefault();\n const pending = new Pending('create-quiz-grade-item');\n addIconToContainerRemoveOnCompletion(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(() => pending.resolve())\n .then(() => window.location.reload())\n .catch(Notification.exception);\n};\n\n/**\n * Replace the container with a new version.\n */\nconst registerEventListeners = () => {\n const gradeItemTable = document.getElementById('mod_quiz-grade-item-list');\n if (gradeItemTable) {\n gradeItemTable.addEventListener('click', handleGradeItemClick);\n gradeItemTable.addEventListener('keydown', handleGradeItemKeyDown);\n gradeItemTable.addEventListener('keyup', handleGradeItemKeyUp);\n gradeItemTable.addEventListener('focusout', handleGradeItemFocusOut);\n }\n\n document.getElementById('mod_quiz-add_grade_item').addEventListener('click', handleAddGradeItemClick);\n\n const slotTable = document.getElementById('mod_quiz-slot-list');\n if (gradeItemTable) {\n slotTable.addEventListener('change', handleSlotGradeItemChanged);\n }\n};\n\n/**\n * Entry point.\n */\nexport const init = () => {\n registerEventListeners();\n};\n"],"names":["handleGradeItemDelete","e","preventDefault","pending","Pending","tableCell","target","closest","tableRow","quizId","gradeItemId","methodname","args","quizid","quizgradeitems","id","deleteGradeItem","dataset","quizGradeItemId","then","resolve","window","location","reload","catch","Notification","exception","stopEditingGadeItem","editableSpan","innerHTML","oldContent","classList","remove","querySelector","focus","handleGradeItemKeyDown","keyCode","newName","value","name","updateGradeItem","handleGradeItemKeyUp","handleGradeItemFocusOut","MoodleConfig","behatsiterunning","handleSlotGradeItemChanged","select","slotId","newGradeItemId","slots","quizgradeitemid","updateSlotGradeItem","handleGradeItemClick","link","actionDelete","actionEdit","document","querySelectorAll","forEach","instructions","uniqueId","editLabel","rawName","inputElement","add","handleGradeItemEditStart","handleAddGradeItemClick","parentNode","createGradeItem","gradeItemTable","getElementById","addEventListener","slotTable","registerEventListeners"],"mappings":";;;;;;;8NAiGMA,sBAAyBC,IAC3BA,EAAEC,uBACIC,QAAU,IAAIC,iBAAQ,0BAEtBC,UAAYJ,EAAEK,OAAOC,QAAQ,4DACEF,UAAWF,eAE1CK,SAAWH,UAAUE,QAAQ,MApCf,EAACE,OAAQC,eAAgB,cAAU,CAAC,CACxDC,WAAY,8BACZC,KAAM,CACFC,OAAQJ,OACRK,eAAgB,CAAC,CAACC,GAAIL,kBAE1B,GAkCAM,CAHeR,SAASD,QAAQ,SAASU,QAAQR,OAC7BD,SAASS,QAAQC,iBAGhCC,MAAK,IAAMhB,QAAQiB,YACnBD,MAAK,IAAME,OAAOC,SAASC,WAC3BC,MAAMC,sBAAaC,YAOtBC,oBAAuBC,eACzBA,aAAaC,UAAYD,aAAaX,QAAQa,kBACvCF,aAAaX,QAAQa,WAE5BF,aAAaG,UAAUC,OAAO,oBAC9BJ,aAAaK,cAAc,sBAAsBC,SAuC/CC,uBAA0BlC,OACV,KAAdA,EAAEmC,qBAIAR,aAAe3B,EAAEK,OAAOC,QAAQ,6CACjCqB,oBAIL3B,EAAEC,uBACIC,QAAU,IAAIC,iBAAQ,6BAEtBiC,QAAUT,aAAaK,cAAc,SAASK,MAC9CjC,UAAYJ,EAAEK,OAAOC,QAAQ,4DACEF,UAAWF,eAE1CK,SAAWH,UAAUE,QAAQ,MA9Hf,EAACE,OAAQC,YAAa2B,WAAY,cAAU,CAAC,CACjE1B,WAAY,8BACZC,KAAM,CACFC,OAAQJ,OACRK,eAAgB,CAAC,CAACC,GAAIL,YAAa6B,KAAMF,cAE7C,GA4HAG,CAHehC,SAASD,QAAQ,SAASU,QAAQR,OAC7BD,SAASS,QAAQC,gBAEAmB,SAChClB,MAAK,IAAMhB,QAAQiB,YACnBD,MAAK,IAAME,OAAOC,SAASC,WAC3BC,MAAMC,sBAAaC,YAQtBe,qBAAwBxC,OACR,KAAdA,EAAEmC,qBAIAR,aAAe3B,EAAEK,OAAOC,QAAQ,yCACjCqB,eAIL3B,EAAEC,iBACFyB,oBAAoBC,gBAQlBc,wBAA2BzC,OACzB0C,gBAAaC,8BAKXhB,aAAe3B,EAAEK,OAAOC,QAAQ,yCACjCqB,eAIL3B,EAAEC,iBACFyB,oBAAoBC,gBAQlBiB,2BAA8B5C,UAC1B6C,OAAS7C,EAAEK,OAAOC,QAAQ,4BAC3BuC,cAIL7C,EAAEC,uBACIC,QAAU,IAAIC,iBAAQ,gCAEtB2C,OAASD,OAAO7B,QAAQ8B,OACxBC,eAAiBF,OAAOR,MAAQQ,OAAOR,MAAQ,KAC/CjC,UAAYJ,EAAEK,OAAOC,QAAQ,4DACEF,UAAWF,SAjKxB,EAACM,OAAQsC,OAAQrC,eAAgB,cAAU,CAAC,CACpEC,WAAY,wBACZC,KAAM,CACFC,OAAQJ,OACRwC,MAAO,CAAC,CAAClC,GAAIgC,OAAQG,gBAAiBxC,kBAE1C,GA+JAyC,CAFe9C,UAAUE,QAAQ,SAASU,QAAQR,OAEtBsC,OAAQC,gBAC/B7B,MAAK,IAAMhB,QAAQiB,YACnBD,MAAK,IAAME,OAAOC,SAASC,WAC3BC,MAAMC,sBAAaC,YAQtB0B,qBAAwBnD,UACpBoD,KAAOpD,EAAEK,OAAOC,QAAQ,KAEzB8C,OAIDA,KAAKpC,QAAQqC,cACbtD,sBAAsBC,GAGtBoD,KAAKpC,QAAQsC,YA5IatD,CAAAA,IAC9BA,EAAEC,uBACIC,QAAU,IAAIC,iBAAQ,8BACtBwB,aAAe3B,EAAEK,OAAOC,QAAQ,wBAEtCiD,SAASC,iBAAiB,yCAAyCC,QAAQ/B,qBAE3EC,aAAaX,QAAQa,WAAaF,aAAaC,8BACrC,yBACLV,MAAMwC,qBACGC,SAAW,iBAAmBhC,aAAarB,QAAQ,MAAMU,QAAQC,gBACvEU,aAAaC,UAAY,kCAAoC8B,aAApC,sCACgBC,SAAW,KAAOhC,aAAaX,QAAQ4C,UADvD,kCAEWD,SAAW,YAAchC,aAAaX,QAAQ6C,QAClE,4CAEVC,aAAenC,aAAaK,cAAc,gBAChD8B,aAAa7B,QACb6B,aAAajB,SACblB,aAAaG,UAAUiC,IAAI,oBAC3B7D,QAAQiB,UACD,QAEVI,MAAMC,sBAAaC,YAsHpBuC,CAAyBhE,KAS3BiE,wBAA2BjE,IAC7BA,EAAEC,uBACIC,QAAU,IAAIC,iBAAQ,gFACSH,EAAEK,OAAO6D,WAAYhE,eAEpDM,OAASR,EAAEK,OAAOW,QAAQR,2BAEtB,uBAAwB,QAC7BU,MAAMoB,MA5PS,EAAC9B,OAAQ8B,QAAS,cAAU,CAAC,CACjD5B,WAAY,8BACZC,KAAM,CACFC,OAAQJ,OACRK,eAAgB,CAAC,CAACyB,KAAMA,WAE5B,GAsPoB6B,CAAgB3D,OAAQ8B,QACvCpB,MAAK,IAAMhB,QAAQiB,YACnBD,MAAK,IAAME,OAAOC,SAASC,WAC3BC,MAAMC,sBAAaC,0BA0BR,KApBW,YACrB2C,eAAiBb,SAASc,eAAe,4BAC3CD,iBACAA,eAAeE,iBAAiB,QAASnB,sBACzCiB,eAAeE,iBAAiB,UAAWpC,wBAC3CkC,eAAeE,iBAAiB,QAAS9B,sBACzC4B,eAAeE,iBAAiB,WAAY7B,0BAGhDc,SAASc,eAAe,2BAA2BC,iBAAiB,QAASL,+BAEvEM,UAAYhB,SAASc,eAAe,sBACtCD,gBACAG,UAAUD,iBAAiB,SAAU1B,6BAQzC4B"} \ 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 03f14d30ae0..b28b2764018 100644 --- a/mod/quiz/amd/src/edit_multiple_grades.js +++ b/mod/quiz/amd/src/edit_multiple_grades.js @@ -74,6 +74,22 @@ const deleteGradeItem = (quizId, gradeItemId) => fetchMany([{ } }])[0]; +/** + * Call the Ajax service to update the quiz grade item used by a slot. + * + * @param {Number} quizId + * @param {Number} slotId + * @param {Number|null} gradeItemId + * @return {Promise} + */ +const updateSlotGradeItem = (quizId, slotId, gradeItemId) => fetchMany([{ + methodname: 'mod_quiz_update_slots', + args: { + quizid: quizId, + slots: [{id: slotId, quizgradeitemid: gradeItemId}], + } +}])[0]; + /** * Handle click events on the delete icon. * @@ -210,6 +226,33 @@ const handleGradeItemFocusOut = (e) => { stopEditingGadeItem(editableSpan); }; +/** + * Handle when the selected grade item for a slot is changed. + * + * @param {Event} e event. + */ +const handleSlotGradeItemChanged = (e) => { + const select = e.target.closest('select[data-slot-id]'); + if (!select) { + return; + } + + e.preventDefault(); + const pending = new Pending('edit-slot-grade-item-updated'); + + const slotId = select.dataset.slotId; + const newGradeItemId = select.value ? select.value : null; + const tableCell = e.target.closest('td'); + addIconToContainerRemoveOnCompletion(tableCell, pending); + + const quizId = tableCell.closest('table').dataset.quizId; + + updateSlotGradeItem(quizId, slotId, newGradeItemId) + .then(() => pending.resolve()) + .then(() => window.location.reload()) + .catch(Notification.exception); +}; + /** * Handle clicks in the table the shows the grade items. * @@ -263,6 +306,11 @@ const registerEventListeners = () => { } document.getElementById('mod_quiz-add_grade_item').addEventListener('click', handleAddGradeItemClick); + + const slotTable = document.getElementById('mod_quiz-slot-list'); + if (gradeItemTable) { + slotTable.addEventListener('change', handleSlotGradeItemChanged); + } }; /** diff --git a/mod/quiz/templates/edit_grading_page.mustache b/mod/quiz/templates/edit_grading_page.mustache index ce3a2cd0b63..581e25b084a 100644 --- a/mod/quiz/templates/edit_grading_page.mustache +++ b/mod/quiz/templates/edit_grading_page.mustache @@ -105,7 +105,7 @@ {{/hasslots}} {{#hasslots}} - +
@@ -126,7 +126,7 @@
{{#str}} qnumberbrief, quiz {{/str}}{{displaynumber}} - {{#choices}} {{/choices}} diff --git a/mod/quiz/tests/behat/editing_multiple_grades.feature b/mod/quiz/tests/behat/editing_multiple_grades.feature index 798a3e6717b..7bfadce81c1 100644 --- a/mod/quiz/tests/behat/editing_multiple_grades.feature +++ b/mod/quiz/tests/behat/editing_multiple_grades.feature @@ -89,3 +89,20 @@ Feature: Setup multiple grades for a quiz And I follow "Delete grade item Unused grade item" Then I should not see "Unused grade item" And I should see "This quiz does not yet have any grade items defined" + + @javascript + Scenario: Grade item for a slot can be changed + Given the following "mod_quiz > grade items" exist: + | quiz | name | + | Quiz 1 | Intuition | + And quiz "Quiz 1" contains the following questions: + | question | page | + | Question A | 1 | + When I am on the "Quiz 1" "mod_quiz > multiple grades setup" page logged in as teacher + And "Delete" "icon" should exist in the "Intuition" "table_row" + And I set the field "Question A" to "Intuition" + Then "Delete" "icon" should not exist in the "Intuition" "table_row" + And the field "Question A" matches value "Intuition" + And I set the field "Question A" to "[none]" + And "Delete" "icon" should exist in the "Intuition" "table_row" + And the field "Question A" matches value "[none]"