diff --git a/grade/amd/build/grades/grader/gradingpanel/point.min.js b/grade/amd/build/grades/grader/gradingpanel/point.min.js index 3f9ab230a71..1042e337e47 100644 --- a/grade/amd/build/grades/grader/gradingpanel/point.min.js +++ b/grade/amd/build/grades/grader/gradingpanel/point.min.js @@ -1,2 +1,2 @@ -define ("core_grades/grades/grader/gradingpanel/point",["exports","./repository","core_grades/grades/grader/gradingpanel/comparison","jquery","./normalise"],function(a,b,c,d,e){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.storeCurrentGrade=a.fetchCurrentGrade=void 0;d=function(a){return a&&a.__esModule?a:{default:a}}(d);function f(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function g(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var i=a.apply(b,c);function g(a){f(i,d,e,g,h,"next",a)}function h(a){f(i,d,e,g,h,"throw",a)}g(void 0)})}}a.fetchCurrentGrade=function fetchCurrentGrade(){return(0,b.fetchGrade)("point").apply(void 0,arguments)};var h=function(){var a=g(regeneratorRuntime.mark(function a(f,g,h,i,j){var k,l;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:k=j.querySelector("form");l=k.querySelector("input[name=\"grade\"]");if(!(!l.checkValidity()||!l.value.trim())){a.next=4;break}return a.abrupt("return",e.invalidResult);case 4:if(!(!0===(0,c.compareData)(k))){a.next=10;break}a.next=7;return(0,b.saveGrade)("point")(f,g,h,i,(0,d.default)(k).serialize());case 7:return a.abrupt("return",a.sent);case 10:return a.abrupt("return","");case 11:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}();a.storeCurrentGrade=h}); +define ("core_grades/grades/grader/gradingpanel/point",["exports","./repository","core_grades/grades/grader/gradingpanel/comparison","jquery","./normalise"],function(a,b,c,d,e){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.storeCurrentGrade=a.fetchCurrentGrade=void 0;d=function(a){return a&&a.__esModule?a:{default:a}}(d);function f(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function g(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var i=a.apply(b,c);function g(a){f(i,d,e,g,h,"next",a)}function h(a){f(i,d,e,g,h,"throw",a)}g(void 0)})}}a.fetchCurrentGrade=function fetchCurrentGrade(){return(0,b.fetchGrade)("point").apply(void 0,arguments)};var h=function(){var a=g(regeneratorRuntime.mark(function a(f,g,h,i,j,k){var l,m;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:l=k.querySelector("form");m=l.querySelector("input[name=\"grade\"]");if(!(!m.checkValidity()||!m.value.trim())){a.next=4;break}return a.abrupt("return",e.invalidResult);case 4:if(!(!0===(0,c.compareData)(l))){a.next=10;break}a.next=7;return(0,b.saveGrade)("point")(f,g,h,i,j,(0,d.default)(l).serialize());case 7:return a.abrupt("return",a.sent);case 10:return a.abrupt("return","");case 11:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}();a.storeCurrentGrade=h}); //# sourceMappingURL=point.min.js.map diff --git a/grade/amd/build/grades/grader/gradingpanel/point.min.js.map b/grade/amd/build/grades/grader/gradingpanel/point.min.js.map index 81470f9d171..4f3b718f3fb 100644 --- a/grade/amd/build/grades/grader/gradingpanel/point.min.js.map +++ b/grade/amd/build/grades/grader/gradingpanel/point.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../../src/grades/grader/gradingpanel/point.js"],"names":["fetchCurrentGrade","storeCurrentGrade","component","context","itemname","userId","rootNode","form","querySelector","grade","checkValidity","value","trim","invalidResult","serialize"],"mappings":"8RA2BA,uD,mVAaiC,QAApBA,CAAAA,iBAAoB,SAAa,iBAAW,OAAX,yBAAb,C,CAY1B,GAAMC,CAAAA,CAAiB,4CAAG,WAAMC,CAAN,CAAiBC,CAAjB,CAA0BC,CAA1B,CAAoCC,CAApC,CAA4CC,CAA5C,2FACvBC,CADuB,CAChBD,CAAQ,CAACE,aAAT,CAAuB,MAAvB,CADgB,CAEvBC,CAFuB,CAEfF,CAAI,CAACC,aAAL,CAAmB,uBAAnB,CAFe,MAIzB,CAACC,CAAK,CAACC,aAAN,EAAD,EAA0B,CAACD,CAAK,CAACE,KAAN,CAAYC,IAAZ,EAJF,2CAKlBC,eALkB,cAQzB,uBAAYN,CAAZ,CARyB,kCASZ,gBAAU,OAAV,EAAmBL,CAAnB,CAA8BC,CAA9B,CAAuCC,CAAvC,CAAiDC,CAAjD,CAAyD,cAAOE,CAAP,EAAaO,SAAb,EAAzD,CATY,0EAWlB,EAXkB,2CAAH,uDAAvB,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Grading panel for simple direct grading.\n *\n * @module core_grades/grades/grader/gradingpanel/point\n * @package core_grades\n * @copyright 2019 Andrew Nicols \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {saveGrade, fetchGrade} from './repository';\nimport {compareData} from 'core_grades/grades/grader/gradingpanel/comparison';\n// Note: We use jQuery.serializer here until we can rewrite Ajax to use XHR.send()\nimport jQuery from 'jquery';\nimport {invalidResult} from './normalise';\n\n/**\n * Fetch the current grade for a user.\n *\n * @param {String} component\n * @param {Number} context\n * @param {String} itemname\n * @param {Number} userId\n * @param {Element} rootNode\n * @return {Object}\n */\nexport const fetchCurrentGrade = (...args) => fetchGrade('point')(...args);\n\n/**\n * Store a new grade for a user.\n *\n * @param {String} component\n * @param {Number} context\n * @param {String} itemname\n * @param {Number} userId\n * @param {Element} rootNode\n * @return {Object}\n */\nexport const storeCurrentGrade = async(component, context, itemname, userId, rootNode) => {\n const form = rootNode.querySelector('form');\n const grade = form.querySelector('input[name=\"grade\"]');\n\n if (!grade.checkValidity() || !grade.value.trim()) {\n return invalidResult;\n }\n\n if (compareData(form) === true) {\n return await saveGrade('point')(component, context, itemname, userId, jQuery(form).serialize());\n } else {\n return '';\n }\n};\n"],"file":"point.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../../src/grades/grader/gradingpanel/point.js"],"names":["fetchCurrentGrade","storeCurrentGrade","component","context","itemname","userId","notifyUser","rootNode","form","querySelector","grade","checkValidity","value","trim","invalidResult","serialize"],"mappings":"8RA2BA,uD,mVAaiC,QAApBA,CAAAA,iBAAoB,SAAa,iBAAW,OAAX,yBAAb,C,CAc1B,GAAMC,CAAAA,CAAiB,4CAAG,WAAMC,CAAN,CAAiBC,CAAjB,CAA0BC,CAA1B,CAAoCC,CAApC,CAA4CC,CAA5C,CAAwDC,CAAxD,2FACvBC,CADuB,CAChBD,CAAQ,CAACE,aAAT,CAAuB,MAAvB,CADgB,CAEvBC,CAFuB,CAEfF,CAAI,CAACC,aAAL,CAAmB,uBAAnB,CAFe,MAIzB,CAACC,CAAK,CAACC,aAAN,EAAD,EAA0B,CAACD,CAAK,CAACE,KAAN,CAAYC,IAAZ,EAJF,2CAKlBC,eALkB,cAQzB,uBAAYN,CAAZ,CARyB,kCASZ,gBAAU,OAAV,EAAmBN,CAAnB,CAA8BC,CAA9B,CAAuCC,CAAvC,CAAiDC,CAAjD,CAAyDC,CAAzD,CAAqE,cAAOE,CAAP,EAAaO,SAAb,EAArE,CATY,0EAWlB,EAXkB,2CAAH,uDAAvB,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Grading panel for simple direct grading.\n *\n * @module core_grades/grades/grader/gradingpanel/point\n * @package core_grades\n * @copyright 2019 Andrew Nicols \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {saveGrade, fetchGrade} from './repository';\nimport {compareData} from 'core_grades/grades/grader/gradingpanel/comparison';\n// Note: We use jQuery.serializer here until we can rewrite Ajax to use XHR.send()\nimport jQuery from 'jquery';\nimport {invalidResult} from './normalise';\n\n/**\n * Fetch the current grade for a user.\n *\n * @param {String} component\n * @param {Number} context\n * @param {String} itemname\n * @param {Number} userId\n * @param {Element} rootNode\n * @return {Object}\n */\nexport const fetchCurrentGrade = (...args) => fetchGrade('point')(...args);\n\n/**\n * Store a new grade for a user.\n *\n * @param {String} component\n * @param {Number} context\n * @param {String} itemname\n * @param {Number} userId\n * @param {Boolean} notifyUser\n * @param {Element} rootNode\n *\n * @return {Object}\n */\nexport const storeCurrentGrade = async(component, context, itemname, userId, notifyUser, rootNode) => {\n const form = rootNode.querySelector('form');\n const grade = form.querySelector('input[name=\"grade\"]');\n\n if (!grade.checkValidity() || !grade.value.trim()) {\n return invalidResult;\n }\n\n if (compareData(form) === true) {\n return await saveGrade('point')(component, context, itemname, userId, notifyUser, jQuery(form).serialize());\n } else {\n return '';\n }\n};\n"],"file":"point.min.js"} \ No newline at end of file diff --git a/grade/amd/build/grades/grader/gradingpanel/repository.min.js b/grade/amd/build/grades/grader/gradingpanel/repository.min.js index 34faeffbf3b..7e4a27b9adc 100644 --- a/grade/amd/build/grades/grader/gradingpanel/repository.min.js +++ b/grade/amd/build/grades/grader/gradingpanel/repository.min.js @@ -1,2 +1,2 @@ -define ("core_grades/grades/grader/gradingpanel/repository",["exports","core/ajax","./normalise"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.saveGrade=a.fetchGrade=void 0;function d(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function e(a){return function(){var b=this,c=arguments;return new Promise(function(e,f){var i=a.apply(b,c);function g(a){d(i,e,f,g,h,"next",a)}function h(a){d(i,e,f,g,h,"throw",a)}g(void 0)})}}a.fetchGrade=function fetchGrade(a){return function(c,d,e,f){return(0,b.call)([{methodname:"core_grades_grader_gradingpanel_".concat(a,"_fetch"),args:{component:c,contextid:d,itemname:e,gradeduserid:f}}])[0]}};a.saveGrade=function saveGrade(a){return function(){var d=e(regeneratorRuntime.mark(function d(e,f,g,h,i){return regeneratorRuntime.wrap(function(d){while(1){switch(d.prev=d.next){case 0:d.t0=c.normaliseResult;d.next=3;return(0,b.call)([{methodname:"core_grades_grader_gradingpanel_".concat(a,"_store"),args:{component:e,contextid:f,itemname:g,gradeduserid:h,formdata:i}}])[0];case 3:d.t1=d.sent;return d.abrupt("return",(0,d.t0)(d.t1));case 5:case"end":return d.stop();}}},d)}));return function(){return d.apply(this,arguments)}}()}}); +define ("core_grades/grades/grader/gradingpanel/repository",["exports","core/ajax","./normalise"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.saveGrade=a.fetchGrade=void 0;function d(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function e(a){return function(){var b=this,c=arguments;return new Promise(function(e,f){var i=a.apply(b,c);function g(a){d(i,e,f,g,h,"next",a)}function h(a){d(i,e,f,g,h,"throw",a)}g(void 0)})}}a.fetchGrade=function fetchGrade(a){return function(c,d,e,f){return(0,b.call)([{methodname:"core_grades_grader_gradingpanel_".concat(a,"_fetch"),args:{component:c,contextid:d,itemname:e,gradeduserid:f}}])[0]}};a.saveGrade=function saveGrade(a){return function(){var d=e(regeneratorRuntime.mark(function d(e,f,g,h,i,j){return regeneratorRuntime.wrap(function(d){while(1){switch(d.prev=d.next){case 0:d.t0=c.normaliseResult;d.next=3;return(0,b.call)([{methodname:"core_grades_grader_gradingpanel_".concat(a,"_store"),args:{component:e,contextid:f,itemname:g,gradeduserid:h,notifyuser:i,formdata:j}}])[0];case 3:d.t1=d.sent;return d.abrupt("return",(0,d.t0)(d.t1));case 5:case"end":return d.stop();}}},d)}));return function(){return d.apply(this,arguments)}}()}}); //# sourceMappingURL=repository.min.js.map diff --git a/grade/amd/build/grades/grader/gradingpanel/repository.min.js.map b/grade/amd/build/grades/grader/gradingpanel/repository.min.js.map index 2fd79381669..5f95d18b0c0 100644 --- a/grade/amd/build/grades/grader/gradingpanel/repository.min.js.map +++ b/grade/amd/build/grades/grader/gradingpanel/repository.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../../src/grades/grader/gradingpanel/repository.js"],"names":["fetchGrade","type","component","contextid","itemname","gradeduserid","methodname","args","saveGrade","formdata","normaliseResult"],"mappings":"4hBA0B0B,QAAbA,CAAAA,UAAa,CAAAC,CAAI,QAAI,UAACC,CAAD,CAAYC,CAAZ,CAAuBC,CAAvB,CAAiCC,CAAjC,CAAkD,CAChF,MAAO,WAAU,CAAC,CACdC,UAAU,2CAAqCL,CAArC,UADI,CAEdM,IAAI,CAAE,CACFL,SAAS,CAATA,CADE,CAEFC,SAAS,CAATA,CAFE,CAGFC,QAAQ,CAARA,CAHE,CAIFC,YAAY,CAAZA,CAJE,CAFQ,CAAD,CAAV,EAQH,CARG,CASV,CAV6B,C,aAYL,QAAZG,CAAAA,SAAY,CAAAP,CAAI,oDAAI,WAAMC,CAAN,CAAiBC,CAAjB,CAA4BC,CAA5B,CAAsCC,CAAtC,CAAoDI,CAApD,wFACtBC,iBADsB,gBACA,WAAU,CAAC,CACpCJ,UAAU,2CAAqCL,CAArC,UAD0B,CAEpCM,IAAI,CAAE,CACFL,SAAS,CAATA,CADE,CAEFC,SAAS,CAATA,CAFE,CAGFC,QAAQ,CAARA,CAHE,CAIFC,YAAY,CAAZA,CAJE,CAKFI,QAAQ,CAARA,CALE,CAF8B,CAAD,CAAV,EASzB,CATyB,CADA,qGAAJ,wD","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 * Repository for simple direct grading panel.\n *\n * @module core_grades/grades/grader/gradingpanel/repository\n * @package core_grades\n * @copyright 2019 Andrew Nicols \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport {call as fetchMany} from 'core/ajax';\nimport {normaliseResult} from './normalise';\n\nexport const fetchGrade = type => (component, contextid, itemname, gradeduserid) => {\n return fetchMany([{\n methodname: `core_grades_grader_gradingpanel_${type}_fetch`,\n args: {\n component,\n contextid,\n itemname,\n gradeduserid,\n },\n }])[0];\n};\n\nexport const saveGrade = type => async(component, contextid, itemname, gradeduserid, formdata) => {\n return normaliseResult(await fetchMany([{\n methodname: `core_grades_grader_gradingpanel_${type}_store`,\n args: {\n component,\n contextid,\n itemname,\n gradeduserid,\n formdata,\n },\n }])[0]);\n};\n"],"file":"repository.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../../src/grades/grader/gradingpanel/repository.js"],"names":["fetchGrade","type","component","contextid","itemname","gradeduserid","methodname","args","saveGrade","notifyUser","formdata","normaliseResult","notifyuser"],"mappings":"4hBA0B0B,QAAbA,CAAAA,UAAa,CAAAC,CAAI,QAAI,UAACC,CAAD,CAAYC,CAAZ,CAAuBC,CAAvB,CAAiCC,CAAjC,CAAkD,CAChF,MAAO,WAAU,CAAC,CACdC,UAAU,2CAAqCL,CAArC,UADI,CAEdM,IAAI,CAAE,CACFL,SAAS,CAATA,CADE,CAEFC,SAAS,CAATA,CAFE,CAGFC,QAAQ,CAARA,CAHE,CAIFC,YAAY,CAAZA,CAJE,CAFQ,CAAD,CAAV,EAQH,CARG,CASV,CAV6B,C,aAYL,QAAZG,CAAAA,SAAY,CAAAP,CAAI,oDAAI,WAAMC,CAAN,CAAiBC,CAAjB,CAA4BC,CAA5B,CAAsCC,CAAtC,CAAoDI,CAApD,CAAgEC,CAAhE,wFACtBC,iBADsB,gBACA,WAAU,CAAC,CACpCL,UAAU,2CAAqCL,CAArC,UAD0B,CAEpCM,IAAI,CAAE,CACFL,SAAS,CAATA,CADE,CAEFC,SAAS,CAATA,CAFE,CAGFC,QAAQ,CAARA,CAHE,CAIFC,YAAY,CAAZA,CAJE,CAKFO,UAAU,CAAEH,CALV,CAMFC,QAAQ,CAARA,CANE,CAF8B,CAAD,CAAV,EAUzB,CAVyB,CADA,qGAAJ,wD","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 * Repository for simple direct grading panel.\n *\n * @module core_grades/grades/grader/gradingpanel/repository\n * @package core_grades\n * @copyright 2019 Andrew Nicols \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport {call as fetchMany} from 'core/ajax';\nimport {normaliseResult} from './normalise';\n\nexport const fetchGrade = type => (component, contextid, itemname, gradeduserid) => {\n return fetchMany([{\n methodname: `core_grades_grader_gradingpanel_${type}_fetch`,\n args: {\n component,\n contextid,\n itemname,\n gradeduserid,\n },\n }])[0];\n};\n\nexport const saveGrade = type => async(component, contextid, itemname, gradeduserid, notifyUser, formdata) => {\n return normaliseResult(await fetchMany([{\n methodname: `core_grades_grader_gradingpanel_${type}_store`,\n args: {\n component,\n contextid,\n itemname,\n gradeduserid,\n notifyuser: notifyUser,\n formdata,\n },\n }])[0]);\n};\n"],"file":"repository.min.js"} \ No newline at end of file diff --git a/grade/amd/build/grades/grader/gradingpanel/scale.min.js b/grade/amd/build/grades/grader/gradingpanel/scale.min.js index 3c3b6f2a84b..f491b64e11f 100644 --- a/grade/amd/build/grades/grader/gradingpanel/scale.min.js +++ b/grade/amd/build/grades/grader/gradingpanel/scale.min.js @@ -1,2 +1,2 @@ -define ("core_grades/grades/grader/gradingpanel/scale",["exports","./repository","core_grades/grades/grader/gradingpanel/comparison","jquery","./normalise"],function(a,b,c,d,e){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.storeCurrentGrade=a.fetchCurrentGrade=void 0;d=function(a){return a&&a.__esModule?a:{default:a}}(d);a.fetchCurrentGrade=function fetchCurrentGrade(){return(0,b.fetchGrade)("scale").apply(void 0,arguments)};var f=function(a,f,g,h,i){var j=i.querySelector("form"),k=j.querySelector("select[name=\"grade\"]");if(!k.checkValidity()||!k.value.trim()){return e.invalidResult}if(!0===(0,c.compareData)(j)){return(0,b.saveGrade)("scale")(a,f,g,h,(0,d.default)(j).serialize())}else{return""}};a.storeCurrentGrade=f}); +define ("core_grades/grades/grader/gradingpanel/scale",["exports","./repository","core_grades/grades/grader/gradingpanel/comparison","jquery","./normalise"],function(a,b,c,d,e){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.storeCurrentGrade=a.fetchCurrentGrade=void 0;d=function(a){return a&&a.__esModule?a:{default:a}}(d);a.fetchCurrentGrade=function fetchCurrentGrade(){return(0,b.fetchGrade)("scale").apply(void 0,arguments)};var f=function(a,f,g,h,i,j){var k=j.querySelector("form"),l=k.querySelector("select[name=\"grade\"]");if(!l.checkValidity()||!l.value.trim()){return e.invalidResult}if(!0===(0,c.compareData)(k)){return(0,b.saveGrade)("scale")(a,f,g,h,i,(0,d.default)(k).serialize())}else{return""}};a.storeCurrentGrade=f}); //# sourceMappingURL=scale.min.js.map diff --git a/grade/amd/build/grades/grader/gradingpanel/scale.min.js.map b/grade/amd/build/grades/grader/gradingpanel/scale.min.js.map index e79a065fa74..b64fe517843 100644 --- a/grade/amd/build/grades/grader/gradingpanel/scale.min.js.map +++ b/grade/amd/build/grades/grader/gradingpanel/scale.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../../src/grades/grader/gradingpanel/scale.js"],"names":["fetchCurrentGrade","storeCurrentGrade","component","context","itemname","userId","rootNode","form","querySelector","grade","checkValidity","value","trim","invalidResult","serialize"],"mappings":"8RA2BA,uD,oBAGiC,QAApBA,CAAAA,iBAAoB,SAAa,iBAAW,OAAX,yBAAb,C,CAE1B,GAAMC,CAAAA,CAAiB,CAAG,SAACC,CAAD,CAAYC,CAAZ,CAAqBC,CAArB,CAA+BC,CAA/B,CAAuCC,CAAvC,CAAoD,IAC3EC,CAAAA,CAAI,CAAGD,CAAQ,CAACE,aAAT,CAAuB,MAAvB,CADoE,CAE3EC,CAAK,CAAGF,CAAI,CAACC,aAAL,CAAmB,wBAAnB,CAFmE,CAIjF,GAAI,CAACC,CAAK,CAACC,aAAN,EAAD,EAA0B,CAACD,CAAK,CAACE,KAAN,CAAYC,IAAZ,EAA/B,CAAmD,CAC/C,MAAOC,gBACV,CAED,GAAI,uBAAYN,CAAZ,CAAJ,CAAgC,CAC5B,MAAO,gBAAU,OAAV,EAAmBL,CAAnB,CAA8BC,CAA9B,CAAuCC,CAAvC,CAAiDC,CAAjD,CAAyD,cAAOE,CAAP,EAAaO,SAAb,EAAzD,CACV,CAFD,IAEO,CACH,MAAO,EACV,CACJ,CAbM,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Grading panel for simple direct grading.\n *\n * @module core_grades/grades/grader/gradingpanel/scale\n * @package core_grades\n * @copyright 2019 Andrew Nicols \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {saveGrade, fetchGrade} from './repository';\nimport {compareData} from 'core_grades/grades/grader/gradingpanel/comparison';\n// Note: We use jQuery.serializer here until we can rewrite Ajax to use XHR.send()\nimport jQuery from 'jquery';\nimport {invalidResult} from './normalise';\n\nexport const fetchCurrentGrade = (...args) => fetchGrade('scale')(...args);\n\nexport const storeCurrentGrade = (component, context, itemname, userId, rootNode) => {\n const form = rootNode.querySelector('form');\n const grade = form.querySelector('select[name=\"grade\"]');\n\n if (!grade.checkValidity() || !grade.value.trim()) {\n return invalidResult;\n }\n\n if (compareData(form) === true) {\n return saveGrade('scale')(component, context, itemname, userId, jQuery(form).serialize());\n } else {\n return '';\n }\n};\n"],"file":"scale.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../../src/grades/grader/gradingpanel/scale.js"],"names":["fetchCurrentGrade","storeCurrentGrade","component","context","itemname","userId","notifyUser","rootNode","form","querySelector","grade","checkValidity","value","trim","invalidResult","serialize"],"mappings":"8RA2BA,uD,oBAGiC,QAApBA,CAAAA,iBAAoB,SAAa,iBAAW,OAAX,yBAAb,C,CAE1B,GAAMC,CAAAA,CAAiB,CAAG,SAACC,CAAD,CAAYC,CAAZ,CAAqBC,CAArB,CAA+BC,CAA/B,CAAuCC,CAAvC,CAAmDC,CAAnD,CAAgE,IACvFC,CAAAA,CAAI,CAAGD,CAAQ,CAACE,aAAT,CAAuB,MAAvB,CADgF,CAEvFC,CAAK,CAAGF,CAAI,CAACC,aAAL,CAAmB,wBAAnB,CAF+E,CAI7F,GAAI,CAACC,CAAK,CAACC,aAAN,EAAD,EAA0B,CAACD,CAAK,CAACE,KAAN,CAAYC,IAAZ,EAA/B,CAAmD,CAC/C,MAAOC,gBACV,CAED,GAAI,uBAAYN,CAAZ,CAAJ,CAAgC,CAC5B,MAAO,gBAAU,OAAV,EAAmBN,CAAnB,CAA8BC,CAA9B,CAAuCC,CAAvC,CAAiDC,CAAjD,CAAyDC,CAAzD,CAAqE,cAAOE,CAAP,EAAaO,SAAb,EAArE,CACV,CAFD,IAEO,CACH,MAAO,EACV,CACJ,CAbM,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Grading panel for simple direct grading.\n *\n * @module core_grades/grades/grader/gradingpanel/scale\n * @package core_grades\n * @copyright 2019 Andrew Nicols \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {saveGrade, fetchGrade} from './repository';\nimport {compareData} from 'core_grades/grades/grader/gradingpanel/comparison';\n// Note: We use jQuery.serializer here until we can rewrite Ajax to use XHR.send()\nimport jQuery from 'jquery';\nimport {invalidResult} from './normalise';\n\nexport const fetchCurrentGrade = (...args) => fetchGrade('scale')(...args);\n\nexport const storeCurrentGrade = (component, context, itemname, userId, notifyUser, rootNode) => {\n const form = rootNode.querySelector('form');\n const grade = form.querySelector('select[name=\"grade\"]');\n\n if (!grade.checkValidity() || !grade.value.trim()) {\n return invalidResult;\n }\n\n if (compareData(form) === true) {\n return saveGrade('scale')(component, context, itemname, userId, notifyUser, jQuery(form).serialize());\n } else {\n return '';\n }\n};\n"],"file":"scale.min.js"} \ No newline at end of file diff --git a/grade/amd/src/grades/grader/gradingpanel/point.js b/grade/amd/src/grades/grader/gradingpanel/point.js index 5fc6fa1f660..0c42c3f4a3d 100644 --- a/grade/amd/src/grades/grader/gradingpanel/point.js +++ b/grade/amd/src/grades/grader/gradingpanel/point.js @@ -47,10 +47,12 @@ export const fetchCurrentGrade = (...args) => fetchGrade('point')(...args); * @param {Number} context * @param {String} itemname * @param {Number} userId + * @param {Boolean} notifyUser * @param {Element} rootNode + * * @return {Object} */ -export const storeCurrentGrade = async(component, context, itemname, userId, rootNode) => { +export const storeCurrentGrade = async(component, context, itemname, userId, notifyUser, rootNode) => { const form = rootNode.querySelector('form'); const grade = form.querySelector('input[name="grade"]'); @@ -59,7 +61,7 @@ export const storeCurrentGrade = async(component, context, itemname, userId, roo } if (compareData(form) === true) { - return await saveGrade('point')(component, context, itemname, userId, jQuery(form).serialize()); + return await saveGrade('point')(component, context, itemname, userId, notifyUser, jQuery(form).serialize()); } else { return ''; } diff --git a/grade/amd/src/grades/grader/gradingpanel/repository.js b/grade/amd/src/grades/grader/gradingpanel/repository.js index c7a51bdfb49..c0ad026decb 100644 --- a/grade/amd/src/grades/grader/gradingpanel/repository.js +++ b/grade/amd/src/grades/grader/gradingpanel/repository.js @@ -36,7 +36,7 @@ export const fetchGrade = type => (component, contextid, itemname, gradeduserid) }])[0]; }; -export const saveGrade = type => async(component, contextid, itemname, gradeduserid, formdata) => { +export const saveGrade = type => async(component, contextid, itemname, gradeduserid, notifyUser, formdata) => { return normaliseResult(await fetchMany([{ methodname: `core_grades_grader_gradingpanel_${type}_store`, args: { @@ -44,6 +44,7 @@ export const saveGrade = type => async(component, contextid, itemname, gradeduse contextid, itemname, gradeduserid, + notifyuser: notifyUser, formdata, }, }])[0]); diff --git a/grade/amd/src/grades/grader/gradingpanel/scale.js b/grade/amd/src/grades/grader/gradingpanel/scale.js index 543eab1e94e..278047b491e 100644 --- a/grade/amd/src/grades/grader/gradingpanel/scale.js +++ b/grade/amd/src/grades/grader/gradingpanel/scale.js @@ -30,7 +30,7 @@ import {invalidResult} from './normalise'; export const fetchCurrentGrade = (...args) => fetchGrade('scale')(...args); -export const storeCurrentGrade = (component, context, itemname, userId, rootNode) => { +export const storeCurrentGrade = (component, context, itemname, userId, notifyUser, rootNode) => { const form = rootNode.querySelector('form'); const grade = form.querySelector('select[name="grade"]'); @@ -39,7 +39,7 @@ export const storeCurrentGrade = (component, context, itemname, userId, rootNode } if (compareData(form) === true) { - return saveGrade('scale')(component, context, itemname, userId, jQuery(form).serialize()); + return saveGrade('scale')(component, context, itemname, userId, notifyUser, jQuery(form).serialize()); } else { return ''; } diff --git a/grade/classes/component_gradeitem.php b/grade/classes/component_gradeitem.php index 9e7ba921078..0f5ec629e89 100644 --- a/grade/classes/component_gradeitem.php +++ b/grade/classes/component_gradeitem.php @@ -483,4 +483,29 @@ abstract class component_gradeitem { return $gradinginstance; } + + /** + * Sends a notification about the item being graded for the student. + * + * @param stdClass $gradeduser The user being graded + * @param stdClass $grader The user who is grading + */ + public function send_student_notification(stdClass $gradeduser, stdClass $grader): void { + $contextname = $this->context->get_context_name(); + $eventdata = new \core\message\message(); + $eventdata->courseid = $this->context->get_course_context()->instanceid; + $eventdata->component = 'moodle'; + $eventdata->name = 'gradenotifications'; + $eventdata->userfrom = $grader; + $eventdata->userto = $gradeduser; + $eventdata->subject = get_string('gradenotificationsubject', 'grades'); + $eventdata->fullmessage = get_string('gradenotificationmessage', 'grades', $contextname); + $eventdata->contexturl = $this->context->get_url(); + $eventdata->contexturlname = $contextname; + $eventdata->fullmessageformat = FORMAT_HTML; + $eventdata->fullmessagehtml = ''; + $eventdata->smallmessage = ''; + $eventdata->notification = 1; + message_send($eventdata); + } } diff --git a/grade/classes/grades/grader/gradingpanel/point/external/store.php b/grade/classes/grades/grader/gradingpanel/point/external/store.php index 37f95c6a342..38446ae791a 100644 --- a/grade/classes/grades/grader/gradingpanel/point/external/store.php +++ b/grade/classes/grades/grader/gradingpanel/point/external/store.php @@ -77,6 +77,12 @@ class store extends external_api { 'The ID of the user show', VALUE_REQUIRED ), + 'notifyuser' => new external_value( + PARAM_BOOL, + 'Wheteher to notify the user or not', + VALUE_DEFAULT, + false + ), 'formdata' => new external_value( PARAM_RAW, 'The serialised form data representing the grade', @@ -92,6 +98,7 @@ class store extends external_api { * @param int $contextid * @param string $itemname * @param int $gradeduserid + * @param bool $notifyuser * @param string $formdata * @return array * @throws \dml_exception @@ -101,7 +108,8 @@ class store extends external_api { * @throws moodle_exception * @since Moodle 3.8 */ - public static function execute(string $component, int $contextid, string $itemname, int $gradeduserid, string $formdata): array { + public static function execute(string $component, int $contextid, string $itemname, int $gradeduserid, + bool $notifyuser, string $formdata): array { global $USER; [ @@ -151,8 +159,14 @@ class store extends external_api { // Grade. $gradeitem->store_grade_from_formdata($gradeduser, $USER, (object) $data); - $hasgrade = $gradeitem->user_has_grade($gradeduser); + + // Notify. + if ($notifyuser) { + // Send notification. + $gradeitem->send_student_notification($gradeduser, $USER); + } + // Fetch the updated grade back out. $grade = $gradeitem->get_grade_for_user($gradeduser, $USER); diff --git a/grade/classes/grades/grader/gradingpanel/scale/external/store.php b/grade/classes/grades/grader/gradingpanel/scale/external/store.php index 6c7fa5b8427..88c185d824c 100644 --- a/grade/classes/grades/grader/gradingpanel/scale/external/store.php +++ b/grade/classes/grades/grader/gradingpanel/scale/external/store.php @@ -82,6 +82,12 @@ class store extends external_api { 'The serialised form data representing the grade', VALUE_REQUIRED ), + 'notifyuser' => new external_value( + PARAM_BOOL, + 'Wheteher to notify the user or not', + VALUE_DEFAULT, + false + ), ]); } @@ -93,15 +99,14 @@ class store extends external_api { * @param string $itemname * @param int $gradeduserid * @param string $formdata + * @param bool $notifyuser * @return array - * @throws \dml_exception - * @throws \invalid_parameter_exception - * @throws \restricted_context_exception * @throws coding_exception * @throws moodle_exception * @since Moodle 3.8 */ - public static function execute(string $component, int $contextid, string $itemname, int $gradeduserid, string $formdata): array { + public static function execute(string $component, int $contextid, string $itemname, int $gradeduserid, + bool $notifyuser, string $formdata): array { global $USER; [ @@ -152,6 +157,12 @@ class store extends external_api { // Grade. $gradeitem->store_grade_from_formdata($gradeduser, $USER, (object) $data); + // Notify. + if ($notifyuser) { + // Send notification. + $gradeitem->send_student_notification($gradeduser, $USER); + } + return fetch::get_fetch_data($gradeitem, $gradeduser); } diff --git a/grade/grading/form/guide/amd/build/grades/grader/gradingpanel.min.js b/grade/grading/form/guide/amd/build/grades/grader/gradingpanel.min.js index 7fbdf882b64..f156e91bcaf 100644 --- a/grade/grading/form/guide/amd/build/grades/grader/gradingpanel.min.js +++ b/grade/grading/form/guide/amd/build/grades/grader/gradingpanel.min.js @@ -1,2 +1,2 @@ -define ("gradingform_guide/grades/grader/gradingpanel",["exports","core/ajax","core_grades/grades/grader/gradingpanel/normalise","core_grades/grades/grader/gradingpanel/comparison","jquery"],function(a,b,c,d,e){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.storeCurrentGrade=a.fetchCurrentGrade=void 0;e=function(a){return a&&a.__esModule?a:{default:a}}(e);function f(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function g(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var i=a.apply(b,c);function g(a){f(i,d,e,g,h,"next",a)}function h(a){f(i,d,e,g,h,"throw",a)}g(void 0)})}}a.fetchCurrentGrade=function fetchCurrentGrade(a,c,d,e){return(0,b.call)([{methodname:"gradingform_guide_grader_gradingpanel_fetch",args:{component:a,contextid:c,itemname:d,gradeduserid:e}}])[0]};var h=function(){var a=g(regeneratorRuntime.mark(function a(f,g,h,i,j){var k;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:k=j.querySelector("form");if(!(!0===(0,d.compareData)(k))){a.next=9;break}a.t0=c.normaliseResult;a.next=5;return(0,b.call)([{methodname:"gradingform_guide_grader_gradingpanel_store",args:{component:f,contextid:g,itemname:h,gradeduserid:i,formdata:(0,e.default)(k).serialize()}}])[0];case 5:a.t1=a.sent;return a.abrupt("return",(0,a.t0)(a.t1));case 9:return a.abrupt("return","");case 10:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}();a.storeCurrentGrade=h}); +define ("gradingform_guide/grades/grader/gradingpanel",["exports","core/ajax","core_grades/grades/grader/gradingpanel/normalise","core_grades/grades/grader/gradingpanel/comparison","jquery"],function(a,b,c,d,e){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.storeCurrentGrade=a.fetchCurrentGrade=void 0;e=function(a){return a&&a.__esModule?a:{default:a}}(e);function f(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function g(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var i=a.apply(b,c);function g(a){f(i,d,e,g,h,"next",a)}function h(a){f(i,d,e,g,h,"throw",a)}g(void 0)})}}a.fetchCurrentGrade=function fetchCurrentGrade(a,c,d,e){return(0,b.call)([{methodname:"gradingform_guide_grader_gradingpanel_fetch",args:{component:a,contextid:c,itemname:d,gradeduserid:e}}])[0]};var h=function(){var a=g(regeneratorRuntime.mark(function a(f,g,h,i,j,k){var l;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:l=k.querySelector("form");if(!(!0===(0,d.compareData)(l))){a.next=9;break}a.t0=c.normaliseResult;a.next=5;return(0,b.call)([{methodname:"gradingform_guide_grader_gradingpanel_store",args:{component:f,contextid:g,itemname:h,gradeduserid:i,notifyuser:j,formdata:(0,e.default)(l).serialize()}}])[0];case 5:a.t1=a.sent;return a.abrupt("return",(0,a.t0)(a.t1));case 9:return a.abrupt("return","");case 10:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}();a.storeCurrentGrade=h}); //# sourceMappingURL=gradingpanel.min.js.map diff --git a/grade/grading/form/guide/amd/build/grades/grader/gradingpanel.min.js.map b/grade/grading/form/guide/amd/build/grades/grader/gradingpanel.min.js.map index c3abc7b66eb..cac4be33469 100644 --- a/grade/grading/form/guide/amd/build/grades/grader/gradingpanel.min.js.map +++ b/grade/grading/form/guide/amd/build/grades/grader/gradingpanel.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/grades/grader/gradingpanel.js"],"names":["fetchCurrentGrade","component","contextid","itemname","gradeduserid","methodname","args","storeCurrentGrade","rootNode","form","querySelector","normaliseResult","formdata","serialize"],"mappings":"gUA6BA,uD,mVAYiC,QAApBA,CAAAA,iBAAoB,CAACC,CAAD,CAAYC,CAAZ,CAAuBC,CAAvB,CAAiCC,CAAjC,CAAkD,CAC/E,MAAO,WAAU,CAAC,CACdC,UAAU,8CADI,CAEdC,IAAI,CAAE,CACFL,SAAS,CAATA,CADE,CAEFC,SAAS,CAATA,CAFE,CAGFC,QAAQ,CAARA,CAHE,CAIFC,YAAY,CAAZA,CAJE,CAFQ,CAAD,CAAV,EAQH,CARG,CASV,C,CAaM,GAAMG,CAAAA,CAAiB,4CAAG,WAAMN,CAAN,CAAiBC,CAAjB,CAA4BC,CAA5B,CAAsCC,CAAtC,CAAoDI,CAApD,yFACvBC,CADuB,CAChBD,CAAQ,CAACE,aAAT,CAAuB,MAAvB,CADgB,MAGzB,uBAAYD,CAAZ,CAHyB,uBAIlBE,iBAJkB,gBAII,WAAU,CAAC,CACpCN,UAAU,8CAD0B,CAEpCC,IAAI,CAAE,CACFL,SAAS,CAATA,CADE,CAEFC,SAAS,CAATA,CAFE,CAGFC,QAAQ,CAARA,CAHE,CAIFC,YAAY,CAAZA,CAJE,CAKFQ,QAAQ,CAAE,cAAOH,CAAP,EAAaI,SAAb,EALR,CAF8B,CAAD,CAAV,EASzB,CATyB,CAJJ,6FAelB,EAfkB,2CAAH,uDAAvB,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Grading panel for gradingform_guide.\n *\n * @module gradingform_guide/grades/grader/gradingpanel\n * @package gradingform_guide\n * @copyright 2019 Andrew Nicols \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {call as fetchMany} from 'core/ajax';\nimport {normaliseResult} from 'core_grades/grades/grader/gradingpanel/normalise';\nimport {compareData} from 'core_grades/grades/grader/gradingpanel/comparison';\n\n// Note: We use jQuery.serializer here until we can rewrite Ajax to use XHR.send()\nimport jQuery from 'jquery';\n\n/**\n * For a given component, contextid, itemname & gradeduserid we can fetch the currently assigned grade.\n *\n * @param {String} component\n * @param {Number} contextid\n * @param {String} itemname\n * @param {Number} gradeduserid\n *\n * @returns {Promise}\n */\nexport const fetchCurrentGrade = (component, contextid, itemname, gradeduserid) => {\n return fetchMany([{\n methodname: `gradingform_guide_grader_gradingpanel_fetch`,\n args: {\n component,\n contextid,\n itemname,\n gradeduserid,\n },\n }])[0];\n};\n\n/**\n * For a given component, contextid, itemname & gradeduserid we can store the currently assigned grade in a given form.\n *\n * @param {String} component\n * @param {Number} contextid\n * @param {String} itemname\n * @param {Number} gradeduserid\n * @param {HTMLElement} rootNode\n *\n * @returns {Promise}\n */\nexport const storeCurrentGrade = async(component, contextid, itemname, gradeduserid, rootNode) => {\n const form = rootNode.querySelector('form');\n\n if (compareData(form) === true) {\n return normaliseResult(await fetchMany([{\n methodname: `gradingform_guide_grader_gradingpanel_store`,\n args: {\n component,\n contextid,\n itemname,\n gradeduserid,\n formdata: jQuery(form).serialize(),\n },\n }])[0]);\n } else {\n return '';\n }\n};\n"],"file":"gradingpanel.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/grades/grader/gradingpanel.js"],"names":["fetchCurrentGrade","component","contextid","itemname","gradeduserid","methodname","args","storeCurrentGrade","notifyUser","rootNode","form","querySelector","normaliseResult","notifyuser","formdata","serialize"],"mappings":"gUA6BA,uD,mVAYiC,QAApBA,CAAAA,iBAAoB,CAACC,CAAD,CAAYC,CAAZ,CAAuBC,CAAvB,CAAiCC,CAAjC,CAAkD,CAC/E,MAAO,WAAU,CAAC,CACdC,UAAU,8CADI,CAEdC,IAAI,CAAE,CACFL,SAAS,CAATA,CADE,CAEFC,SAAS,CAATA,CAFE,CAGFC,QAAQ,CAARA,CAHE,CAIFC,YAAY,CAAZA,CAJE,CAFQ,CAAD,CAAV,EAQH,CARG,CASV,C,CAcM,GAAMG,CAAAA,CAAiB,4CAAG,WAAMN,CAAN,CAAiBC,CAAjB,CAA4BC,CAA5B,CAAsCC,CAAtC,CAAoDI,CAApD,CAAgEC,CAAhE,yFACvBC,CADuB,CAChBD,CAAQ,CAACE,aAAT,CAAuB,MAAvB,CADgB,MAGzB,uBAAYD,CAAZ,CAHyB,uBAIlBE,iBAJkB,gBAII,WAAU,CAAC,CACpCP,UAAU,8CAD0B,CAEpCC,IAAI,CAAE,CACFL,SAAS,CAATA,CADE,CAEFC,SAAS,CAATA,CAFE,CAGFC,QAAQ,CAARA,CAHE,CAIFC,YAAY,CAAZA,CAJE,CAKFS,UAAU,CAAEL,CALV,CAMFM,QAAQ,CAAE,cAAOJ,CAAP,EAAaK,SAAb,EANR,CAF8B,CAAD,CAAV,EAUzB,CAVyB,CAJJ,6FAgBlB,EAhBkB,2CAAH,uDAAvB,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Grading panel for gradingform_guide.\n *\n * @module gradingform_guide/grades/grader/gradingpanel\n * @package gradingform_guide\n * @copyright 2019 Andrew Nicols \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {call as fetchMany} from 'core/ajax';\nimport {normaliseResult} from 'core_grades/grades/grader/gradingpanel/normalise';\nimport {compareData} from 'core_grades/grades/grader/gradingpanel/comparison';\n\n// Note: We use jQuery.serializer here until we can rewrite Ajax to use XHR.send()\nimport jQuery from 'jquery';\n\n/**\n * For a given component, contextid, itemname & gradeduserid we can fetch the currently assigned grade.\n *\n * @param {String} component\n * @param {Number} contextid\n * @param {String} itemname\n * @param {Number} gradeduserid\n *\n * @returns {Promise}\n */\nexport const fetchCurrentGrade = (component, contextid, itemname, gradeduserid) => {\n return fetchMany([{\n methodname: `gradingform_guide_grader_gradingpanel_fetch`,\n args: {\n component,\n contextid,\n itemname,\n gradeduserid,\n },\n }])[0];\n};\n\n/**\n * For a given component, contextid, itemname & gradeduserid we can store the currently assigned grade in a given form.\n *\n * @param {String} component\n * @param {Number} contextid\n * @param {String} itemname\n * @param {Number} gradeduserid\n * @param {Boolean} notifyUser\n * @param {HTMLElement} rootNode\n *\n * @returns {Promise}\n */\nexport const storeCurrentGrade = async(component, contextid, itemname, gradeduserid, notifyUser, rootNode) => {\n const form = rootNode.querySelector('form');\n\n if (compareData(form) === true) {\n return normaliseResult(await fetchMany([{\n methodname: `gradingform_guide_grader_gradingpanel_store`,\n args: {\n component,\n contextid,\n itemname,\n gradeduserid,\n notifyuser: notifyUser,\n formdata: jQuery(form).serialize(),\n },\n }])[0]);\n } else {\n return '';\n }\n};\n"],"file":"gradingpanel.min.js"} \ No newline at end of file diff --git a/grade/grading/form/guide/amd/src/grades/grader/gradingpanel.js b/grade/grading/form/guide/amd/src/grades/grader/gradingpanel.js index 287f1046a38..2233c5bcc55 100644 --- a/grade/grading/form/guide/amd/src/grades/grader/gradingpanel.js +++ b/grade/grading/form/guide/amd/src/grades/grader/gradingpanel.js @@ -58,11 +58,12 @@ export const fetchCurrentGrade = (component, contextid, itemname, gradeduserid) * @param {Number} contextid * @param {String} itemname * @param {Number} gradeduserid + * @param {Boolean} notifyUser * @param {HTMLElement} rootNode * * @returns {Promise} */ -export const storeCurrentGrade = async(component, contextid, itemname, gradeduserid, rootNode) => { +export const storeCurrentGrade = async(component, contextid, itemname, gradeduserid, notifyUser, rootNode) => { const form = rootNode.querySelector('form'); if (compareData(form) === true) { @@ -73,6 +74,7 @@ export const storeCurrentGrade = async(component, contextid, itemname, gradeduse contextid, itemname, gradeduserid, + notifyuser: notifyUser, formdata: jQuery(form).serialize(), }, }])[0]); diff --git a/grade/grading/form/guide/classes/grades/grader/gradingpanel/external/store.php b/grade/grading/form/guide/classes/grades/grader/gradingpanel/external/store.php index f1300e0e2ba..d5c4220435d 100644 --- a/grade/grading/form/guide/classes/grades/grader/gradingpanel/external/store.php +++ b/grade/grading/form/guide/classes/grades/grader/gradingpanel/external/store.php @@ -77,6 +77,12 @@ class store extends external_api { 'The ID of the user show', VALUE_REQUIRED ), + 'notifyuser' => new external_value( + PARAM_BOOL, + 'Wheteher to notify the user or not', + VALUE_DEFAULT, + false + ), 'formdata' => new external_value( PARAM_RAW, 'The serialised form data representing the grade', @@ -92,7 +98,9 @@ class store extends external_api { * @param int $contextid * @param string $itemname * @param int $gradeduserid + * @param bool $notifyuser * @param string $formdata + * * @return array * @throws \dml_exception * @throws \invalid_parameter_exception @@ -101,7 +109,8 @@ class store extends external_api { * @throws moodle_exception * @since Moodle 3.8 */ - public static function execute(string $component, int $contextid, string $itemname, int $gradeduserid, string $formdata): array { + public static function execute(string $component, int $contextid, string $itemname, int $gradeduserid, + bool $notifyuser, string $formdata): array { global $USER; [ @@ -154,6 +163,12 @@ class store extends external_api { // Grade. $gradeitem->store_grade_from_formdata($gradeduser, $USER, (object) $data); + // Notify. + if ($notifyuser) { + // Send notification. + $gradeitem->send_student_notification($gradeduser, $USER); + } + return fetch::get_fetch_data($gradeitem, $gradeduser); } diff --git a/grade/grading/form/guide/tests/grades_grader_gradingpanel_guide_external_store_test.php b/grade/grading/form/guide/tests/grades_grader_gradingpanel_guide_external_store_test.php index c55badf01b7..051b3119177 100644 --- a/grade/grading/form/guide/tests/grades_grader_gradingpanel_guide_external_store_test.php +++ b/grade/grading/form/guide/tests/grades_grader_gradingpanel_guide_external_store_test.php @@ -59,7 +59,7 @@ class store_test extends advanced_testcase { $this->expectException(coding_exception::class); $this->expectExceptionMessage("The 'foo' item is not valid for the 'mod_invalid' component"); - store::execute('mod_invalid', 1, 'foo', 2, 'formdata'); + store::execute('mod_invalid', 1, 'foo', 2, 'formdata', false); } /** @@ -72,7 +72,7 @@ class store_test extends advanced_testcase { $this->expectException(coding_exception::class); $this->expectExceptionMessage("The 'foo' item is not valid for the 'mod_forum' component"); - store::execute('mod_forum', 1, 'foo', 2, 'formdata'); + store::execute('mod_forum', 1, 'foo', 2, 'formdata', false); } /** @@ -93,7 +93,7 @@ class store_test extends advanced_testcase { $this->expectException(moodle_exception::class); $this->expectExceptionMessage("not configured for advanced grading with a marking guide"); - store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, 'formdata'); + store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, 'formdata', false); } /** @@ -112,7 +112,7 @@ class store_test extends advanced_testcase { $this->expectException(moodle_exception::class); $this->expectExceptionMessage("Grading is not enabled"); - store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, 'formdata'); + store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, 'formdata', false); } /** @@ -147,7 +147,7 @@ class store_test extends advanced_testcase { 'advancedgrading' => $submissiondata, ], '', '&'); - $result = store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, $formdata); + $result = store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, $formdata, false); $result = external_api::clean_returnvalue(store::execute_returns(), $result); $this->assertIsArray($result); diff --git a/grade/grading/form/rubric/amd/build/grades/grader/gradingpanel.min.js b/grade/grading/form/rubric/amd/build/grades/grader/gradingpanel.min.js index b4c76ecad85..ab57c1aecb0 100644 --- a/grade/grading/form/rubric/amd/build/grades/grader/gradingpanel.min.js +++ b/grade/grading/form/rubric/amd/build/grades/grader/gradingpanel.min.js @@ -1,2 +1,2 @@ -define ("gradingform_rubric/grades/grader/gradingpanel",["exports","core/ajax","core_grades/grades/grader/gradingpanel/normalise","core_grades/grades/grader/gradingpanel/comparison","jquery"],function(a,b,c,d,e){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.storeCurrentGrade=a.fetchCurrentGrade=void 0;e=function(a){return a&&a.__esModule?a:{default:a}}(e);function f(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function g(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var i=a.apply(b,c);function g(a){f(i,d,e,g,h,"next",a)}function h(a){f(i,d,e,g,h,"throw",a)}g(void 0)})}}a.fetchCurrentGrade=function fetchCurrentGrade(a,c,d,e){return(0,b.call)([{methodname:"gradingform_rubric_grader_gradingpanel_fetch",args:{component:a,contextid:c,itemname:d,gradeduserid:e}}])[0]};var h=function(){var a=g(regeneratorRuntime.mark(function a(f,g,h,i,j){var k;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:k=j.querySelector("form");if(!(!0===(0,d.compareData)(k))){a.next=9;break}a.t0=c.normaliseResult;a.next=5;return(0,b.call)([{methodname:"gradingform_rubric_grader_gradingpanel_store",args:{component:f,contextid:g,itemname:h,gradeduserid:i,formdata:(0,e.default)(k).serialize()}}])[0];case 5:a.t1=a.sent;return a.abrupt("return",(0,a.t0)(a.t1));case 9:return a.abrupt("return","");case 10:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}();a.storeCurrentGrade=h}); +define ("gradingform_rubric/grades/grader/gradingpanel",["exports","core/ajax","core_grades/grades/grader/gradingpanel/normalise","core_grades/grades/grader/gradingpanel/comparison","jquery"],function(a,b,c,d,e){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.storeCurrentGrade=a.fetchCurrentGrade=void 0;e=function(a){return a&&a.__esModule?a:{default:a}}(e);function f(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function g(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var i=a.apply(b,c);function g(a){f(i,d,e,g,h,"next",a)}function h(a){f(i,d,e,g,h,"throw",a)}g(void 0)})}}a.fetchCurrentGrade=function fetchCurrentGrade(a,c,d,e){return(0,b.call)([{methodname:"gradingform_rubric_grader_gradingpanel_fetch",args:{component:a,contextid:c,itemname:d,gradeduserid:e}}])[0]};var h=function(){var a=g(regeneratorRuntime.mark(function a(f,g,h,i,j,k){var l;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:l=k.querySelector("form");if(!(!0===(0,d.compareData)(l))){a.next=9;break}a.t0=c.normaliseResult;a.next=5;return(0,b.call)([{methodname:"gradingform_rubric_grader_gradingpanel_store",args:{component:f,contextid:g,itemname:h,gradeduserid:i,notifyuser:j,formdata:(0,e.default)(l).serialize()}}])[0];case 5:a.t1=a.sent;return a.abrupt("return",(0,a.t0)(a.t1));case 9:return a.abrupt("return","");case 10:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}();a.storeCurrentGrade=h}); //# sourceMappingURL=gradingpanel.min.js.map diff --git a/grade/grading/form/rubric/amd/build/grades/grader/gradingpanel.min.js.map b/grade/grading/form/rubric/amd/build/grades/grader/gradingpanel.min.js.map index 427dec3fad0..33ce01ccb0b 100644 --- a/grade/grading/form/rubric/amd/build/grades/grader/gradingpanel.min.js.map +++ b/grade/grading/form/rubric/amd/build/grades/grader/gradingpanel.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/grades/grader/gradingpanel.js"],"names":["fetchCurrentGrade","component","contextid","itemname","gradeduserid","methodname","args","storeCurrentGrade","rootNode","form","querySelector","normaliseResult","formdata","serialize"],"mappings":"iUA8BA,uD,mVAYiC,QAApBA,CAAAA,iBAAoB,CAACC,CAAD,CAAYC,CAAZ,CAAuBC,CAAvB,CAAiCC,CAAjC,CAAkD,CAC/E,MAAO,WAAU,CAAC,CACdC,UAAU,+CADI,CAEdC,IAAI,CAAE,CACFL,SAAS,CAATA,CADE,CAEFC,SAAS,CAATA,CAFE,CAGFC,QAAQ,CAARA,CAHE,CAIFC,YAAY,CAAZA,CAJE,CAFQ,CAAD,CAAV,EAQH,CARG,CASV,C,CAaM,GAAMG,CAAAA,CAAiB,4CAAG,WAAMN,CAAN,CAAiBC,CAAjB,CAA4BC,CAA5B,CAAsCC,CAAtC,CAAoDI,CAApD,yFACvBC,CADuB,CAChBD,CAAQ,CAACE,aAAT,CAAuB,MAAvB,CADgB,MAGzB,uBAAYD,CAAZ,CAHyB,uBAIlBE,iBAJkB,gBAII,WAAU,CAAC,CACpCN,UAAU,+CAD0B,CAEpCC,IAAI,CAAE,CACFL,SAAS,CAATA,CADE,CAEFC,SAAS,CAATA,CAFE,CAGFC,QAAQ,CAARA,CAHE,CAIFC,YAAY,CAAZA,CAJE,CAKFQ,QAAQ,CAAE,cAAOH,CAAP,EAAaI,SAAb,EALR,CAF8B,CAAD,CAAV,EASzB,CATyB,CAJJ,6FAelB,EAfkB,2CAAH,uDAAvB,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Grading panel for gradingform_rubric.\n *\n * @module gradingform_rubric/grades/grader/gradingpanel\n * @package gradingform_rubric\n * @copyright 2019 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {call as fetchMany} from 'core/ajax';\nimport {normaliseResult} from 'core_grades/grades/grader/gradingpanel/normalise';\nimport {compareData} from 'core_grades/grades/grader/gradingpanel/comparison';\n\n\n// Note: We use jQuery.serializer here until we can rewrite Ajax to use XHR.send()\nimport jQuery from 'jquery';\n\n/**\n * For a given component, contextid, itemname & gradeduserid we can fetch the currently assigned grade.\n *\n * @param {String} component\n * @param {Number} contextid\n * @param {String} itemname\n * @param {Number} gradeduserid\n *\n * @returns {Promise}\n */\nexport const fetchCurrentGrade = (component, contextid, itemname, gradeduserid) => {\n return fetchMany([{\n methodname: `gradingform_rubric_grader_gradingpanel_fetch`,\n args: {\n component,\n contextid,\n itemname,\n gradeduserid,\n },\n }])[0];\n};\n\n/**\n * For a given component, contextid, itemname & gradeduserid we can store the currently assigned grade in a given form.\n *\n * @param {String} component\n * @param {Number} contextid\n * @param {String} itemname\n * @param {Number} gradeduserid\n * @param {HTMLElement} rootNode\n *\n * @returns {Promise}\n */\nexport const storeCurrentGrade = async(component, contextid, itemname, gradeduserid, rootNode) => {\n const form = rootNode.querySelector('form');\n\n if (compareData(form) === true) {\n return normaliseResult(await fetchMany([{\n methodname: `gradingform_rubric_grader_gradingpanel_store`,\n args: {\n component,\n contextid,\n itemname,\n gradeduserid,\n formdata: jQuery(form).serialize(),\n },\n }])[0]);\n } else {\n return '';\n }\n};\n"],"file":"gradingpanel.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/grades/grader/gradingpanel.js"],"names":["fetchCurrentGrade","component","contextid","itemname","gradeduserid","methodname","args","storeCurrentGrade","notifyUser","rootNode","form","querySelector","normaliseResult","notifyuser","formdata","serialize"],"mappings":"iUA6BA,uD,mVAYiC,QAApBA,CAAAA,iBAAoB,CAACC,CAAD,CAAYC,CAAZ,CAAuBC,CAAvB,CAAiCC,CAAjC,CAAkD,CAC/E,MAAO,WAAU,CAAC,CACdC,UAAU,+CADI,CAEdC,IAAI,CAAE,CACFL,SAAS,CAATA,CADE,CAEFC,SAAS,CAATA,CAFE,CAGFC,QAAQ,CAARA,CAHE,CAIFC,YAAY,CAAZA,CAJE,CAFQ,CAAD,CAAV,EAQH,CARG,CASV,C,CAcM,GAAMG,CAAAA,CAAiB,4CAAG,WAAMN,CAAN,CAAiBC,CAAjB,CAA4BC,CAA5B,CAAsCC,CAAtC,CAAoDI,CAApD,CAAgEC,CAAhE,yFACvBC,CADuB,CAChBD,CAAQ,CAACE,aAAT,CAAuB,MAAvB,CADgB,MAGzB,uBAAYD,CAAZ,CAHyB,uBAIlBE,iBAJkB,gBAII,WAAU,CAAC,CACpCP,UAAU,+CAD0B,CAEpCC,IAAI,CAAE,CACFL,SAAS,CAATA,CADE,CAEFC,SAAS,CAATA,CAFE,CAGFC,QAAQ,CAARA,CAHE,CAIFC,YAAY,CAAZA,CAJE,CAKFS,UAAU,CAAEL,CALV,CAMFM,QAAQ,CAAE,cAAOJ,CAAP,EAAaK,SAAb,EANR,CAF8B,CAAD,CAAV,EAUzB,CAVyB,CAJJ,6FAgBlB,EAhBkB,2CAAH,uDAAvB,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Grading panel for gradingform_rubric.\n *\n * @module gradingform_rubric/grades/grader/gradingpanel\n * @package gradingform_rubric\n * @copyright 2019 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {call as fetchMany} from 'core/ajax';\nimport {normaliseResult} from 'core_grades/grades/grader/gradingpanel/normalise';\nimport {compareData} from 'core_grades/grades/grader/gradingpanel/comparison';\n\n// Note: We use jQuery.serializer here until we can rewrite Ajax to use XHR.send()\nimport jQuery from 'jquery';\n\n/**\n * For a given component, contextid, itemname & gradeduserid we can fetch the currently assigned grade.\n *\n * @param {String} component\n * @param {Number} contextid\n * @param {String} itemname\n * @param {Number} gradeduserid\n *\n * @returns {Promise}\n */\nexport const fetchCurrentGrade = (component, contextid, itemname, gradeduserid) => {\n return fetchMany([{\n methodname: `gradingform_rubric_grader_gradingpanel_fetch`,\n args: {\n component,\n contextid,\n itemname,\n gradeduserid,\n },\n }])[0];\n};\n\n/**\n * For a given component, contextid, itemname & gradeduserid we can store the currently assigned grade in a given form.\n *\n * @param {String} component\n * @param {Number} contextid\n * @param {String} itemname\n * @param {Number} gradeduserid\n * @param {Boolean} notifyUser\n * @param {HTMLElement} rootNode\n *\n * @returns {Promise}\n */\nexport const storeCurrentGrade = async(component, contextid, itemname, gradeduserid, notifyUser, rootNode) => {\n const form = rootNode.querySelector('form');\n\n if (compareData(form) === true) {\n return normaliseResult(await fetchMany([{\n methodname: `gradingform_rubric_grader_gradingpanel_store`,\n args: {\n component,\n contextid,\n itemname,\n gradeduserid,\n notifyuser: notifyUser,\n formdata: jQuery(form).serialize(),\n },\n }])[0]);\n } else {\n return '';\n }\n};\n"],"file":"gradingpanel.min.js"} \ No newline at end of file diff --git a/grade/grading/form/rubric/amd/src/grades/grader/gradingpanel.js b/grade/grading/form/rubric/amd/src/grades/grader/gradingpanel.js index 2b34c1ef3d5..9a27ab4c0be 100644 --- a/grade/grading/form/rubric/amd/src/grades/grader/gradingpanel.js +++ b/grade/grading/form/rubric/amd/src/grades/grader/gradingpanel.js @@ -26,7 +26,6 @@ import {call as fetchMany} from 'core/ajax'; import {normaliseResult} from 'core_grades/grades/grader/gradingpanel/normalise'; import {compareData} from 'core_grades/grades/grader/gradingpanel/comparison'; - // Note: We use jQuery.serializer here until we can rewrite Ajax to use XHR.send() import jQuery from 'jquery'; @@ -59,11 +58,12 @@ export const fetchCurrentGrade = (component, contextid, itemname, gradeduserid) * @param {Number} contextid * @param {String} itemname * @param {Number} gradeduserid + * @param {Boolean} notifyUser * @param {HTMLElement} rootNode * * @returns {Promise} */ -export const storeCurrentGrade = async(component, contextid, itemname, gradeduserid, rootNode) => { +export const storeCurrentGrade = async(component, contextid, itemname, gradeduserid, notifyUser, rootNode) => { const form = rootNode.querySelector('form'); if (compareData(form) === true) { @@ -74,6 +74,7 @@ export const storeCurrentGrade = async(component, contextid, itemname, gradeduse contextid, itemname, gradeduserid, + notifyuser: notifyUser, formdata: jQuery(form).serialize(), }, }])[0]); diff --git a/grade/grading/form/rubric/classes/grades/grader/gradingpanel/external/store.php b/grade/grading/form/rubric/classes/grades/grader/gradingpanel/external/store.php index 67ccfc7f538..8bbcb0b33b2 100644 --- a/grade/grading/form/rubric/classes/grades/grader/gradingpanel/external/store.php +++ b/grade/grading/form/rubric/classes/grades/grader/gradingpanel/external/store.php @@ -81,6 +81,12 @@ class store extends external_api { 'The serialised form data representing the grade', VALUE_REQUIRED ), + 'notifyuser' => new external_value( + PARAM_BOOL, + 'Wheteher to notify the user or not', + VALUE_DEFAULT, + false + ), ]); } @@ -92,10 +98,14 @@ class store extends external_api { * @param string $itemname * @param int $gradeduserid * @param string $formdata + * @param bool $notifyuser * @return array + * @throws coding_exception + * @throws moodle_exception * @since Moodle 3.8 */ - public static function execute(string $component, int $contextid, string $itemname, int $gradeduserid, string $formdata): array { + public static function execute(string $component, int $contextid, string $itemname, int $gradeduserid, bool $notifyuser, + string $formdata): array { global $USER; [ @@ -148,6 +158,12 @@ class store extends external_api { // Grade. $gradeitem->store_grade_from_formdata($gradeduser, $USER, (object) $data); + // Notify. + if ($notifyuser) { + // Send notification. + $gradeitem->send_student_notification($gradeduser, $USER); + } + // Fetch the updated grade back out. $grade = $gradeitem->get_grade_for_user($gradeduser, $USER); diff --git a/grade/grading/form/rubric/tests/grades_grader_gradingpanel_rubric_external_store_test.php b/grade/grading/form/rubric/tests/grades_grader_gradingpanel_rubric_external_store_test.php index 67954a45ebb..9e69038ea52 100644 --- a/grade/grading/form/rubric/tests/grades_grader_gradingpanel_rubric_external_store_test.php +++ b/grade/grading/form/rubric/tests/grades_grader_gradingpanel_rubric_external_store_test.php @@ -59,7 +59,7 @@ class store_test extends advanced_testcase { $this->expectException(coding_exception::class); $this->expectExceptionMessage("The 'foo' item is not valid for the 'mod_invalid' component"); - store::execute('mod_invalid', 1, 'foo', 2, 'formdata'); + store::execute('mod_invalid', 1, 'foo', 2, 'formdata', false); } /** @@ -72,7 +72,7 @@ class store_test extends advanced_testcase { $this->expectException(coding_exception::class); $this->expectExceptionMessage("The 'foo' item is not valid for the 'mod_forum' component"); - store::execute('mod_forum', 1, 'foo', 2, 'formdata'); + store::execute('mod_forum', 1, 'foo', 2, 'formdata', false); } /** @@ -92,7 +92,7 @@ class store_test extends advanced_testcase { $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum'); $this->expectException(moodle_exception::class); - store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, 'formdata'); + store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, 'formdata', false); } /** @@ -111,7 +111,7 @@ class store_test extends advanced_testcase { $this->expectException(moodle_exception::class); $this->expectExceptionMessage("Grading is not enabled"); - store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, 'formdata'); + store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, 'formdata', false); } /** @@ -146,7 +146,7 @@ class store_test extends advanced_testcase { 'advancedgrading' => $submissiondata, ], '', '&'); - $result = store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, $formdata); + $result = store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, $formdata, false); $result = external_api::clean_returnvalue(store::execute_returns(), $result); $this->assertIsArray($result); diff --git a/grade/tests/grades_grader_gradingpanel_point_external_store_test.php b/grade/tests/grades_grader_gradingpanel_point_external_store_test.php index b9c47603479..498342e4a3d 100644 --- a/grade/tests/grades_grader_gradingpanel_point_external_store_test.php +++ b/grade/tests/grades_grader_gradingpanel_point_external_store_test.php @@ -61,7 +61,7 @@ class store_test extends advanced_testcase { $this->expectException(coding_exception::class); $this->expectExceptionMessage("The 'foo' item is not valid for the 'mod_invalid' component"); - store::execute('mod_invalid', 1, 'foo', 2, 'formdata'); + store::execute('mod_invalid', 1, 'foo', 2, 'formdata', false); } /** @@ -74,7 +74,7 @@ class store_test extends advanced_testcase { $this->expectException(coding_exception::class); $this->expectExceptionMessage("The 'foo' item is not valid for the 'mod_forum' component"); - store::execute('mod_forum', 1, 'foo', 2, 'formdata'); + store::execute('mod_forum', 1, 'foo', 2, 'formdata', false); } /** @@ -96,7 +96,7 @@ class store_test extends advanced_testcase { $this->expectException(moodle_exception::class); $this->expectExceptionMessage("not configured for direct grading"); - store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, 'formdata'); + store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, 'formdata', false); } /** @@ -115,7 +115,7 @@ class store_test extends advanced_testcase { $this->expectException(moodle_exception::class); $this->expectExceptionMessage("Grading is not enabled"); - store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, 'formdata'); + store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, 'formdata', false); } /** @@ -139,7 +139,8 @@ class store_test extends advanced_testcase { $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum'); - $result = store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, http_build_query($formdata)); + $result = store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', + (int) $student->id, http_build_query($formdata), false); $result = external_api::clean_returnvalue(store::execute_returns(), $result); // The result should still be empty. @@ -199,7 +200,8 @@ class store_test extends advanced_testcase { $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum'); - $result = store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, http_build_query($formdata)); + $result = store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', + (int) $student->id, http_build_query($formdata), false); $result = external_api::clean_returnvalue(store::execute_returns(), $result); // The result should still be empty. @@ -264,7 +266,8 @@ class store_test extends advanced_testcase { $this->expectException(moodle_exception::class); $this->expectExceptionMessage("Invalid grade '{$suppliedvalue}' provided. Grades must be between 0 and {$maxvalue}."); - store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, http_build_query($formdata)); + store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', + (int) $student->id, http_build_query($formdata), false); } /** diff --git a/grade/tests/grades_grader_gradingpanel_scale_external_store_test.php b/grade/tests/grades_grader_gradingpanel_scale_external_store_test.php index dbf7ab59476..e95c35860ee 100644 --- a/grade/tests/grades_grader_gradingpanel_scale_external_store_test.php +++ b/grade/tests/grades_grader_gradingpanel_scale_external_store_test.php @@ -61,7 +61,7 @@ class store_test extends advanced_testcase { $this->expectException(coding_exception::class); $this->expectExceptionMessage("The 'foo' item is not valid for the 'mod_invalid' component"); - store::execute('mod_invalid', 1, 'foo', 2, 'formdata'); + store::execute('mod_invalid', 1, 'foo', 2, 'formdata', false); } /** @@ -74,7 +74,7 @@ class store_test extends advanced_testcase { $this->expectException(coding_exception::class); $this->expectExceptionMessage("The 'foo' item is not valid for the 'mod_forum' component"); - store::execute('mod_forum', 1, 'foo', 2, 'formdata'); + store::execute('mod_forum', 1, 'foo', 2, 'formdata', false); } /** @@ -96,7 +96,7 @@ class store_test extends advanced_testcase { $this->expectException(moodle_exception::class); $this->expectExceptionMessage("not configured for grading with scales"); - store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, 'formdata'); + store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, 'formdata', false); } /** @@ -115,7 +115,7 @@ class store_test extends advanced_testcase { $this->expectException(moodle_exception::class); $this->expectExceptionMessage("Grading is not enabled"); - store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, 'formdata'); + store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, 'formdata', false); } /** @@ -137,7 +137,8 @@ class store_test extends advanced_testcase { $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum'); - $result = store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, http_build_query($formdata)); + $result = store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', + (int) $student->id, http_build_query($formdata), false); $result = external_api::clean_returnvalue(store::execute_returns(), $result); // The result should still be empty. @@ -208,7 +209,8 @@ class store_test extends advanced_testcase { $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum'); - $result = store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, http_build_query($formdata)); + $result = store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', + (int) $student->id, http_build_query($formdata), false); $result = external_api::clean_returnvalue(store::execute_returns(), $result); // The result should still be empty. @@ -282,7 +284,8 @@ class store_test extends advanced_testcase { $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum'); - $result = store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, http_build_query($formdata)); + $result = store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', + (int) $student->id, http_build_query($formdata), false); $result = external_api::clean_returnvalue(store::execute_returns(), $result); // The result should still be empty. @@ -364,7 +367,8 @@ class store_test extends advanced_testcase { $this->expectException(moodle_exception::class); $this->expectExceptionMessage("Invalid grade '{$suppliedvalue}' provided. Grades must be between 0 and 3."); - store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, http_build_query($formdata)); + store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', + (int) $student->id, http_build_query($formdata), false); } /** diff --git a/lang/en/grades.php b/lang/en/grades.php index 63f2b000431..c60964c0f40 100644 --- a/lang/en/grades.php +++ b/lang/en/grades.php @@ -318,6 +318,8 @@ $string['grademax'] = 'Maximum grade'; $string['grademax_help'] = 'This setting determines the maximum grade when using the value grade type. The maximum grade for an activity-based grade item is set on the activity settings page.'; $string['grademin'] = 'Minimum grade'; $string['grademin_help'] = 'This setting determines the minimum grade when using the value grade type.'; +$string['gradenotificationmessage'] = 'You have new feedback on your work for "{$a}"'; +$string['gradenotificationsubject'] = 'You have been graded'; $string['gradeoutcomeitem'] = 'Grade outcome item'; $string['gradeoutcomes'] = 'Outcomes'; $string['gradeoutcomescourses'] = 'Course outcomes'; diff --git a/lang/en/moodle.php b/lang/en/moodle.php index d3e80a7dfed..2b7061fe3e4 100644 --- a/lang/en/moodle.php +++ b/lang/en/moodle.php @@ -1213,6 +1213,7 @@ $string['messageprovider:courserequested'] = 'Course creation request notificati $string['messageprovider:courserequestrejected'] = 'Course creation request rejection notification'; $string['messageprovider:errors'] = 'Important errors with the site'; $string['messageprovider:errors_help'] = 'These are important errors that an administrator should know about.'; +$string['messageprovider:gradenotifications'] = 'Grade notifications'; $string['messageprovider:messagecontactrequests'] = 'Message contact requests notification'; $string['messageprovider:notices'] = 'Notices about minor problems'; $string['messageprovider:notices_help'] = 'These are notices that an administrator might be interested in seeing.'; diff --git a/lib/db/messages.php b/lib/db/messages.php index 3ae3810c3d4..b89298865d7 100644 --- a/lib/db/messages.php +++ b/lib/db/messages.php @@ -135,4 +135,11 @@ $messageproviders = array ( 'email' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_LOGGEDOFF, ) ), + + 'gradenotifications' => [ + 'defaults' => array( + 'popup' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_LOGGEDIN + MESSAGE_DEFAULT_LOGGEDOFF, + 'email' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_LOGGEDOFF, + ), + ], ); diff --git a/mod/forum/amd/build/grades/grader.min.js b/mod/forum/amd/build/grades/grader.min.js index 47b12c8a7c9..5dc6ae0ab2e 100644 --- a/mod/forum/amd/build/grades/grader.min.js +++ b/mod/forum/amd/build/grades/grader.min.js @@ -1,2 +1,2 @@ -define ("mod_forum/grades/grader",["exports","./grader/selectors","mod_forum/repository","core/templates","../local/grades/grader","core/notification","core_course/repository","core/url"],function(a,b,c,d,e,f,g,h){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.registerLaunchListeners=void 0;b=j(b);c=i(c);d=i(d);e=j(e);f=i(f);g=i(g);function i(a){return a&&a.__esModule?a:{default:a}}function j(a){if(a&&a.__esModule){return a}else{var b={};if(null!=a){for(var c in a){if(Object.prototype.hasOwnProperty.call(a,c)){var d=Object.defineProperty&&Object.getOwnPropertyDescriptor?Object.getOwnPropertyDescriptor(a,c):{};if(d.get||d.set){Object.defineProperty(b,c,d)}else{b[c]=a[c]}}}}b.default=a;return b}}function k(a){for(var b=1;b.\n\n/**\n * This module will tie together all of the different calls the gradable module will make.\n *\n * @module mod_forum/grades/grader\n * @package mod_forum\n * @copyright 2019 Andrew Nicols \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport * as Selectors from './grader/selectors';\nimport Repository from 'mod_forum/repository';\nimport Templates from 'core/templates';\nimport * as Grader from '../local/grades/grader';\nimport Notification from 'core/notification';\nimport CourseRepository from 'core_course/repository';\nimport {relativeUrl} from 'core/url';\n\nconst templateNames = {\n contentRegion: 'mod_forum/grades/grader/discussion/posts',\n};\n\n/**\n * Curried function with CMID set, this is then used in unified grader as a fetch a users content.\n *\n * @param {Number} cmid\n * @param {Bool} experimentalDisplayMode\n * @return {Function}\n */\nconst getContentForUserIdFunction = (cmid, experimentalDisplayMode) => (userid) => {\n /**\n * Given the parent function is called with the second param set execute the partially executed function.\n *\n * @param {Number} userid\n */\n return Repository.getDiscussionByUserID(userid, cmid)\n .then(context => {\n // Rebuild the returned data for the template.\n context.discussions = context.discussions.map(discussionPostMapper);\n context.experimentaldisplaymode = experimentalDisplayMode ? true : false;\n\n return Templates.render(templateNames.contentRegion, context);\n })\n .catch(Notification.exception);\n};\n\n/**\n * Curried function with CMID set, this is then used in unified grader as a fetch users call.\n * The function curried fetches all users in a course for a given CMID.\n *\n * @param {Number} cmid\n * @param {Number} groupID\n * @return {Array} Array of users for a given context.\n */\nconst getUsersForCmidFunction = (cmid, groupID) => async() => {\n const context = await CourseRepository.getUsersFromCourseModuleID(cmid, groupID);\n\n return context.users;\n};\n\n\nconst findGradableNode = node => node.closest(Selectors.gradableItem);\n\n/**\n * For a discussion we need to manipulate it's posts to hide certain UI elements.\n *\n * @param {Object} discussion\n * @return {Array} name, id, posts\n */\nconst discussionPostMapper = (discussion) => {\n // Map postid => post.\n const parentMap = new Map();\n discussion.posts.parentposts.forEach(post => parentMap.set(post.id, post));\n const userPosts = discussion.posts.userposts.map(post => {\n post.readonly = true;\n post.hasreplies = false;\n post.replies = [];\n\n const parent = post.parentid ? parentMap.get(post.parentid) : null;\n if (parent) {\n parent.hasreplies = false;\n parent.replies = [];\n parent.readonly = true;\n post.parentauthorname = parent.author.fullname;\n }\n\n return {\n parent,\n post\n };\n });\n\n return {\n ...discussion,\n posts: userPosts,\n };\n};\n\n/**\n * Launch the Grader.\n *\n * @param {HTMLElement} rootNode the root HTML element describing what is to be graded\n */\nconst launchWholeForumGrading = async(rootNode) => {\n const data = rootNode.dataset;\n const gradingPanelFunctions = await Grader.getGradingPanelFunctions(\n 'mod_forum',\n data.contextid,\n data.gradingComponent,\n data.gradingComponentSubtype,\n data.gradableItemtype\n );\n\n const groupID = data.group ? data.group : 0;\n\n await Grader.launch(\n getUsersForCmidFunction(data.cmid, groupID),\n getContentForUserIdFunction(data.cmid, data.experimentalDisplayMode == \"1\"),\n gradingPanelFunctions.getter,\n gradingPanelFunctions.setter,\n {\n groupid: data.groupid,\n initialUserId: data.initialuserid,\n moduleName: data.name,\n courseName: data.courseName,\n courseUrl: relativeUrl('/course/view.php', {id: data.courseId})\n }\n );\n};\n\n/**\n * Register listeners to launch the grading panel.\n */\nexport const registerLaunchListeners = () => {\n document.addEventListener('click', async(e) => {\n if (e.target.matches(Selectors.launch)) {\n const rootNode = findGradableNode(e.target);\n\n if (!rootNode) {\n throw Error('Unable to find a gradable item');\n }\n\n if (rootNode.matches(Selectors.gradableItems.wholeForum)) {\n // Note: The preventDefault must be before any async function calls because the function becomes async\n // at that point and the default action is implemented.\n e.preventDefault();\n try {\n await launchWholeForumGrading(rootNode);\n } catch (error) {\n Notification.exception(error);\n }\n } else {\n throw Error('Unable to find a valid gradable item');\n }\n }\n });\n};\n"],"file":"grader.min.js"} \ No newline at end of file +{"version":3,"sources":["../../src/grades/grader.js"],"names":["templateNames","contentRegion","getContentForUserIdFunction","cmid","experimentalDisplayMode","userid","Repository","getDiscussionByUserID","then","context","discussions","map","discussionPostMapper","experimentaldisplaymode","Templates","render","catch","Notification","exception","getUsersForCmidFunction","groupID","CourseRepository","getUsersFromCourseModuleID","users","findGradableNode","node","closest","Selectors","gradableItem","discussion","parentMap","Map","posts","parentposts","forEach","post","set","id","userPosts","userposts","readonly","hasreplies","replies","parent","parentid","get","parentauthorname","author","fullname","launchWholeForumGrading","rootNode","data","dataset","Grader","getGradingPanelFunctions","contextid","gradingComponent","gradingComponentSubtype","gradableItemtype","gradingPanelFunctions","group","launch","getter","setter","groupid","initialUserId","initialuserid","moduleName","name","courseName","courseUrl","courseId","sendStudentNotifications","registerLaunchListeners","document","addEventListener","e","target","matches","Error","gradableItems","wholeForum","preventDefault"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * This module will tie together all of the different calls the gradable module will make.\n *\n * @module mod_forum/grades/grader\n * @package mod_forum\n * @copyright 2019 Andrew Nicols \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport * as Selectors from './grader/selectors';\nimport Repository from 'mod_forum/repository';\nimport Templates from 'core/templates';\nimport * as Grader from '../local/grades/grader';\nimport Notification from 'core/notification';\nimport CourseRepository from 'core_course/repository';\nimport {relativeUrl} from 'core/url';\n\nconst templateNames = {\n contentRegion: 'mod_forum/grades/grader/discussion/posts',\n};\n\n/**\n * Curried function with CMID set, this is then used in unified grader as a fetch a users content.\n *\n * @param {Number} cmid\n * @param {Bool} experimentalDisplayMode\n * @return {Function}\n */\nconst getContentForUserIdFunction = (cmid, experimentalDisplayMode) => (userid) => {\n /**\n * Given the parent function is called with the second param set execute the partially executed function.\n *\n * @param {Number} userid\n */\n return Repository.getDiscussionByUserID(userid, cmid)\n .then(context => {\n // Rebuild the returned data for the template.\n context.discussions = context.discussions.map(discussionPostMapper);\n context.experimentaldisplaymode = experimentalDisplayMode ? true : false;\n\n return Templates.render(templateNames.contentRegion, context);\n })\n .catch(Notification.exception);\n};\n\n/**\n * Curried function with CMID set, this is then used in unified grader as a fetch users call.\n * The function curried fetches all users in a course for a given CMID.\n *\n * @param {Number} cmid\n * @param {Number} groupID\n * @return {Array} Array of users for a given context.\n */\nconst getUsersForCmidFunction = (cmid, groupID) => async() => {\n const context = await CourseRepository.getUsersFromCourseModuleID(cmid, groupID);\n\n return context.users;\n};\n\n\nconst findGradableNode = node => node.closest(Selectors.gradableItem);\n\n/**\n * For a discussion we need to manipulate it's posts to hide certain UI elements.\n *\n * @param {Object} discussion\n * @return {Array} name, id, posts\n */\nconst discussionPostMapper = (discussion) => {\n // Map postid => post.\n const parentMap = new Map();\n discussion.posts.parentposts.forEach(post => parentMap.set(post.id, post));\n const userPosts = discussion.posts.userposts.map(post => {\n post.readonly = true;\n post.hasreplies = false;\n post.replies = [];\n\n const parent = post.parentid ? parentMap.get(post.parentid) : null;\n if (parent) {\n parent.hasreplies = false;\n parent.replies = [];\n parent.readonly = true;\n post.parentauthorname = parent.author.fullname;\n }\n\n return {\n parent,\n post\n };\n });\n\n return {\n ...discussion,\n posts: userPosts,\n };\n};\n\n/**\n * Launch the Grader.\n *\n * @param {HTMLElement} rootNode the root HTML element describing what is to be graded\n */\nconst launchWholeForumGrading = async(rootNode) => {\n const data = rootNode.dataset;\n const gradingPanelFunctions = await Grader.getGradingPanelFunctions(\n 'mod_forum',\n data.contextid,\n data.gradingComponent,\n data.gradingComponentSubtype,\n data.gradableItemtype\n );\n\n const groupID = data.group ? data.group : 0;\n\n await Grader.launch(\n getUsersForCmidFunction(data.cmid, groupID),\n getContentForUserIdFunction(data.cmid, data.experimentalDisplayMode == \"1\"),\n gradingPanelFunctions.getter,\n gradingPanelFunctions.setter,\n {\n groupid: data.groupid,\n initialUserId: data.initialuserid,\n moduleName: data.name,\n courseName: data.courseName,\n courseUrl: relativeUrl('/course/view.php', {id: data.courseId}),\n sendStudentNotifications: data.sendStudentNotifications,\n }\n );\n};\n\n/**\n * Register listeners to launch the grading panel.\n */\nexport const registerLaunchListeners = () => {\n document.addEventListener('click', async(e) => {\n if (e.target.matches(Selectors.launch)) {\n const rootNode = findGradableNode(e.target);\n\n if (!rootNode) {\n throw Error('Unable to find a gradable item');\n }\n\n if (rootNode.matches(Selectors.gradableItems.wholeForum)) {\n // Note: The preventDefault must be before any async function calls because the function becomes async\n // at that point and the default action is implemented.\n e.preventDefault();\n try {\n await launchWholeForumGrading(rootNode);\n } catch (error) {\n Notification.exception(error);\n }\n } else {\n throw Error('Unable to find a valid gradable item');\n }\n }\n });\n};\n"],"file":"grader.min.js"} \ No newline at end of file diff --git a/mod/forum/amd/build/local/grades/grader.min.js b/mod/forum/amd/build/local/grades/grader.min.js index a3857854bf7..7c5b5c58914 100644 --- a/mod/forum/amd/build/local/grades/grader.min.js +++ b/mod/forum/amd/build/local/grades/grader.min.js @@ -1,2 +1,2 @@ -define ("mod_forum/local/grades/grader",["exports","core/templates","./local/grader/selectors","./local/grader/user_picker","mod_forum/local/layout/fullscreen","./local/grader/gradingpanel","core/toast","core/str","core_grades/grades/grader/gradingpanel/normalise","core/loadingicon","core/utils","core_grades/grades/grader/gradingpanel/comparison"],function(a,b,c,d,e,f,g,h,i,j,k,l){"use strict";Object.defineProperty(a,"__esModule",{value:!0});Object.defineProperty(a,"getGradingPanelFunctions",{enumerable:!0,get:function get(){return f.default}});a.launch=void 0;b=m(b);c=m(c);d=m(d);f=m(f);function m(a){return a&&a.__esModule?a:{default:a}}function n(a){for(var b=1;b.\n\n/**\n * This module will tie together all of the different calls the gradable module will make.\n *\n * @module mod_forum/local/grades/grader\n * @package mod_forum\n * @copyright 2019 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport Templates from 'core/templates';\nimport Selectors from './local/grader/selectors';\nimport getUserPicker from './local/grader/user_picker';\nimport {createLayout as createFullScreenWindow} from 'mod_forum/local/layout/fullscreen';\nimport getGradingPanelFunctions from './local/grader/gradingpanel';\nimport {add as addToast} from 'core/toast';\nimport {get_string as getString} from 'core/str';\nimport {failedUpdate} from 'core_grades/grades/grader/gradingpanel/normalise';\nimport {addIconToContainerWithPromise} from 'core/loadingicon';\nimport {debounce} from 'core/utils';\nimport {fillInitialValues} from 'core_grades/grades/grader/gradingpanel/comparison';\n\nconst templateNames = {\n grader: {\n app: 'mod_forum/local/grades/grader',\n gradingPanel: {\n error: 'mod_forum/local/grades/local/grader/gradingpanel/error',\n },\n searchResults: 'mod_forum/local/grades/local/grader/user_picker/user_search',\n status: 'mod_forum/local/grades/local/grader/status',\n },\n};\n\n/**\n * Helper function that replaces the user picker placeholder with what we get back from the user picker class.\n *\n * @param {HTMLElement} root\n * @param {String} html\n */\nconst displayUserPicker = (root, html) => {\n const pickerRegion = root.querySelector(Selectors.regions.pickerRegion);\n Templates.replaceNodeContents(pickerRegion, html, '');\n};\n\n/**\n * To be removed, this is now done as a part of Templates.renderForPromise()\n *\n * @param {String} html\n * @param {String} js\n * @return {[*, *]}\n */\nconst fetchContentFromRender = (html, js) => {\n return [html, js];\n};\n\n/**\n * Here we build the function that is passed to the user picker that'll handle updating the user content area\n * of the grading interface.\n *\n * @param {HTMLElement} root\n * @param {Function} getContentForUser\n * @param {Function} getGradeForUser\n * @return {Function}\n */\nconst getUpdateUserContentFunction = (root, getContentForUser, getGradeForUser) => {\n let firstLoad = true;\n\n return async(user) => {\n const spinner = firstLoad ? null : addIconToContainerWithPromise(root);\n const [\n [html, js],\n userGrade,\n ] = await Promise.all([\n getContentForUser(user.id).then(fetchContentFromRender),\n getGradeForUser(user.id),\n ]);\n Templates.replaceNodeContents(root.querySelector(Selectors.regions.moduleReplace), html, js);\n\n const [\n gradingPanelHtml,\n gradingPanelJS\n ] = await Templates.render(userGrade.templatename, userGrade.grade).then(fetchContentFromRender);\n const panelContainer = root.querySelector(Selectors.regions.gradingPanelContainer);\n const panel = panelContainer.querySelector(Selectors.regions.gradingPanel);\n Templates.replaceNodeContents(panel, gradingPanelHtml, gradingPanelJS);\n fillInitialValues(panel.querySelector('form'));\n\n panelContainer.scrollTop = 0;\n firstLoad = false;\n\n if (spinner) {\n spinner.resolve();\n }\n };\n};\n\n/**\n * Show the search results container and hide the user picker and body content.\n *\n * @param {HTMLElement} bodyContainer The container element for the body content\n * @param {HTMLElement} userPickerContainer The container element for the user picker\n * @param {HTMLElement} searchResultsContainer The container element for the search results\n */\nconst showSearchResultContainer = (bodyContainer, userPickerContainer, searchResultsContainer) => {\n bodyContainer.classList.add('hidden');\n userPickerContainer.classList.add('hidden');\n searchResultsContainer.classList.remove('hidden');\n};\n\n/**\n * Hide the search results container and show the user picker and body content.\n *\n * @param {HTMLElement} bodyContainer The container element for the body content\n * @param {HTMLElement} userPickerContainer The container element for the user picker\n * @param {HTMLElement} searchResultsContainer The container element for the search results\n */\nconst hideSearchResultContainer = (bodyContainer, userPickerContainer, searchResultsContainer) => {\n bodyContainer.classList.remove('hidden');\n userPickerContainer.classList.remove('hidden');\n searchResultsContainer.classList.add('hidden');\n};\n\n/**\n * Toggles the visibility of the user search.\n *\n * @param {HTMLElement} toggleSearchButton The button that toggles the search\n * @param {HTMLElement} searchContainer The container element for the user search\n * @param {HTMLElement} searchInput The input element for searching\n */\nconst showUserSearchInput = (toggleSearchButton, searchContainer, searchInput) => {\n searchContainer.classList.remove('collapsed');\n toggleSearchButton.setAttribute('aria-expanded', 'true');\n toggleSearchButton.classList.add('expand');\n toggleSearchButton.classList.remove('collapse');\n searchInput.focus();\n};\n\n/**\n * Toggles the visibility of the user search.\n *\n * @param {HTMLElement} toggleSearchButton The button that toggles the search\n * @param {HTMLElement} searchContainer The container element for the user search\n * @param {HTMLElement} searchInput The input element for searching\n */\nconst hideUserSearchInput = (toggleSearchButton, searchContainer, searchInput) => {\n searchContainer.classList.add('collapsed');\n toggleSearchButton.setAttribute('aria-expanded', 'false');\n toggleSearchButton.classList.add('collapse');\n toggleSearchButton.classList.remove('expand');\n toggleSearchButton.focus();\n searchInput.value = '';\n};\n\n/**\n * Find the list of users who's names include the given search term.\n *\n * @param {Array} userList List of users for the grader\n * @param {String} searchTerm The search term to match\n * @return {Array}\n */\nconst searchForUsers = (userList, searchTerm) => {\n if (searchTerm === '') {\n return userList;\n }\n\n searchTerm = searchTerm.toLowerCase();\n\n return userList.filter((user) => {\n return user.fullname.toLowerCase().includes(searchTerm);\n });\n};\n\n/**\n * Render the list of users in the search results area.\n *\n * @param {HTMLElement} searchResultsContainer The container element for search results\n * @param {Array} users The list of users to display\n */\nconst renderSearchResults = async (searchResultsContainer, users) => {\n const {html, js} = await Templates.renderForPromise(templateNames.grader.searchResults, {users});\n Templates.replaceNodeContents(searchResultsContainer, html, js);\n};\n\n/**\n * Add click handlers to the buttons in the header of the grading interface.\n *\n * @param {HTMLElement} graderLayout\n * @param {Object} userPicker\n * @param {Function} saveGradeFunction\n * @param {Array} userList List of users for the grader.\n */\nconst registerEventListeners = (graderLayout, userPicker, saveGradeFunction, userList) => {\n const graderContainer = graderLayout.getContainer();\n const toggleSearchButton = graderContainer.querySelector(Selectors.buttons.toggleSearch);\n const searchInputContainer = graderContainer.querySelector(Selectors.regions.userSearchContainer);\n const searchInput = searchInputContainer.querySelector(Selectors.regions.userSearchInput);\n const bodyContainer = graderContainer.querySelector(Selectors.regions.bodyContainer);\n const userPickerContainer = graderContainer.querySelector(Selectors.regions.pickerRegion);\n const searchResultsContainer = graderContainer.querySelector(Selectors.regions.searchResultsContainer);\n\n graderContainer.addEventListener('click', (e) => {\n if (e.target.closest(Selectors.buttons.toggleFullscreen)) {\n e.stopImmediatePropagation();\n e.preventDefault();\n graderLayout.toggleFullscreen();\n\n return;\n }\n\n if (e.target.closest(Selectors.buttons.closeGrader)) {\n e.stopImmediatePropagation();\n e.preventDefault();\n\n graderLayout.close();\n\n return;\n }\n\n if (e.target.closest(Selectors.buttons.saveGrade)) {\n saveGradeFunction(userPicker.currentUser);\n }\n\n if (e.target.closest(Selectors.buttons.toggleSearch)) {\n if (toggleSearchButton.getAttribute('aria-expanded') === 'true') {\n // Search is open so let's close it.\n hideUserSearchInput(toggleSearchButton, searchInputContainer, searchInput);\n hideSearchResultContainer(bodyContainer, userPickerContainer, searchResultsContainer);\n searchResultsContainer.innerHTML = '';\n } else {\n // Search is closed so let's open it.\n showUserSearchInput(toggleSearchButton, searchInputContainer, searchInput);\n showSearchResultContainer(bodyContainer, userPickerContainer, searchResultsContainer);\n renderSearchResults(searchResultsContainer, userList);\n }\n\n return;\n }\n\n const selectUserButton = e.target.closest(Selectors.buttons.selectUser);\n if (selectUserButton) {\n const userId = selectUserButton.getAttribute('data-userid');\n const user = userList.find(user => user.id == userId);\n userPicker.setUserId(userId);\n userPicker.showUser(user);\n hideUserSearchInput(toggleSearchButton, searchInputContainer, searchInput);\n hideSearchResultContainer(bodyContainer, userPickerContainer, searchResultsContainer);\n searchResultsContainer.innerHTML = '';\n }\n });\n\n // Debounce the search input so that it only executes 300 milliseconds after the user has finished typing.\n searchInput.addEventListener('input', debounce(() => {\n const users = searchForUsers(userList, searchInput.value);\n renderSearchResults(searchResultsContainer, users);\n }, 300));\n};\n\n/**\n * Get the function used to save a user grade.\n *\n * @param {HTMLElement} root The container for the grader\n * @param {Function} setGradeForUser The function that will be called.\n * @return {Function}\n */\nconst getSaveUserGradeFunction = (root, setGradeForUser) => {\n return async(user) => {\n try {\n root.querySelector(Selectors.regions.gradingPanelErrors).innerHTML = '';\n const result = await setGradeForUser(user.id, root.querySelector(Selectors.regions.gradingPanel));\n if (result.success) {\n addToast(await getString('grades:gradesavedfor', 'mod_forum', user));\n }\n if (result.failed) {\n displayGradingError(root, user, result.error);\n }\n\n return result;\n } catch (err) {\n displayGradingError(root, user, err);\n\n return failedUpdate(err);\n }\n };\n};\n\n/**\n * Display a grading error, typically from a failed save.\n *\n * @param {HTMLElement} root The container for the grader\n * @param {Object} user The user who was errored\n * @param {Object} err The details of the error\n */\nconst displayGradingError = async(root, user, err) => {\n const [\n {html, js},\n errorString\n ] = await Promise.all([\n Templates.renderForPromise(templateNames.grader.gradingPanel.error, {error: err}),\n await getString('grades:gradesavefailed', 'mod_forum', {error: err.message, ...user}),\n ]);\n\n Templates.replaceNodeContents(root.querySelector(Selectors.regions.gradingPanelErrors), html, js);\n addToast(errorString);\n};\n\n/**\n * Launch the grader interface with the specified parameters.\n *\n * @param {Function} getListOfUsers A function to get the list of users\n * @param {Function} getContentForUser A function to get the content for a specific user\n * @param {Function} getGradeForUser A function get the grade details for a specific user\n * @param {Function} setGradeForUser A function to set the grade for a specific user\n */\nexport const launch = async(getListOfUsers, getContentForUser, getGradeForUser, setGradeForUser, {\n initialUserId = null, moduleName, courseName, courseUrl\n} = {}) => {\n\n // We need all of these functions to be executed in series, if one step runs before another the interface\n // will not work.\n const [\n graderLayout,\n {html, js},\n userList,\n ] = await Promise.all([\n createFullScreenWindow({fullscreen: false, showLoader: false}),\n Templates.renderForPromise(templateNames.grader.app, {\n moduleName,\n courseName,\n courseUrl,\n drawer: {show: true}\n }),\n getListOfUsers(),\n ]);\n const graderContainer = graderLayout.getContainer();\n\n const saveGradeFunction = getSaveUserGradeFunction(graderContainer, setGradeForUser);\n\n Templates.replaceNodeContents(graderContainer, html, js);\n const updateUserContent = getUpdateUserContentFunction(graderContainer, getContentForUser, getGradeForUser);\n\n const userIds = userList.map(user => user.id);\n const statusContainer = graderContainer.querySelector(Selectors.regions.statusContainer);\n // Fetch the userpicker for display.\n const userPicker = await getUserPicker(\n userList,\n user => {\n const renderContext = {\n status: null,\n index: userIds.indexOf(user.id) + 1,\n total: userList.length\n };\n Templates.render(templateNames.grader.status, renderContext).then(html => {\n statusContainer.innerHTML = html;\n return html;\n }).catch();\n updateUserContent(user);\n },\n saveGradeFunction,\n {\n initialUserId,\n },\n );\n\n // Register all event listeners.\n registerEventListeners(graderLayout, userPicker, saveGradeFunction, userList);\n\n // Display the newly created user picker.\n displayUserPicker(graderContainer, userPicker.rootNode);\n};\n\nexport {getGradingPanelFunctions};\n"],"file":"grader.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/grades/grader.js"],"names":["templateNames","grader","app","gradingPanel","error","searchResults","status","displayUserPicker","root","html","pickerRegion","querySelector","Selectors","regions","Templates","replaceNodeContents","fetchContentFromRender","js","getUpdateUserContentFunction","getContentForUser","getGradeForUser","firstLoad","user","spinner","Promise","all","id","then","userGrade","moduleReplace","render","templatename","grade","gradingPanelHtml","gradingPanelJS","panelContainer","gradingPanelContainer","panel","scrollTop","resolve","showSearchResultContainer","bodyContainer","userPickerContainer","searchResultsContainer","classList","add","remove","hideSearchResultContainer","showUserSearchInput","toggleSearchButton","searchContainer","searchInput","setAttribute","focus","hideUserSearchInput","value","searchForUsers","userList","searchTerm","toLowerCase","filter","fullname","includes","renderSearchResults","users","renderForPromise","registerEventListeners","graderLayout","userPicker","saveGradeFunction","graderContainer","getContainer","buttons","toggleSearch","searchInputContainer","userSearchContainer","userSearchInput","addEventListener","e","target","closest","toggleFullscreen","stopImmediatePropagation","preventDefault","closeGrader","close","saveGrade","currentUser","getAttribute","innerHTML","selectUserButton","selectUser","userId","find","setUserId","showUser","getSaveUserGradeFunction","setGradeForUser","gradingPanelErrors","values","sendStudentNotifications","result","success","addToast","failed","displayGradingError","err","message","errorString","launch","getListOfUsers","initialUserId","moduleName","courseName","courseUrl","fullscreen","showLoader","drawer","show","defaultsendnotifications","updateUserContent","userIds","map","statusContainer","renderContext","index","indexOf","total","length","catch","rootNode"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * This module will tie together all of the different calls the gradable module will make.\n *\n * @module mod_forum/local/grades/grader\n * @package mod_forum\n * @copyright 2019 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport Templates from 'core/templates';\nimport Selectors from './local/grader/selectors';\nimport getUserPicker from './local/grader/user_picker';\nimport {createLayout as createFullScreenWindow} from 'mod_forum/local/layout/fullscreen';\nimport getGradingPanelFunctions from './local/grader/gradingpanel';\nimport {add as addToast} from 'core/toast';\nimport {get_string as getString} from 'core/str';\nimport {failedUpdate} from 'core_grades/grades/grader/gradingpanel/normalise';\nimport {addIconToContainerWithPromise} from 'core/loadingicon';\nimport {debounce} from 'core/utils';\nimport {fillInitialValues} from 'core_grades/grades/grader/gradingpanel/comparison';\n\nconst templateNames = {\n grader: {\n app: 'mod_forum/local/grades/grader',\n gradingPanel: {\n error: 'mod_forum/local/grades/local/grader/gradingpanel/error',\n },\n searchResults: 'mod_forum/local/grades/local/grader/user_picker/user_search',\n status: 'mod_forum/local/grades/local/grader/status',\n },\n};\n\n/**\n * Helper function that replaces the user picker placeholder with what we get back from the user picker class.\n *\n * @param {HTMLElement} root\n * @param {String} html\n */\nconst displayUserPicker = (root, html) => {\n const pickerRegion = root.querySelector(Selectors.regions.pickerRegion);\n Templates.replaceNodeContents(pickerRegion, html, '');\n};\n\n/**\n * To be removed, this is now done as a part of Templates.renderForPromise()\n *\n * @param {String} html\n * @param {String} js\n * @return {[*, *]}\n */\nconst fetchContentFromRender = (html, js) => {\n return [html, js];\n};\n\n/**\n * Here we build the function that is passed to the user picker that'll handle updating the user content area\n * of the grading interface.\n *\n * @param {HTMLElement} root\n * @param {Function} getContentForUser\n * @param {Function} getGradeForUser\n * @return {Function}\n */\nconst getUpdateUserContentFunction = (root, getContentForUser, getGradeForUser) => {\n let firstLoad = true;\n\n return async(user) => {\n const spinner = firstLoad ? null : addIconToContainerWithPromise(root);\n const [\n [html, js],\n userGrade,\n ] = await Promise.all([\n getContentForUser(user.id).then(fetchContentFromRender),\n getGradeForUser(user.id),\n ]);\n Templates.replaceNodeContents(root.querySelector(Selectors.regions.moduleReplace), html, js);\n\n const [\n gradingPanelHtml,\n gradingPanelJS\n ] = await Templates.render(userGrade.templatename, userGrade.grade).then(fetchContentFromRender);\n const panelContainer = root.querySelector(Selectors.regions.gradingPanelContainer);\n const panel = panelContainer.querySelector(Selectors.regions.gradingPanel);\n Templates.replaceNodeContents(panel, gradingPanelHtml, gradingPanelJS);\n fillInitialValues(panel.querySelector('form'));\n\n panelContainer.scrollTop = 0;\n firstLoad = false;\n\n if (spinner) {\n spinner.resolve();\n }\n };\n};\n\n/**\n * Show the search results container and hide the user picker and body content.\n *\n * @param {HTMLElement} bodyContainer The container element for the body content\n * @param {HTMLElement} userPickerContainer The container element for the user picker\n * @param {HTMLElement} searchResultsContainer The container element for the search results\n */\nconst showSearchResultContainer = (bodyContainer, userPickerContainer, searchResultsContainer) => {\n bodyContainer.classList.add('hidden');\n userPickerContainer.classList.add('hidden');\n searchResultsContainer.classList.remove('hidden');\n};\n\n/**\n * Hide the search results container and show the user picker and body content.\n *\n * @param {HTMLElement} bodyContainer The container element for the body content\n * @param {HTMLElement} userPickerContainer The container element for the user picker\n * @param {HTMLElement} searchResultsContainer The container element for the search results\n */\nconst hideSearchResultContainer = (bodyContainer, userPickerContainer, searchResultsContainer) => {\n bodyContainer.classList.remove('hidden');\n userPickerContainer.classList.remove('hidden');\n searchResultsContainer.classList.add('hidden');\n};\n\n/**\n * Toggles the visibility of the user search.\n *\n * @param {HTMLElement} toggleSearchButton The button that toggles the search\n * @param {HTMLElement} searchContainer The container element for the user search\n * @param {HTMLElement} searchInput The input element for searching\n */\nconst showUserSearchInput = (toggleSearchButton, searchContainer, searchInput) => {\n searchContainer.classList.remove('collapsed');\n toggleSearchButton.setAttribute('aria-expanded', 'true');\n toggleSearchButton.classList.add('expand');\n toggleSearchButton.classList.remove('collapse');\n searchInput.focus();\n};\n\n/**\n * Toggles the visibility of the user search.\n *\n * @param {HTMLElement} toggleSearchButton The button that toggles the search\n * @param {HTMLElement} searchContainer The container element for the user search\n * @param {HTMLElement} searchInput The input element for searching\n */\nconst hideUserSearchInput = (toggleSearchButton, searchContainer, searchInput) => {\n searchContainer.classList.add('collapsed');\n toggleSearchButton.setAttribute('aria-expanded', 'false');\n toggleSearchButton.classList.add('collapse');\n toggleSearchButton.classList.remove('expand');\n toggleSearchButton.focus();\n searchInput.value = '';\n};\n\n/**\n * Find the list of users who's names include the given search term.\n *\n * @param {Array} userList List of users for the grader\n * @param {String} searchTerm The search term to match\n * @return {Array}\n */\nconst searchForUsers = (userList, searchTerm) => {\n if (searchTerm === '') {\n return userList;\n }\n\n searchTerm = searchTerm.toLowerCase();\n\n return userList.filter((user) => {\n return user.fullname.toLowerCase().includes(searchTerm);\n });\n};\n\n/**\n * Render the list of users in the search results area.\n *\n * @param {HTMLElement} searchResultsContainer The container element for search results\n * @param {Array} users The list of users to display\n */\nconst renderSearchResults = async (searchResultsContainer, users) => {\n const {html, js} = await Templates.renderForPromise(templateNames.grader.searchResults, {users});\n Templates.replaceNodeContents(searchResultsContainer, html, js);\n};\n\n/**\n * Add click handlers to the buttons in the header of the grading interface.\n *\n * @param {HTMLElement} graderLayout\n * @param {Object} userPicker\n * @param {Function} saveGradeFunction\n * @param {Array} userList List of users for the grader.\n */\nconst registerEventListeners = (graderLayout, userPicker, saveGradeFunction, userList) => {\n const graderContainer = graderLayout.getContainer();\n const toggleSearchButton = graderContainer.querySelector(Selectors.buttons.toggleSearch);\n const searchInputContainer = graderContainer.querySelector(Selectors.regions.userSearchContainer);\n const searchInput = searchInputContainer.querySelector(Selectors.regions.userSearchInput);\n const bodyContainer = graderContainer.querySelector(Selectors.regions.bodyContainer);\n const userPickerContainer = graderContainer.querySelector(Selectors.regions.pickerRegion);\n const searchResultsContainer = graderContainer.querySelector(Selectors.regions.searchResultsContainer);\n\n graderContainer.addEventListener('click', (e) => {\n if (e.target.closest(Selectors.buttons.toggleFullscreen)) {\n e.stopImmediatePropagation();\n e.preventDefault();\n graderLayout.toggleFullscreen();\n\n return;\n }\n\n if (e.target.closest(Selectors.buttons.closeGrader)) {\n e.stopImmediatePropagation();\n e.preventDefault();\n\n graderLayout.close();\n\n return;\n }\n\n if (e.target.closest(Selectors.buttons.saveGrade)) {\n saveGradeFunction(userPicker.currentUser);\n }\n\n if (e.target.closest(Selectors.buttons.toggleSearch)) {\n if (toggleSearchButton.getAttribute('aria-expanded') === 'true') {\n // Search is open so let's close it.\n hideUserSearchInput(toggleSearchButton, searchInputContainer, searchInput);\n hideSearchResultContainer(bodyContainer, userPickerContainer, searchResultsContainer);\n searchResultsContainer.innerHTML = '';\n } else {\n // Search is closed so let's open it.\n showUserSearchInput(toggleSearchButton, searchInputContainer, searchInput);\n showSearchResultContainer(bodyContainer, userPickerContainer, searchResultsContainer);\n renderSearchResults(searchResultsContainer, userList);\n }\n\n return;\n }\n\n const selectUserButton = e.target.closest(Selectors.buttons.selectUser);\n if (selectUserButton) {\n const userId = selectUserButton.getAttribute('data-userid');\n const user = userList.find(user => user.id == userId);\n userPicker.setUserId(userId);\n userPicker.showUser(user);\n hideUserSearchInput(toggleSearchButton, searchInputContainer, searchInput);\n hideSearchResultContainer(bodyContainer, userPickerContainer, searchResultsContainer);\n searchResultsContainer.innerHTML = '';\n }\n });\n\n // Debounce the search input so that it only executes 300 milliseconds after the user has finished typing.\n searchInput.addEventListener('input', debounce(() => {\n const users = searchForUsers(userList, searchInput.value);\n renderSearchResults(searchResultsContainer, users);\n }, 300));\n};\n\n/**\n * Get the function used to save a user grade.\n *\n * @param {HTMLElement} root The container for the grader\n * @param {Function} setGradeForUser The function that will be called.\n * @return {Function}\n */\nconst getSaveUserGradeFunction = (root, setGradeForUser) => {\n return async(user) => {\n try {\n root.querySelector(Selectors.regions.gradingPanelErrors).innerHTML = '';\n const result = await setGradeForUser(\n user.id,\n root.querySelector(Selectors.values.sendStudentNotifications).value,\n root.querySelector(Selectors.regions.gradingPanel)\n );\n if (result.success) {\n addToast(await getString('grades:gradesavedfor', 'mod_forum', user));\n }\n if (result.failed) {\n displayGradingError(root, user, result.error);\n }\n\n return result;\n } catch (err) {\n displayGradingError(root, user, err);\n\n return failedUpdate(err);\n }\n };\n};\n\n/**\n * Display a grading error, typically from a failed save.\n *\n * @param {HTMLElement} root The container for the grader\n * @param {Object} user The user who was errored\n * @param {Object} err The details of the error\n */\nconst displayGradingError = async(root, user, err) => {\n const [\n {html, js},\n errorString\n ] = await Promise.all([\n Templates.renderForPromise(templateNames.grader.gradingPanel.error, {error: err}),\n await getString('grades:gradesavefailed', 'mod_forum', {error: err.message, ...user}),\n ]);\n\n Templates.replaceNodeContents(root.querySelector(Selectors.regions.gradingPanelErrors), html, js);\n addToast(errorString);\n};\n\n/**\n * Launch the grader interface with the specified parameters.\n *\n * @param {Function} getListOfUsers A function to get the list of users\n * @param {Function} getContentForUser A function to get the content for a specific user\n * @param {Function} getGradeForUser A function get the grade details for a specific user\n * @param {Function} setGradeForUser A function to set the grade for a specific user\n * @param\n */\nexport const launch = async(getListOfUsers, getContentForUser, getGradeForUser, setGradeForUser, {\n initialUserId = null, moduleName, courseName, courseUrl, sendStudentNotifications\n} = {}) => {\n\n // We need all of these functions to be executed in series, if one step runs before another the interface\n // will not work.\n const [\n graderLayout,\n {html, js},\n userList,\n ] = await Promise.all([\n createFullScreenWindow({fullscreen: false, showLoader: false}),\n\n Templates.renderForPromise(templateNames.grader.app, {\n moduleName,\n courseName,\n courseUrl,\n drawer: {show: true},\n defaultsendnotifications: sendStudentNotifications,\n }),\n getListOfUsers(),\n ]);\n const graderContainer = graderLayout.getContainer();\n\n const saveGradeFunction = getSaveUserGradeFunction(graderContainer, setGradeForUser);\n\n Templates.replaceNodeContents(graderContainer, html, js);\n const updateUserContent = getUpdateUserContentFunction(graderContainer, getContentForUser, getGradeForUser);\n\n const userIds = userList.map(user => user.id);\n const statusContainer = graderContainer.querySelector(Selectors.regions.statusContainer);\n // Fetch the userpicker for display.\n const userPicker = await getUserPicker(\n userList,\n user => {\n const renderContext = {\n status: null,\n index: userIds.indexOf(user.id) + 1,\n total: userList.length\n };\n Templates.render(templateNames.grader.status, renderContext).then(html => {\n statusContainer.innerHTML = html;\n return html;\n }).catch();\n updateUserContent(user);\n },\n saveGradeFunction,\n {\n initialUserId,\n },\n );\n\n // Register all event listeners.\n registerEventListeners(graderLayout, userPicker, saveGradeFunction, userList);\n\n // Display the newly created user picker.\n displayUserPicker(graderContainer, userPicker.rootNode);\n};\n\nexport {getGradingPanelFunctions};\n"],"file":"grader.min.js"} \ No newline at end of file diff --git a/mod/forum/amd/build/local/grades/local/grader/gradingpanel.min.js b/mod/forum/amd/build/local/grades/local/grader/gradingpanel.min.js index f3cd71291f0..3aae98cacd9 100644 --- a/mod/forum/amd/build/local/grades/local/grader/gradingpanel.min.js +++ b/mod/forum/amd/build/local/grades/local/grader/gradingpanel.min.js @@ -1,2 +1,2 @@ -define ("mod_forum/local/grades/local/grader/gradingpanel",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;var d="undefined"!=typeof window?window:"undefined"!=typeof self?self:"undefined"!=typeof global?global:{};function b(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function c(a){return function(){var c=this,d=arguments;return new Promise(function(e,f){var i=a.apply(c,d);function g(a){b(i,e,f,g,h,"next",a)}function h(a){b(i,e,f,g,h,"throw",a)}g(void 0)})}}var e=function(){var a=c(regeneratorRuntime.mark(function a(b,c,e,f,g){var h,i;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:h="".concat(e,"/grades/grader/gradingpanel");if(f){h+="/".concat(f)}a.next=4;return"function"==typeof d.define&&d.define.amd?new Promise(function(a,b){d.require([h],a,b)}):"undefined"!=typeof module&&module.exports&&"undefined"!=typeof require||"undefined"!=typeof module&&module.component&&d.require&&"component"===d.require.loader?Promise.resolve(require((h))):Promise.resolve(d[h]);case 4:i=a.sent;return a.abrupt("return",{getter:function(a){return i.fetchCurrentGrade(b,c,g,a)},setter:function(a,d){return i.storeCurrentGrade(b,c,g,a,d)}});case 6:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}();a.default=e;return a.default}); +define ("mod_forum/local/grades/local/grader/gradingpanel",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;var d="undefined"!=typeof window?window:"undefined"!=typeof self?self:"undefined"!=typeof global?global:{};function b(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function c(a){return function(){var c=this,d=arguments;return new Promise(function(e,f){var i=a.apply(c,d);function g(a){b(i,e,f,g,h,"next",a)}function h(a){b(i,e,f,g,h,"throw",a)}g(void 0)})}}var e=function(){var a=c(regeneratorRuntime.mark(function a(b,c,e,f,g){var h,i;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:h="".concat(e,"/grades/grader/gradingpanel");if(f){h+="/".concat(f)}a.next=4;return"function"==typeof d.define&&d.define.amd?new Promise(function(a,b){d.require([h],a,b)}):"undefined"!=typeof module&&module.exports&&"undefined"!=typeof require||"undefined"!=typeof module&&module.component&&d.require&&"component"===d.require.loader?Promise.resolve(require((h))):Promise.resolve(d[h]);case 4:i=a.sent;return a.abrupt("return",{getter:function(a){return i.fetchCurrentGrade(b,c,g,a)},setter:function(a,d,e){return i.storeCurrentGrade(b,c,g,a,d,e)}});case 6:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}();a.default=e;return a.default}); //# sourceMappingURL=gradingpanel.min.js.map diff --git a/mod/forum/amd/build/local/grades/local/grader/gradingpanel.min.js.map b/mod/forum/amd/build/local/grades/local/grader/gradingpanel.min.js.map index dc9cfa7e23d..a8fc06ee6ae 100644 --- a/mod/forum/amd/build/local/grades/local/grader/gradingpanel.min.js.map +++ b/mod/forum/amd/build/local/grades/local/grader/gradingpanel.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../../../src/local/grades/local/grader/gradingpanel.js"],"names":["component","context","gradingComponent","gradingSubtype","itemName","gradingMethodHandler","GradingMethod","getter","userId","fetchCurrentGrade","setter","formData","storeCurrentGrade"],"mappings":"6nBAoCe,WAAMA,CAAN,CAAiBC,CAAjB,CAA0BC,CAA1B,CAA4CC,CAA5C,CAA4DC,CAA5D,2FACPC,CADO,WACmBH,CADnB,gCAEX,GAAIC,CAAJ,CAAoB,CAChBE,CAAoB,aAAQF,CAAR,CACvB,CAJU,8FAMwBE,CANxB,mMAMwBA,CANxB,sBAMwBA,CANxB,UAMLC,CANK,iCAQJ,CACHC,MAAM,CAAE,SAACC,CAAD,QAAYF,CAAAA,CAAa,CAACG,iBAAd,CAAgCT,CAAhC,CAA2CC,CAA3C,CAAoDG,CAApD,CAA8DI,CAA9D,CAAZ,CADL,CAEHE,MAAM,CAAE,SAACF,CAAD,CAASG,CAAT,QAAsBL,CAAAA,CAAa,CAACM,iBAAd,CAAgCZ,CAAhC,CAA2CC,CAA3C,CAAoDG,CAApD,CAA8DI,CAA9D,CAAsEG,CAAtE,CAAtB,CAFL,CARI,0C","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 * Grading panel functions.\n *\n * @module mod_forum/local/grades/local/grader/gradingpnael\n * @package mod_forum\n * @copyright 2019 Andrew Nicols \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * Get the grade panel setter and getter for the current component.\n * This function dynamically pulls the relevant gradingpanel JS file defined in the grading method.\n * We do this because we do not know until execution time what the grading type is and we do not want to import unused files.\n *\n * @param {String} component The component being graded\n * @param {Number} context The contextid of the thing being graded\n * @param {String} gradingComponent The thing providing the grading type\n * @param {String} gradingSubtype The subtype fo the grading component\n * @param {String} itemName The name of the thing being graded\n * @return {Object}\n */\nexport default async(component, context, gradingComponent, gradingSubtype, itemName) => {\n let gradingMethodHandler = `${gradingComponent}/grades/grader/gradingpanel`;\n if (gradingSubtype) {\n gradingMethodHandler += `/${gradingSubtype}`;\n }\n\n const GradingMethod = await import(gradingMethodHandler);\n\n return {\n getter: (userId) => GradingMethod.fetchCurrentGrade(component, context, itemName, userId),\n setter: (userId, formData) => GradingMethod.storeCurrentGrade(component, context, itemName, userId, formData),\n };\n};\n\n"],"file":"gradingpanel.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../../../src/local/grades/local/grader/gradingpanel.js"],"names":["component","context","gradingComponent","gradingSubtype","itemName","gradingMethodHandler","GradingMethod","getter","userId","fetchCurrentGrade","setter","notifyStudent","formData","storeCurrentGrade"],"mappings":"6nBAoCe,WAAMA,CAAN,CAAiBC,CAAjB,CAA0BC,CAA1B,CAA4CC,CAA5C,CAA4DC,CAA5D,2FACPC,CADO,WACmBH,CADnB,gCAEX,GAAIC,CAAJ,CAAoB,CAChBE,CAAoB,aAAQF,CAAR,CACvB,CAJU,8FAMwBE,CANxB,mMAMwBA,CANxB,sBAMwBA,CANxB,UAMLC,CANK,iCAQJ,CACHC,MAAM,CAAE,SAACC,CAAD,QAAYF,CAAAA,CAAa,CAACG,iBAAd,CAAgCT,CAAhC,CAA2CC,CAA3C,CAAoDG,CAApD,CAA8DI,CAA9D,CAAZ,CADL,CAEHE,MAAM,CAAE,SAACF,CAAD,CAASG,CAAT,CAAwBC,CAAxB,QAAqCN,CAAAA,CAAa,CAACO,iBAAd,CACzCb,CADyC,CAC9BC,CAD8B,CACrBG,CADqB,CACXI,CADW,CACHG,CADG,CACYC,CADZ,CAArC,CAFL,CARI,0C","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 * Grading panel functions.\n *\n * @module mod_forum/local/grades/local/grader/gradingpnael\n * @package mod_forum\n * @copyright 2019 Andrew Nicols \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * Get the grade panel setter and getter for the current component.\n * This function dynamically pulls the relevant gradingpanel JS file defined in the grading method.\n * We do this because we do not know until execution time what the grading type is and we do not want to import unused files.\n *\n * @param {String} component The component being graded\n * @param {Number} context The contextid of the thing being graded\n * @param {String} gradingComponent The thing providing the grading type\n * @param {String} gradingSubtype The subtype fo the grading component\n * @param {String} itemName The name of the thing being graded\n * @return {Object}\n */\nexport default async(component, context, gradingComponent, gradingSubtype, itemName) => {\n let gradingMethodHandler = `${gradingComponent}/grades/grader/gradingpanel`;\n if (gradingSubtype) {\n gradingMethodHandler += `/${gradingSubtype}`;\n }\n\n const GradingMethod = await import(gradingMethodHandler);\n\n return {\n getter: (userId) => GradingMethod.fetchCurrentGrade(component, context, itemName, userId),\n setter: (userId, notifyStudent, formData) => GradingMethod.storeCurrentGrade(\n component, context, itemName, userId, notifyStudent, formData),\n };\n};\n\n"],"file":"gradingpanel.min.js"} \ No newline at end of file diff --git a/mod/forum/amd/build/local/grades/local/grader/selectors.min.js b/mod/forum/amd/build/local/grades/local/grader/selectors.min.js index c71a05712ad..fee7890c0a1 100644 --- a/mod/forum/amd/build/local/grades/local/grader/selectors.min.js +++ b/mod/forum/amd/build/local/grades/local/grader/selectors.min.js @@ -1,2 +1,2 @@ -define ("mod_forum/local/grades/local/grader/selectors",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;var b=function(a,b){return"[data-".concat(a,"=\"").concat(b,"\"]")},c={buttons:{toggleFullscreen:b("action","togglefullscreen"),closeGrader:b("action","closegrader"),saveGrade:b("action","savegrade"),selectUser:b("action","select-user"),toggleSearch:b("action","toggle-search")},regions:{bodyContainer:b("region","body-container"),moduleReplace:b("region","module_content"),pickerRegion:b("region","user_picker"),gradingPanel:b("region","grade"),gradingPanelContainer:b("region","grading-panel-container"),gradingPanelErrors:b("region","grade-errors"),searchResultsContainer:b("region","search-results-container"),statusContainer:b("region","status-container"),userSearchContainer:b("region","user-search-container"),userSearchInput:b("region","user-search-input")}};a.default=c;return a.default}); +define ("mod_forum/local/grades/local/grader/selectors",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;var b=function(a,b){return"[data-".concat(a,"=\"").concat(b,"\"]")},c={buttons:{toggleFullscreen:b("action","togglefullscreen"),closeGrader:b("action","closegrader"),saveGrade:b("action","savegrade"),selectUser:b("action","select-user"),toggleSearch:b("action","toggle-search")},regions:{bodyContainer:b("region","body-container"),moduleReplace:b("region","module_content"),pickerRegion:b("region","user_picker"),gradingPanel:b("region","grade"),gradingPanelContainer:b("region","grading-panel-container"),gradingPanelErrors:b("region","grade-errors"),searchResultsContainer:b("region","search-results-container"),statusContainer:b("region","status-container"),userSearchContainer:b("region","user-search-container"),userSearchInput:b("region","user-search-input")},values:{sendStudentNotifications:"[data-region=\"notification\"] input[type=\"radio\"]:checked"}};a.default=c;return a.default}); //# sourceMappingURL=selectors.min.js.map diff --git a/mod/forum/amd/build/local/grades/local/grader/selectors.min.js.map b/mod/forum/amd/build/local/grades/local/grader/selectors.min.js.map index f7ddb573d5f..bcd975c99bc 100644 --- a/mod/forum/amd/build/local/grades/local/grader/selectors.min.js.map +++ b/mod/forum/amd/build/local/grades/local/grader/selectors.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../../../src/local/grades/local/grader/selectors.js"],"names":["getDataSelector","name","value","buttons","toggleFullscreen","closeGrader","saveGrade","selectUser","toggleSearch","regions","bodyContainer","moduleReplace","pickerRegion","gradingPanel","gradingPanelContainer","gradingPanelErrors","searchResultsContainer","statusContainer","userSearchContainer","userSearchInput"],"mappings":"kKA8BMA,CAAAA,CAAe,CAAG,SAACC,CAAD,CAAOC,CAAP,CAAiB,CACrC,sBAAgBD,CAAhB,eAAyBC,CAAzB,OACH,C,GAEc,CACXC,OAAO,CAAE,CACLC,gBAAgB,CAAEJ,CAAe,CAAC,QAAD,CAAW,kBAAX,CAD5B,CAELK,WAAW,CAAEL,CAAe,CAAC,QAAD,CAAW,aAAX,CAFvB,CAGLM,SAAS,CAAEN,CAAe,CAAC,QAAD,CAAW,WAAX,CAHrB,CAILO,UAAU,CAAEP,CAAe,CAAC,QAAD,CAAW,aAAX,CAJtB,CAKLQ,YAAY,CAAER,CAAe,CAAC,QAAD,CAAW,eAAX,CALxB,CADE,CAQXS,OAAO,CAAE,CACLC,aAAa,CAAEV,CAAe,CAAC,QAAD,CAAW,gBAAX,CADzB,CAELW,aAAa,CAAEX,CAAe,CAAC,QAAD,CAAW,gBAAX,CAFzB,CAGLY,YAAY,CAAEZ,CAAe,CAAC,QAAD,CAAW,aAAX,CAHxB,CAILa,YAAY,CAAEb,CAAe,CAAC,QAAD,CAAW,OAAX,CAJxB,CAKLc,qBAAqB,CAAEd,CAAe,CAAC,QAAD,CAAW,yBAAX,CALjC,CAMLe,kBAAkB,CAAEf,CAAe,CAAC,QAAD,CAAW,cAAX,CAN9B,CAOLgB,sBAAsB,CAAEhB,CAAe,CAAC,QAAD,CAAW,0BAAX,CAPlC,CAQLiB,eAAe,CAAEjB,CAAe,CAAC,QAAD,CAAW,kBAAX,CAR3B,CASLkB,mBAAmB,CAAElB,CAAe,CAAC,QAAD,CAAW,uBAAX,CAT/B,CAULmB,eAAe,CAAEnB,CAAe,CAAC,QAAD,CAAW,mBAAX,CAV3B,CARE,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Define all of the selectors we will be using on the grading interface.\n *\n * @module mod_forum/local/grades/local/grader/selectors\n * @package mod_forum\n * @copyright 2019 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * A small helper function to build queryable data selectors.\n * @param {String} name\n * @param {String} value\n * @return {string}\n */\nconst getDataSelector = (name, value) => {\n return `[data-${name}=\"${value}\"]`;\n};\n\nexport default {\n buttons: {\n toggleFullscreen: getDataSelector('action', 'togglefullscreen'),\n closeGrader: getDataSelector('action', 'closegrader'),\n saveGrade: getDataSelector('action', 'savegrade'),\n selectUser: getDataSelector('action', 'select-user'),\n toggleSearch: getDataSelector('action', 'toggle-search')\n },\n regions: {\n bodyContainer: getDataSelector('region', 'body-container'),\n moduleReplace: getDataSelector('region', 'module_content'),\n pickerRegion: getDataSelector('region', 'user_picker'),\n gradingPanel: getDataSelector('region', 'grade'),\n gradingPanelContainer: getDataSelector('region', 'grading-panel-container'),\n gradingPanelErrors: getDataSelector('region', 'grade-errors'),\n searchResultsContainer: getDataSelector('region', 'search-results-container'),\n statusContainer: getDataSelector('region', 'status-container'),\n userSearchContainer: getDataSelector('region', 'user-search-container'),\n userSearchInput: getDataSelector('region', 'user-search-input')\n },\n};\n\n"],"file":"selectors.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../../../src/local/grades/local/grader/selectors.js"],"names":["getDataSelector","name","value","buttons","toggleFullscreen","closeGrader","saveGrade","selectUser","toggleSearch","regions","bodyContainer","moduleReplace","pickerRegion","gradingPanel","gradingPanelContainer","gradingPanelErrors","searchResultsContainer","statusContainer","userSearchContainer","userSearchInput","values","sendStudentNotifications"],"mappings":"kKA8BMA,CAAAA,CAAe,CAAG,SAACC,CAAD,CAAOC,CAAP,CAAiB,CACrC,sBAAgBD,CAAhB,eAAyBC,CAAzB,OACH,C,GAEc,CACXC,OAAO,CAAE,CACLC,gBAAgB,CAAEJ,CAAe,CAAC,QAAD,CAAW,kBAAX,CAD5B,CAELK,WAAW,CAAEL,CAAe,CAAC,QAAD,CAAW,aAAX,CAFvB,CAGLM,SAAS,CAAEN,CAAe,CAAC,QAAD,CAAW,WAAX,CAHrB,CAILO,UAAU,CAAEP,CAAe,CAAC,QAAD,CAAW,aAAX,CAJtB,CAKLQ,YAAY,CAAER,CAAe,CAAC,QAAD,CAAW,eAAX,CALxB,CADE,CAQXS,OAAO,CAAE,CACLC,aAAa,CAAEV,CAAe,CAAC,QAAD,CAAW,gBAAX,CADzB,CAELW,aAAa,CAAEX,CAAe,CAAC,QAAD,CAAW,gBAAX,CAFzB,CAGLY,YAAY,CAAEZ,CAAe,CAAC,QAAD,CAAW,aAAX,CAHxB,CAILa,YAAY,CAAEb,CAAe,CAAC,QAAD,CAAW,OAAX,CAJxB,CAKLc,qBAAqB,CAAEd,CAAe,CAAC,QAAD,CAAW,yBAAX,CALjC,CAMLe,kBAAkB,CAAEf,CAAe,CAAC,QAAD,CAAW,cAAX,CAN9B,CAOLgB,sBAAsB,CAAEhB,CAAe,CAAC,QAAD,CAAW,0BAAX,CAPlC,CAQLiB,eAAe,CAAEjB,CAAe,CAAC,QAAD,CAAW,kBAAX,CAR3B,CASLkB,mBAAmB,CAAElB,CAAe,CAAC,QAAD,CAAW,uBAAX,CAT/B,CAULmB,eAAe,CAAEnB,CAAe,CAAC,QAAD,CAAW,mBAAX,CAV3B,CARE,CAoBXoB,MAAM,CAAE,CACJC,wBAAwB,CAAE,8DADtB,CApBG,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Define all of the selectors we will be using on the grading interface.\n *\n * @module mod_forum/local/grades/local/grader/selectors\n * @package mod_forum\n * @copyright 2019 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * A small helper function to build queryable data selectors.\n * @param {String} name\n * @param {String} value\n * @return {string}\n */\nconst getDataSelector = (name, value) => {\n return `[data-${name}=\"${value}\"]`;\n};\n\nexport default {\n buttons: {\n toggleFullscreen: getDataSelector('action', 'togglefullscreen'),\n closeGrader: getDataSelector('action', 'closegrader'),\n saveGrade: getDataSelector('action', 'savegrade'),\n selectUser: getDataSelector('action', 'select-user'),\n toggleSearch: getDataSelector('action', 'toggle-search')\n },\n regions: {\n bodyContainer: getDataSelector('region', 'body-container'),\n moduleReplace: getDataSelector('region', 'module_content'),\n pickerRegion: getDataSelector('region', 'user_picker'),\n gradingPanel: getDataSelector('region', 'grade'),\n gradingPanelContainer: getDataSelector('region', 'grading-panel-container'),\n gradingPanelErrors: getDataSelector('region', 'grade-errors'),\n searchResultsContainer: getDataSelector('region', 'search-results-container'),\n statusContainer: getDataSelector('region', 'status-container'),\n userSearchContainer: getDataSelector('region', 'user-search-container'),\n userSearchInput: getDataSelector('region', 'user-search-input')\n },\n values: {\n sendStudentNotifications: '[data-region=\"notification\"] input[type=\"radio\"]:checked',\n }\n};\n\n"],"file":"selectors.min.js"} \ No newline at end of file diff --git a/mod/forum/amd/src/grades/grader.js b/mod/forum/amd/src/grades/grader.js index a8a2b20b0d2..eade66667ee 100644 --- a/mod/forum/amd/src/grades/grader.js +++ b/mod/forum/amd/src/grades/grader.js @@ -136,7 +136,8 @@ const launchWholeForumGrading = async(rootNode) => { initialUserId: data.initialuserid, moduleName: data.name, courseName: data.courseName, - courseUrl: relativeUrl('/course/view.php', {id: data.courseId}) + courseUrl: relativeUrl('/course/view.php', {id: data.courseId}), + sendStudentNotifications: data.sendStudentNotifications, } ); }; diff --git a/mod/forum/amd/src/local/grades/grader.js b/mod/forum/amd/src/local/grades/grader.js index c9166db8aac..65eb6a58e70 100644 --- a/mod/forum/amd/src/local/grades/grader.js +++ b/mod/forum/amd/src/local/grades/grader.js @@ -279,7 +279,11 @@ const getSaveUserGradeFunction = (root, setGradeForUser) => { return async(user) => { try { root.querySelector(Selectors.regions.gradingPanelErrors).innerHTML = ''; - const result = await setGradeForUser(user.id, root.querySelector(Selectors.regions.gradingPanel)); + const result = await setGradeForUser( + user.id, + root.querySelector(Selectors.values.sendStudentNotifications).value, + root.querySelector(Selectors.regions.gradingPanel) + ); if (result.success) { addToast(await getString('grades:gradesavedfor', 'mod_forum', user)); } @@ -323,9 +327,10 @@ const displayGradingError = async(root, user, err) => { * @param {Function} getContentForUser A function to get the content for a specific user * @param {Function} getGradeForUser A function get the grade details for a specific user * @param {Function} setGradeForUser A function to set the grade for a specific user + * @param */ export const launch = async(getListOfUsers, getContentForUser, getGradeForUser, setGradeForUser, { - initialUserId = null, moduleName, courseName, courseUrl + initialUserId = null, moduleName, courseName, courseUrl, sendStudentNotifications } = {}) => { // We need all of these functions to be executed in series, if one step runs before another the interface @@ -336,11 +341,13 @@ export const launch = async(getListOfUsers, getContentForUser, getGradeForUser, userList, ] = await Promise.all([ createFullScreenWindow({fullscreen: false, showLoader: false}), + Templates.renderForPromise(templateNames.grader.app, { moduleName, courseName, courseUrl, - drawer: {show: true} + drawer: {show: true}, + defaultsendnotifications: sendStudentNotifications, }), getListOfUsers(), ]); diff --git a/mod/forum/amd/src/local/grades/local/grader/gradingpanel.js b/mod/forum/amd/src/local/grades/local/grader/gradingpanel.js index 837a9d9ac82..c0460a57f00 100644 --- a/mod/forum/amd/src/local/grades/local/grader/gradingpanel.js +++ b/mod/forum/amd/src/local/grades/local/grader/gradingpanel.js @@ -44,7 +44,8 @@ export default async(component, context, gradingComponent, gradingSubtype, itemN return { getter: (userId) => GradingMethod.fetchCurrentGrade(component, context, itemName, userId), - setter: (userId, formData) => GradingMethod.storeCurrentGrade(component, context, itemName, userId, formData), + setter: (userId, notifyStudent, formData) => GradingMethod.storeCurrentGrade( + component, context, itemName, userId, notifyStudent, formData), }; }; diff --git a/mod/forum/amd/src/local/grades/local/grader/selectors.js b/mod/forum/amd/src/local/grades/local/grader/selectors.js index 0d3ccd24176..b2bd51fac38 100644 --- a/mod/forum/amd/src/local/grades/local/grader/selectors.js +++ b/mod/forum/amd/src/local/grades/local/grader/selectors.js @@ -52,5 +52,8 @@ export default { userSearchContainer: getDataSelector('region', 'user-search-container'), userSearchInput: getDataSelector('region', 'user-search-input') }, + values: { + sendStudentNotifications: '[data-region="notification"] input[type="radio"]:checked', + } }; diff --git a/mod/forum/classes/local/data_mappers/legacy/forum.php b/mod/forum/classes/local/data_mappers/legacy/forum.php index 92580743804..d2858022855 100644 --- a/mod/forum/classes/local/data_mappers/legacy/forum.php +++ b/mod/forum/classes/local/data_mappers/legacy/forum.php @@ -56,6 +56,7 @@ class forum { 'assesstimefinish' => $forum->get_assess_time_finish(), 'scale' => $forum->get_scale(), 'grade_forum' => $forum->get_grade_for_forum(), + 'sendstudentnotifications_forum' => $forum->should_notify_students_default_when_grade_for_forum(), 'maxbytes' => $forum->get_max_bytes(), 'maxattachments' => $forum->get_max_attachments(), 'forcesubscribe' => $forum->get_subscription_mode(), diff --git a/mod/forum/classes/local/entities/forum.php b/mod/forum/classes/local/entities/forum.php index 4da49b28a56..a7e0f9996ee 100644 --- a/mod/forum/classes/local/entities/forum.php +++ b/mod/forum/classes/local/entities/forum.php @@ -69,8 +69,10 @@ class forum { private $assesstimefinish; /** @var int $scale The rating scale */ private $scale; - /** @var int $gradeforum The grade for the forum when grading holistcally */ + /** @var int $gradeforum The grade for the forum when grading holistically */ private $gradeforum; + /** @var bool $sendstudentnotificationsforum Whether to notify students when the forum is graded holistically */ + private $sendstudentnotificationsforum; /** @var int $maxbytes Maximum attachment size */ private $maxbytes; /** @var int $maxattachments Maximum number of attachments */ @@ -123,7 +125,8 @@ class forum { * @param int $assesstimestart Timestamp to begin assessment * @param int $assesstimefinish Timestamp to end assessment * @param int $scale The rating scale - * @param int $forum_grade The holistic grade + * @param int $gradeforum The holistic grade + * @param bool $sendstudentnotificationsforum Default for whether to notify students when grade holistically * @param int $maxbytes Maximum attachment size * @param int $maxattachments Maximum number of attachments * @param int $forcesubscribe Does the forum force users to subscribe? @@ -158,6 +161,7 @@ class forum { int $assesstimefinish, int $scale, int $gradeforum, + bool $sendstudentnotificationsforum, int $maxbytes, int $maxattachments, int $forcesubscribe, @@ -191,6 +195,7 @@ class forum { $this->assesstimefinish = $assesstimefinish; $this->scale = $scale; $this->gradeforum = $gradeforum; + $this->sendstudentnotificationsforum = $sendstudentnotificationsforum; $this->maxbytes = $maxbytes; $this->maxattachments = $maxattachments; $this->forcesubscribe = $forcesubscribe; @@ -372,6 +377,15 @@ class forum { return $this->get_grade_for_forum() !== 0; } + /** + * Get the default for whether the students should be notified when grading holistically. + * + * @return bool + */ + public function should_notify_students_default_when_grade_for_forum(): bool { + return $this->sendstudentnotificationsforum; + } + /** * Get the maximum bytes. * diff --git a/mod/forum/classes/local/factories/entity.php b/mod/forum/classes/local/factories/entity.php index 0b252a638e9..9ca7a526e79 100644 --- a/mod/forum/classes/local/factories/entity.php +++ b/mod/forum/classes/local/factories/entity.php @@ -87,6 +87,7 @@ class entity { $record->assesstimefinish, $record->scale, $record->grade_forum, + $record->sendstudentnotifications_forum, $record->maxbytes, $record->maxattachments, $record->forcesubscribe, diff --git a/mod/forum/db/install.xml b/mod/forum/db/install.xml index f335ad36efe..7ebe1d73d3f 100644 --- a/mod/forum/db/install.xml +++ b/mod/forum/db/install.xml @@ -19,6 +19,7 @@ + diff --git a/mod/forum/db/upgrade.php b/mod/forum/db/upgrade.php index ac6f468bb4c..41ed1d7a4bf 100644 --- a/mod/forum/db/upgrade.php +++ b/mod/forum/db/upgrade.php @@ -242,5 +242,21 @@ function xmldb_forum_upgrade($oldversion) { upgrade_mod_savepoint(true, 2019100100, 'forum'); } + if ($oldversion < 2019100108) { + + // Define field sendstudentnotifications_forum to be added to forum. + $table = new xmldb_table('forum'); + $field = new xmldb_field('sendstudentnotifications_forum', XMLDB_TYPE_INTEGER, '4', null, XMLDB_NOTNULL, null, '0', + 'grade_forum'); + + // Conditionally launch add field sendstudentnotifications_forum. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Forum savepoint reached. + upgrade_mod_savepoint(true, 2019100108, 'forum'); + } + return true; } diff --git a/mod/forum/lang/en/forum.php b/mod/forum/lang/en/forum.php index ad90b326170..c853a2aa30b 100644 --- a/mod/forum/lang/en/forum.php +++ b/mod/forum/lang/en/forum.php @@ -639,6 +639,10 @@ $string['searchuserid'] = 'The Moodle ID of the author'; $string['searchwhichforums'] = 'Choose which forums to search'; $string['searchwords'] = 'These words can appear anywhere in the post'; $string['seeallposts'] = 'See all posts made by this user'; +$string['sendstudentnotificationsno'] = 'No'; +$string['sendstudentnotificationsyes'] = 'Yes, send notification to student'; +$string['sendstudentnotificationsdefault'] = 'Default setting for "Notify students"'; +$string['sendstudentnotificationsdefault_help'] = 'Set the default value for the "Notify students" checkbox on the grading form.'; $string['settings'] = 'Settings'; $string['shortpost'] = 'Short post'; $string['showingcountoftotaldiscussions'] = 'Showing {$a->count} of {$a->total} discussions'; diff --git a/mod/forum/mod_form.php b/mod/forum/mod_form.php index 64ae3f13555..9f66da442e9 100644 --- a/mod/forum/mod_form.php +++ b/mod/forum/mod_form.php @@ -235,6 +235,8 @@ class mod_forum_mod_form extends moodleform_mod { $gradefieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, 'grade'); $gradecatfieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, 'gradecat'); $gradepassfieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, 'gradepass'); + $sendstudentnotificationsfieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, + 'sendstudentnotifications'); // The advancedgradingmethod is different in that it is suffixed with an area name... which is not the // itemnumber. @@ -304,6 +306,14 @@ class mod_forum_mod_form extends moodleform_mod { $mform->setDefault($gradepassfieldname, ''); $mform->setType($gradepassfieldname, PARAM_RAW); $mform->hideIf($gradepassfieldname, "{$gradefieldname}[modgrade_type]", 'eq', 'none'); + + $mform->addElement( + 'selectyesno', + $sendstudentnotificationsfieldname, + get_string('sendstudentnotificationsdefault', 'forum') + ); + $mform->addHelpButton($sendstudentnotificationsfieldname, 'sendstudentnotificationsdefault', 'forum'); + $mform->hideIf($sendstudentnotificationsfieldname, "{$gradefieldname}[modgrade_type]", 'eq', 'none'); } function definition_after_data() { diff --git a/mod/forum/templates/grades/grade_button.mustache b/mod/forum/templates/grades/grade_button.mustache index 9e6398e9238..31c68ffd241 100644 --- a/mod/forum/templates/grades/grade_button.mustache +++ b/mod/forum/templates/grades/grade_button.mustache @@ -43,6 +43,8 @@ data-grading-component="{{gradingcomponent}}" data-grading-component-subtype="{{gradingcomponentsubtype}}" data-gradable-itemtype="forum" + {{#firstgradeduserid}}data-initialuserid="{{firstgradeduserid}}" {{/firstgradeduserid}} + {{#sendstudentnotifications}}data-send-student-notifications="1" {{/sendstudentnotifications}} > {{#str}}gradeusers, forum{{/str}} @@ -50,4 +52,4 @@ require(['mod_forum/grades/grader'], function(Grader) { Grader.registerLaunchListeners(); }); -{{/js}} +{{/js}} \ No newline at end of file diff --git a/mod/forum/templates/local/grades/local/grader/grading.mustache b/mod/forum/templates/local/grades/local/grader/grading.mustache index 65c12acc267..013164f4e79 100644 --- a/mod/forum/templates/local/grades/local/grader/grading.mustache +++ b/mod/forum/templates/local/grades/local/grader/grading.mustache @@ -103,5 +103,24 @@ +
+
+
+

+

{{#str}}notifications, core{{/str}}

+
+ + + {{#helpicon}} + {{>core/help_icon}} + {{/helpicon}} +
+
{{/drawercontent}} -{{/core/drawer}} +{{/core/drawer}} \ No newline at end of file diff --git a/mod/forum/tests/entities_forum_test.php b/mod/forum/tests/entities_forum_test.php index 40487cfd733..aaf9f7b5ea4 100644 --- a/mod/forum/tests/entities_forum_test.php +++ b/mod/forum/tests/entities_forum_test.php @@ -96,7 +96,7 @@ class mod_forum_entities_forum_testcase extends advanced_testcase { $lockdiscussionafter = 0; $duedate = 0; $cutoffdate = 0; - + $sendnotification = false; $forum = new forum_entity( $context, $coursemodule, @@ -113,6 +113,7 @@ class mod_forum_entities_forum_testcase extends advanced_testcase { $assesstimefinish, $scale, $gradeforum, + $sendnotification, $maxbytes, $maxattachments, $forcesubscribe, diff --git a/mod/forum/version.php b/mod/forum/version.php index 31e86d947c7..53fb946f3f4 100644 --- a/mod/forum/version.php +++ b/mod/forum/version.php @@ -24,6 +24,6 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2019100106; // The current module version (Date: YYYYMMDDXX) +$plugin->version = 2019100108; // The current module version (Date: YYYYMMDDXX) $plugin->requires = 2019051100; // Requires this Moodle version $plugin->component = 'mod_forum'; // Full name of the plugin (used for diagnostics) diff --git a/version.php b/version.php index 2132125ee23..45eb4a742a6 100644 --- a/version.php +++ b/version.php @@ -29,11 +29,9 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2019110800.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2019110800.01; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes. - $release = '3.8dev+ (Build: 20191108)'; // Human-friendly version name - $branch = '38'; // This version's branch. $maturity = MATURITY_ALPHA; // This version's maturity level.