diff --git a/amd/build/datehelper.min.js b/amd/build/datehelper.min.js new file mode 100644 index 0000000..d3e844b --- /dev/null +++ b/amd/build/datehelper.min.js @@ -0,0 +1,3 @@ +define("report_editdates/datehelper",["exports","core/modal_factory","core/str"],(function(_exports,ModalFactory,_str){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,ModalFactory=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(ModalFactory);const farawayDate="December 31, 2099 23:59:59";let datetimeSelectors={},startDate={},endDate=new Date(farawayDate);_exports.init=()=>{datetimeSelectors=document.querySelectorAll("div[data-fieldtype='date_time_selector']");let courseStart=datetimeSelectors.item(0),courseEnd=datetimeSelectors.item(1);startDate=getDate(courseStart),courseEnd.querySelector("input[type='checkbox']").checked&&(endDate=getDate(courseEnd)),courseStart.addEventListener("change",startDateChanged),courseEnd.addEventListener("change",endDateChanged);for(const date of datetimeSelectors.entries())date[0]>1&&date[1].addEventListener("change",modDateChanged);M.form.dateselector.calendar.on("selectionChange",updateDates,!1)};const updateDates=()=>{let datepicker=M.form.dateselector.currentowner;null!==datepicker&&setTimeout((function(){let datetimeSelector=datepicker.calendarimage.getDOMNode().closest("div[data-fieldtype='date_time_selector']");datetimeSelector===datetimeSelectors.item(0)?startDateChanged():datetimeSelector===datetimeSelectors.item(1)?endDateChanged():checkDateRange(datetimeSelector)}),100)},startDateChanged=()=>{startDate=adjustDates(),checkAllDatesRange()},endDateChanged=()=>{let endDateSelector=datetimeSelectors.item(1),optional=endDateSelector.querySelector("input[type='checkbox']");endDate=optional.checked?getDate(endDateSelector):new Date(farawayDate),checkAllDatesRange()},modDateChanged=ev=>{let container=ev.target.closest("div[data-fieldtype='date_time_selector']");checkDateRange(container)},getDate=dateEl=>{let year=dateEl.querySelector(".fdate_time_selector > div:nth-child(3) select"),month=dateEl.querySelector(".fdate_time_selector > div:nth-child(2) select"),day=dateEl.querySelector(".fdate_time_selector > div:nth-child(1) select"),hour=dateEl.querySelector(".fdate_time_selector > div:nth-child(4) select"),minute=dateEl.querySelector(".fdate_time_selector > div:nth-child(5) select");return new Date(year.options[year.selectedIndex].value,month.options[month.selectedIndex].value-1,day.options[day.selectedIndex].value,hour.options[hour.selectedIndex].value,minute.options[minute.selectedIndex].value)},setDate=(dateEl,date)=>{let year=dateEl.querySelector(".fdate_time_selector > div:nth-child(3) select"),month=dateEl.querySelector(".fdate_time_selector > div:nth-child(2) select"),day=dateEl.querySelector(".fdate_time_selector > div:nth-child(1) select"),hour=dateEl.querySelector(".fdate_time_selector > div:nth-child(4) select"),minute=dateEl.querySelector(".fdate_time_selector > div:nth-child(5) select");year.querySelector("[value='"+date.getFullYear()+"']").selected="selected",month.querySelector("[value='"+(date.getMonth()+1)+"']").selected="selected",day.querySelector("[value='"+date.getDate()+"']").selected="selected",hour.querySelector("[value='"+date.getHours()+"']").selected="selected",minute.querySelector("[value='"+date.getMinutes()+"']").selected="selected"},checkAllDatesRange=()=>{let datesOutOfRange=0;for(const date of datetimeSelectors.entries())if(date[0]>1){let optional=date[1].querySelector("input[type='checkbox']");(null===optional||optional.checked)&&(datesOutOfRange+=checkDateRange(date[1]))}return datesOutOfRange>0&&ModalFactory.create({type:ModalFactory.types.DEFAULT,title:(0,_str.get_string)("datesoutofrange_title","report_editdates"),body:(0,_str.get_string)("datesoutofrange_body","report_editdates")}).then((modal=>(modal.show(),modal)))},checkDateRange=el=>{let outOfRange=!1,modDate=getDate(el);if(modDate>endDate||modDate{if(!document.getElementById("id_movealldates").checked)return startDate;let newStart=getDate(datetimeSelectors.item(0)),dst1=startDate.getTimezoneOffset()-newStart.getTimezoneOffset(),change=newStart-startDate+6e4*dst1;for(const date of datetimeSelectors.entries())if(date[0]>0){let optional=date[1].querySelector("input[type='checkbox']");if(null===optional||optional.checked){let currDate=getDate(date[1]),newDate=new Date(currDate.getTime()+change),dst2=newDate.getTimezoneOffset()-currDate.getTimezoneOffset();newDate=new Date(newDate.getTime()+6e4*dst2),setDate(date[1],newDate)}}let endDateSelector=datetimeSelectors.item(1),optional=endDateSelector.querySelector("input[type='checkbox']");return endDate=optional.checked?getDate(endDateSelector):new Date(farawayDate),newStart}})); + +//# sourceMappingURL=datehelper.min.js.map \ No newline at end of file diff --git a/amd/build/datehelper.min.js.map b/amd/build/datehelper.min.js.map new file mode 100644 index 0000000..d6dc565 --- /dev/null +++ b/amd/build/datehelper.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"datehelper.min.js","sources":["../src/datehelper.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * AMD module to help editing course dates\n *\n * @module report_editdates/datehelper\n * @copyright 2022 Te Wānanga o Aotearoa\n */\n\nimport * as ModalFactory from 'core/modal_factory';\nimport {get_string as getString} from 'core/str';\n\nconst classOutOfRange = \"outofrange\";\nconst farawayDate = \"December 31, 2099 23:59:59\";\nlet datetimeSelectors = {};\nlet startDate = {};\nlet endDate = new Date(farawayDate);\n\n/**\n * Initialiser function.\n */\nexport const init = () => {\n // Give our variables values.\n datetimeSelectors = document.querySelectorAll(\"div[data-fieldtype='date_time_selector']\");\n let courseStart = datetimeSelectors.item(0);\n let courseEnd = datetimeSelectors.item(1);\n startDate = getDate(courseStart);\n let optional = courseEnd.querySelector(\"input[type='checkbox']\");\n if (optional.checked) {\n endDate = getDate(courseEnd);\n }\n\n // Add some event Listeners.\n courseStart.addEventListener(\"change\", startDateChanged);\n courseEnd.addEventListener(\"change\", endDateChanged);\n // Activity module date time selector events.\n for (const date of datetimeSelectors.entries()) {\n if (date[0] > 1) {\n date[1].addEventListener(\"change\", modDateChanged);\n }\n }\n // Date picker event.\n M.form.dateselector.calendar.on('selectionChange', updateDates, false);\n};\n\n/**\n * A date has been changed using the date selector calendar.\n */\nconst updateDates = () => {\n let datepicker = M.form.dateselector.currentowner;\n if (null === datepicker) {\n return;\n }\n // Todo: is there a better way to ensure this runs after the selects have been updated?\n setTimeout(\n function() {\n let el = datepicker.calendarimage.getDOMNode();\n let datetimeSelector = el.closest(\"div[data-fieldtype='date_time_selector']\");\n if (datetimeSelector === datetimeSelectors.item(0)) {\n startDateChanged();\n } else if (datetimeSelector === datetimeSelectors.item(1)) {\n endDateChanged();\n } else {\n checkDateRange(datetimeSelector);\n }\n },\n 100\n );\n};\n\n/**\n * The start date of the course has been changed.\n * Adjust all other dates accordingly if desired.\n */\nconst startDateChanged = () => {\n startDate = adjustDates();\n checkAllDatesRange();\n};\n\n/**\n * The end date of the course has been changed.\n * Check if any activity module dates are after this date.\n */\nconst endDateChanged = () => {\n let endDateSelector = datetimeSelectors.item(1);\n let optional = endDateSelector.querySelector(\"input[type='checkbox']\");\n if (optional.checked) {\n endDate = getDate(endDateSelector);\n } else {\n endDate = new Date(farawayDate);\n }\n checkAllDatesRange();\n};\n\n/**\n * An activity module date has been changed.\n * Check and warn if it is now after the course end date.\n * @param {Event} ev\n */\nconst modDateChanged = (ev) => {\n // Check against course end date.\n let container = ev.target.closest(\"div[data-fieldtype='date_time_selector']\");\n\n checkDateRange(container);\n};\n\n/**\n * Get a Date object from the select DOM elements.\n * @param {HTMLElement} dateEl\n * @returns {Date}\n */\nconst getDate = (dateEl) => {\n let year = dateEl.querySelector(\".fdate_time_selector > div:nth-child(3) select\");\n let month = dateEl.querySelector(\".fdate_time_selector > div:nth-child(2) select\");\n let day = dateEl.querySelector(\".fdate_time_selector > div:nth-child(1) select\");\n let hour = dateEl.querySelector(\".fdate_time_selector > div:nth-child(4) select\");\n let minute = dateEl.querySelector(\".fdate_time_selector > div:nth-child(5) select\");\n return new Date(\n year.options[year.selectedIndex].value,\n month.options[month.selectedIndex].value - 1,\n day.options[day.selectedIndex].value,\n hour.options[hour.selectedIndex].value,\n minute.options[minute.selectedIndex].value,\n );\n};\n\n/**\n * Update the select DOM elements with a given Date object.\n * @param {HTMLElement} dateEl\n * @param {Date} date\n */\nconst setDate = (dateEl, date) => {\n let year = dateEl.querySelector(\".fdate_time_selector > div:nth-child(3) select\");\n let month = dateEl.querySelector(\".fdate_time_selector > div:nth-child(2) select\");\n let day = dateEl.querySelector(\".fdate_time_selector > div:nth-child(1) select\");\n let hour = dateEl.querySelector(\".fdate_time_selector > div:nth-child(4) select\");\n let minute = dateEl.querySelector(\".fdate_time_selector > div:nth-child(5) select\");\n year.querySelector(\"[value='\" + date.getFullYear() + \"']\").selected = \"selected\";\n month.querySelector(\"[value='\" + (date.getMonth() + 1) + \"']\").selected = \"selected\";\n day.querySelector(\"[value='\" + date.getDate() + \"']\").selected = \"selected\";\n hour.querySelector(\"[value='\" + date.getHours() + \"']\").selected = \"selected\";\n minute.querySelector(\"[value='\" + date.getMinutes() + \"']\").selected = \"selected\";\n};\n\n/**\n * Check if the date change has caused any dates to be outside the course start and end.\n * @returns {boolean|*}\n */\nconst checkAllDatesRange = () => {\n let datesOutOfRange = 0;\n // Loop dates and if they are enabled then check the date range.\n for (const date of datetimeSelectors.entries()) {\n if (date[0] > 1) {\n let optional = date[1].querySelector(\"input[type='checkbox']\");\n if (optional === null || optional.checked) {\n datesOutOfRange += checkDateRange(date[1]);\n }\n }\n }\n\n // Warn the person about it.\n if (datesOutOfRange > 0) {\n return ModalFactory.create({\n type: ModalFactory.types.DEFAULT,\n title: getString('datesoutofrange_title', 'report_editdates'),\n body: getString('datesoutofrange_body', 'report_editdates')\n })\n .then(modal => {\n modal.show();\n return modal;\n });\n }\n\n return false;\n};\n\n/**\n * Compare the date with the course end date. Toggle CSS class of element.\n * @param {HTMLElement} el\n * @returns {boolean}\n */\nconst checkDateRange = (el) => {\n let outOfRange = false;\n let modDate = getDate(el);\n if (modDate > endDate || modDate < startDate) {\n el.parentNode.classList.add(classOutOfRange);\n for (const input of el.querySelectorAll(\".form-group\").values()) {\n input.classList.add(classOutOfRange);\n }\n outOfRange = true;\n } else {\n el.parentNode.classList.remove(classOutOfRange);\n for (const input of el.querySelectorAll(\".form-group\").values()) {\n input.classList.remove(classOutOfRange);\n }\n }\n return outOfRange;\n};\n\n/**\n * Change the enabled dates by the amount the start date has been shifted.\n * @returns {Date}\n */\nconst adjustDates = () => {\n if (!document.getElementById(\"id_movealldates\").checked) {\n return startDate;\n }\n // Determine the change.\n let newStart = getDate(datetimeSelectors.item(0));\n let dst1 = startDate.getTimezoneOffset() - newStart.getTimezoneOffset(); // Account for daylight savings shift.\n let change = newStart - startDate + (dst1 * 60000);\n\n // Update all enabled dates.\n for (const date of datetimeSelectors.entries()) {\n if (date[0] > 0) {\n let optional = date[1].querySelector(\"input[type='checkbox']\");\n if (optional === null || optional.checked) {\n let currDate = getDate(date[1]);\n let newDate = new Date(currDate.getTime() + change);\n let dst2 = newDate.getTimezoneOffset() - currDate.getTimezoneOffset();\n newDate = new Date(newDate.getTime() + (dst2 * 60000));\n setDate(date[1], newDate);\n }\n }\n }\n\n // Update the stored value for the end date.\n let endDateSelector = datetimeSelectors.item(1);\n let optional = endDateSelector.querySelector(\"input[type='checkbox']\");\n if (optional.checked) {\n endDate = getDate(endDateSelector);\n } else {\n endDate = new Date(farawayDate);\n }\n\n return newStart;\n};\n"],"names":["farawayDate","datetimeSelectors","startDate","endDate","Date","document","querySelectorAll","courseStart","item","courseEnd","getDate","querySelector","checked","addEventListener","startDateChanged","endDateChanged","date","entries","modDateChanged","M","form","dateselector","calendar","on","updateDates","datepicker","currentowner","setTimeout","datetimeSelector","calendarimage","getDOMNode","closest","checkDateRange","adjustDates","checkAllDatesRange","endDateSelector","optional","ev","container","target","dateEl","year","month","day","hour","minute","options","selectedIndex","value","setDate","getFullYear","selected","getMonth","getHours","getMinutes","datesOutOfRange","ModalFactory","create","type","types","DEFAULT","title","body","then","modal","show","el","outOfRange","modDate","parentNode","classList","add","input","values","remove","getElementById","newStart","dst1","getTimezoneOffset","change","currDate","newDate","getTime","dst2"],"mappings":"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"} \ No newline at end of file diff --git a/amd/src/datehelper.js b/amd/src/datehelper.js new file mode 100644 index 0000000..65a90b1 --- /dev/null +++ b/amd/src/datehelper.js @@ -0,0 +1,250 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * AMD module to help editing course dates + * + * @module report_editdates/datehelper + * @copyright 2022 Te Wānanga o Aotearoa + */ + +import * as ModalFactory from 'core/modal_factory'; +import {get_string as getString} from 'core/str'; + +const classOutOfRange = "outofrange"; +const farawayDate = "December 31, 2099 23:59:59"; +let datetimeSelectors = {}; +let startDate = {}; +let endDate = new Date(farawayDate); + +/** + * Initialiser function. + */ +export const init = () => { + // Give our variables values. + datetimeSelectors = document.querySelectorAll("div[data-fieldtype='date_time_selector']"); + let courseStart = datetimeSelectors.item(0); + let courseEnd = datetimeSelectors.item(1); + startDate = getDate(courseStart); + let optional = courseEnd.querySelector("input[type='checkbox']"); + if (optional.checked) { + endDate = getDate(courseEnd); + } + + // Add some event Listeners. + courseStart.addEventListener("change", startDateChanged); + courseEnd.addEventListener("change", endDateChanged); + // Activity module date time selector events. + for (const date of datetimeSelectors.entries()) { + if (date[0] > 1) { + date[1].addEventListener("change", modDateChanged); + } + } + // Date picker event. + M.form.dateselector.calendar.on('selectionChange', updateDates, false); +}; + +/** + * A date has been changed using the date selector calendar. + */ +const updateDates = () => { + let datepicker = M.form.dateselector.currentowner; + if (null === datepicker) { + return; + } + // Todo: is there a better way to ensure this runs after the selects have been updated? + setTimeout( + function() { + let el = datepicker.calendarimage.getDOMNode(); + let datetimeSelector = el.closest("div[data-fieldtype='date_time_selector']"); + if (datetimeSelector === datetimeSelectors.item(0)) { + startDateChanged(); + } else if (datetimeSelector === datetimeSelectors.item(1)) { + endDateChanged(); + } else { + checkDateRange(datetimeSelector); + } + }, + 100 + ); +}; + +/** + * The start date of the course has been changed. + * Adjust all other dates accordingly if desired. + */ +const startDateChanged = () => { + startDate = adjustDates(); + checkAllDatesRange(); +}; + +/** + * The end date of the course has been changed. + * Check if any activity module dates are after this date. + */ +const endDateChanged = () => { + let endDateSelector = datetimeSelectors.item(1); + let optional = endDateSelector.querySelector("input[type='checkbox']"); + if (optional.checked) { + endDate = getDate(endDateSelector); + } else { + endDate = new Date(farawayDate); + } + checkAllDatesRange(); +}; + +/** + * An activity module date has been changed. + * Check and warn if it is now after the course end date. + * @param {Event} ev + */ +const modDateChanged = (ev) => { + // Check against course end date. + let container = ev.target.closest("div[data-fieldtype='date_time_selector']"); + + checkDateRange(container); +}; + +/** + * Get a Date object from the select DOM elements. + * @param {HTMLElement} dateEl + * @returns {Date} + */ +const getDate = (dateEl) => { + let year = dateEl.querySelector(".fdate_time_selector > div:nth-child(3) select"); + let month = dateEl.querySelector(".fdate_time_selector > div:nth-child(2) select"); + let day = dateEl.querySelector(".fdate_time_selector > div:nth-child(1) select"); + let hour = dateEl.querySelector(".fdate_time_selector > div:nth-child(4) select"); + let minute = dateEl.querySelector(".fdate_time_selector > div:nth-child(5) select"); + return new Date( + year.options[year.selectedIndex].value, + month.options[month.selectedIndex].value - 1, + day.options[day.selectedIndex].value, + hour.options[hour.selectedIndex].value, + minute.options[minute.selectedIndex].value, + ); +}; + +/** + * Update the select DOM elements with a given Date object. + * @param {HTMLElement} dateEl + * @param {Date} date + */ +const setDate = (dateEl, date) => { + let year = dateEl.querySelector(".fdate_time_selector > div:nth-child(3) select"); + let month = dateEl.querySelector(".fdate_time_selector > div:nth-child(2) select"); + let day = dateEl.querySelector(".fdate_time_selector > div:nth-child(1) select"); + let hour = dateEl.querySelector(".fdate_time_selector > div:nth-child(4) select"); + let minute = dateEl.querySelector(".fdate_time_selector > div:nth-child(5) select"); + year.querySelector("[value='" + date.getFullYear() + "']").selected = "selected"; + month.querySelector("[value='" + (date.getMonth() + 1) + "']").selected = "selected"; + day.querySelector("[value='" + date.getDate() + "']").selected = "selected"; + hour.querySelector("[value='" + date.getHours() + "']").selected = "selected"; + minute.querySelector("[value='" + date.getMinutes() + "']").selected = "selected"; +}; + +/** + * Check if the date change has caused any dates to be outside the course start and end. + * @returns {boolean|*} + */ +const checkAllDatesRange = () => { + let datesOutOfRange = 0; + // Loop dates and if they are enabled then check the date range. + for (const date of datetimeSelectors.entries()) { + if (date[0] > 1) { + let optional = date[1].querySelector("input[type='checkbox']"); + if (optional === null || optional.checked) { + datesOutOfRange += checkDateRange(date[1]); + } + } + } + + // Warn the person about it. + if (datesOutOfRange > 0) { + return ModalFactory.create({ + type: ModalFactory.types.DEFAULT, + title: getString('datesoutofrange_title', 'report_editdates'), + body: getString('datesoutofrange_body', 'report_editdates') + }) + .then(modal => { + modal.show(); + return modal; + }); + } + + return false; +}; + +/** + * Compare the date with the course end date. Toggle CSS class of element. + * @param {HTMLElement} el + * @returns {boolean} + */ +const checkDateRange = (el) => { + let outOfRange = false; + let modDate = getDate(el); + if (modDate > endDate || modDate < startDate) { + el.parentNode.classList.add(classOutOfRange); + for (const input of el.querySelectorAll(".form-group").values()) { + input.classList.add(classOutOfRange); + } + outOfRange = true; + } else { + el.parentNode.classList.remove(classOutOfRange); + for (const input of el.querySelectorAll(".form-group").values()) { + input.classList.remove(classOutOfRange); + } + } + return outOfRange; +}; + +/** + * Change the enabled dates by the amount the start date has been shifted. + * @returns {Date} + */ +const adjustDates = () => { + if (!document.getElementById("id_movealldates").checked) { + return startDate; + } + // Determine the change. + let newStart = getDate(datetimeSelectors.item(0)); + let dst1 = startDate.getTimezoneOffset() - newStart.getTimezoneOffset(); // Account for daylight savings shift. + let change = newStart - startDate + (dst1 * 60000); + + // Update all enabled dates. + for (const date of datetimeSelectors.entries()) { + if (date[0] > 0) { + let optional = date[1].querySelector("input[type='checkbox']"); + if (optional === null || optional.checked) { + let currDate = getDate(date[1]); + let newDate = new Date(currDate.getTime() + change); + let dst2 = newDate.getTimezoneOffset() - currDate.getTimezoneOffset(); + newDate = new Date(newDate.getTime() + (dst2 * 60000)); + setDate(date[1], newDate); + } + } + } + + // Update the stored value for the end date. + let endDateSelector = datetimeSelectors.item(1); + let optional = endDateSelector.querySelector("input[type='checkbox']"); + if (optional.checked) { + endDate = getDate(endDateSelector); + } else { + endDate = new Date(farawayDate); + } + + return newStart; +}; diff --git a/form.php b/form.php index aa6a258..04d3782 100644 --- a/form.php +++ b/form.php @@ -69,6 +69,9 @@ public function definition() { $mform->addElement('header', 'coursestartdateheader', get_string('coursestartdateheader', 'report_editdates')); $mform->setExpanded('coursestartdateheader', false); + $mform->addElement('checkbox', 'movealldates', get_string('movealldates', 'report_editdates')); + $mform->addHelpButton('movealldates', 'movealldates', 'report_editdates'); + $mform->addElement('date_time_selector', 'coursestartdate', get_string('startdate')); $mform->addHelpButton('coursestartdate', 'startdate'); $mform->setDefault('coursestartdate', $course->startdate); @@ -190,11 +193,15 @@ public function definition() { if ($mod && ($cmdatesettings = $mod->get_settings($cm))) { // Added activity name on the form. foreach ($cmdatesettings as $cmdatetype => $cmdatesetting) { + $dateoutofrange = ($cmdatesetting->currentvalue > $course->enddate && $course->enddate != 0) || + $cmdatesetting->currentvalue < $course->startdate && + $cmdatesetting->currentvalue != 0; + $dateoutofrangeclass = $dateoutofrange ? 'outofrange' : ''; $elname = 'date_mod_'.$cm->id.'_'.$cmdatetype; $mform->addElement($cmdatesetting->type, $elname, $cmdatesetting->label, array( 'optional' => $cmdatesetting->isoptional, - 'step' => $cmdatesetting->getstep)); + 'step' => $cmdatesetting->getstep), ['class' => $dateoutofrangeclass]); $mform->setDefault($elname, $cmdatesetting->currentvalue); $timeline[$cm->id] = array_merge($timeline[$cm->id], array($cmdatesetting->label => $cmdatesetting->currentvalue)); diff --git a/index.php b/index.php index edef980..a799cea 100644 --- a/index.php +++ b/index.php @@ -239,4 +239,5 @@ $mform->display(); +$PAGE->requires->js_call_amd('report_editdates/datehelper', 'init'); echo $OUTPUT->footer(); diff --git a/lang/en/report_editdates.php b/lang/en/report_editdates.php index 8970b88..dc37f2a 100644 --- a/lang/en/report_editdates.php +++ b/lang/en/report_editdates.php @@ -56,3 +56,10 @@ $string['editrestrictedaccess'] = 'Edit restricted access (opens a new window)'; $string['event:reportviewed'] = 'Edit dates report viewed'; $string['privacy:metadata'] = 'The Dates plugin does not store any personal data.'; +$string['datesoutofrange_title'] = 'Uh oh'; +$string['datesoutofrange_body'] = 'There are one or more dates that are either after the end of the course or +before the start of the course. You may want to check the dates on this page.'; +$string['movealldates'] = 'Move all other dates'; +$string['movealldates_help'] = 'When you change the course start date, all other enabled dates on this page +(including the course end date) will be changed by the same amount. You will still need to save the changes.'; + diff --git a/styles.css b/styles.css index 054bbb5..ee642e1 100644 --- a/styles.css +++ b/styles.css @@ -75,3 +75,13 @@ border-radius: 5px; padding: 2px; } + +.path-report-editdates .outofrange select { + color: red; +} + +.path-report-editdates div.row.outofrange .col-form-label::after { + content: "\f071"; + font-family: "fontawesome"; + color: orange; +} \ No newline at end of file