diff --git a/Authors.md b/Authors.md new file mode 100644 index 0000000..0ce0728 --- /dev/null +++ b/Authors.md @@ -0,0 +1,7 @@ +# AUTHORS + +_Prevent Past or Future Dates_ would not be possible without generous contributions from our authors and funders. + +Christopher P Barnes, Director of the University of Florida Clinical Translational Science Institute, provides funding support for the UF CTSI REDCap team via UF CTSA Award and matching funds from the UF Office of Research. + +We want to thank our developers Michael Bentz mbentz@ufl.edu Kyle Chesney kyle.chesney@ufl.edu, Taryn Stoffs tls@ufl.edu, Philip Chase pbc@ufl.edu, and Christopher Barnes cpb@ufl.edu for their contributions to the project. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..590a191 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,21 @@ +# Change Log +All notable changes to Prevent Past or Future Dates will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). + +## [1.0.2] - 2021-04-15 +### Added +Add minimum REDCap and PHP version (Michael Bentz) + +### Changed +Remove eval() in js (Michael Bentz) + + +## [1.0.1] - 2021-04-12 +### Added + - Add Zenodo DOI to README (Kyle Chesney) + + +## [1.0.0] - 2021-04-12 +### Summary + - Initial release of Prevent Past or Future Dates + - Adds actions tags __@PREVENT-FUTUREDATE__ and __@PREVENT-PASTDATE__ that can be applied to date fields to disallow entry of dates in the future or past, respectively + - The action tags will _only_ apply to empty fields diff --git a/ExternalModule.php b/ExternalModule.php index b3946f7..9739663 100644 --- a/ExternalModule.php +++ b/ExternalModule.php @@ -1,6 +1,6 @@ initializeJavascriptModuleObject(); $this->tt_addToJavascriptModuleObject('preventFutureDateFields', json_encode($preventFutureDateFields)); $this->tt_addToJavascriptModuleObject('preventPastDateFields', json_encode($preventPastDateFields)); - $this->includeSource(ResourceType::JS, 'js/pastFutureDateTags.js'); + $this->includeSource(ResourceType::JS, 'js/preventPastOrFutureDates.js'); } } } diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bfce21b --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +Copyright 2021 University of Florida + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/README.md b/README.md index 555092d..6465a54 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,33 @@ -## Past Future Date Tags -This REDCap module adds actions tags __@PREVENT-FUTUREDATE__ and __@PREVENT-PASTDATE__ that can be applied to date fields-—i.e., a text field with date validation applied. The tags are mutually exclusive, applying both tags will not result in any date restrictions. To enforce todays date use the __@TODAY__ action tag instead. +# Prevent Past or Future Dates -### Scenarios +[![DOI](http://zenodo.org/badge/DOI/10.5281/zenodo.4681708.svg)](http://doi.org/10.5281/zenodo.4681708) -| | Applied Tags | Dates available in Datepicker | -| ------------- | ------------- | ------------- | -| Scenario 1 | @PREVENT-FUTUREDATE | past - today | -| Scenario 2 | @PREVENT-PASTDATE | today - future | -| Scenario 3 __NOT SUPPORTED__ | @PREVENT-FUTUREDATE && @PREVENT-PASTDATE | N/A - supplying both tags will not apply any date restrictions use __@TODAY__ instead | +This REDCap module adds actions tags __@PREVENT-FUTUREDATE__ and __@PREVENT-PASTDATE__ that can be applied to date fields - i.e., a text field with date validation applied - to disallow entry of dates in the future or past, respectively. The tags are mutually exclusive, applying both tags will not result in any date restrictions. To enforce today's date use the __@TODAY__ action tag instead. -### Expected Behavior -#### Date vs Datetime field -REDCap handles date fields and datetime fields slightly differently: -* If a datetime field fails validation and the datetimepicker is opened and loses focus, the input will be updated automatically to the closest valid date. -* If a date field fails validation and the datepicker is opened and loses focus, the input will not be updated automatically but will instead need to be updated manually. +![Prevent Future Date](img/prevent_futuredate_mdy.png) + +## Prerequisites +- REDCap >= 10.0.1 -#### Available dates -Dates available in the datepicker are as of the current day, it is not relative to a specific date nor when an instrument was saved. Therefore, revisiting an instrument with tagged date fields a day or more later can result in: +## Installation +- Clone this repo into to `/modules/prevent_past_or_future_dates_v0.0.0`. +- Go to **Control Center > Manage External Modules** and enable _Prevent Past or Future Dates_. +- For each project you want to use this module, go to the project home page, click on **Manage External Modules** link, and then enable _Prevent Past or Future Dates_ for that project. -| | Applied Tags | Original Date Selected | (Original) Dates available in Datepicker | (+1D) Dates available in Datepciker -| ------------- | ------------- | ------------- | ------------- | ------------- | -| Scenario 1 | @PREVENT-FUTUREDATE | 01/14/2021 | past - 01/14/2021 | past - 01/15/2021 | -| Scenario 2 | @PREVENT-PASTDATE | 01/14/2021 | 01/14/2021 - future | 01/15/2021 - future | \ No newline at end of file +## Expected Behavior + +### Scenarios + +| | Applied Tags | Dates available in Datepicker | +| ------------- | ------------- | ------------- | +| Scenario 1 | @PREVENT-FUTUREDATE | past - today | +| Scenario 2 | @PREVENT-PASTDATE | today - future | +| Scenario 3 __NOT SUPPORTED__ | @PREVENT-FUTUREDATE && @PREVENT-PASTDATE | N/A - supplying both tags will not apply any date restrictions use __@TODAY__ instead | + +### Existing Data +The action tags will _only_ apply to empty fields; if data already exists in a field when the form is loaded the action tag will _not_ apply to that field. This is to prevent annoyances when revisiting forms. + +### Date vs Datetime field +This module handles date fields and datetime fields slightly differently: +* If a datetime field fails validation and the datetimepicker is opened and loses focus, the input will be updated automatically to the closest valid date. +* If a date field fails validation and the datepicker is opened and loses focus, the input will not be updated automatically. A validation alert will display and the value will need to be updated manually. diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..6d7de6e --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.0.2 diff --git a/config.json b/config.json index 3386af0..8fa1f03 100644 --- a/config.json +++ b/config.json @@ -1,7 +1,7 @@ { - "name": "Past Future Date Tags", - "namespace": "PastFutureDateTags\\ExternalModule", - "description": "This module adds action tags to prevent entering past dates or future dates for date fields", + "name": "Prevent Past or Future Dates", + "namespace": "PreventPastOrFutureDates\\ExternalModule", + "description": "This module adds action tags to prevent entering past dates or future dates for date and datetime fields.", "permissions": [ "redcap_every_page_top" ], @@ -10,6 +10,20 @@ "name": "Michael Bentz", "email": "mbentz@ufl.edu", "institution": "University of Florida" + }, + { + "name": "Kyle Chesney", + "email": "kyle.chesney@ufl.edu", + "institution": "University of Florida" + }, + { + "name": "Taryn Stoffs", + "email": "tls@ufl.edu", + "institution": "University of Florida" } - ] + ], + "compatibility": { + "redcap-version-min": "9.5.0", + "php-version-min": "7.2.15" + } } diff --git a/examples/pfdt_test_project.xml b/examples/pfdt_test_project.xml new file mode 100644 index 0000000..8027c9a --- /dev/null +++ b/examples/pfdt_test_project.xml @@ -0,0 +1,116 @@ + + + + + past_future_date_tags + This file contains the metadata, events, and data for REDCap project "past_future_date_tags". + past_future_date_tags + 1 + + + 0 + 0 + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Study ID + + + prevent_futuredate_mdy + + + prevent_futuredate_dmy + + + prevent_futuredate_ymd + + + prevent_futuredate_mdy_HM + + + prevent_futuredate_dmy_HM + + + prevent_futuredate_ymd_HM + + + prevent_futuredate_mdy_HMS + + + prevent_futuredate_dmy_HMS + + + prevent_futuredate_ymd_HMS + + + prevent_pastdate_mdy + + + prevent_pastdate_dmy + + + prevent_pastdate_ymd + + + prevent_pastdate_mdy_HM + + + prevent_pastdate_dmy_HM + + + prevent_pastdate_ymd_HM + + + prevent_pastdate_mdy_HMS + + + prevent_pastdate_dmy_HMS + + + prevent_pastdate_ymd_HMS + + + Complete? + + + + Incomplete + Unverified + Complete + + + + \ No newline at end of file diff --git a/img/prevent_futuredate_mdy.png b/img/prevent_futuredate_mdy.png new file mode 100644 index 0000000..4fe56ae Binary files /dev/null and b/img/prevent_futuredate_mdy.png differ diff --git a/js/addActionTags.js b/js/addActionTags.js index 6aafa97..5d5245b 100644 --- a/js/addActionTags.js +++ b/js/addActionTags.js @@ -5,7 +5,7 @@ $(document).ready(function () { $('body').on('dialogopen', function (event, ui) { let $popup = $(event.target); - let module = ExternalModules['PastFutureDateTags'].ExternalModule; + let module = ExternalModules['PreventPastOrFutureDates'].ExternalModule; let futureDateTag = module.tt('futureDateTag'); let pastDateTag = module.tt('pastDateTag'); @@ -59,4 +59,4 @@ $(document).ready(function () { $new_action_tag.insertBefore($default_action_tag); } -}); \ No newline at end of file +}); diff --git a/js/pastFutureDateTags.js b/js/preventPastOrFutureDates.js similarity index 96% rename from js/pastFutureDateTags.js rename to js/preventPastOrFutureDates.js index 3db337f..73a6cf2 100644 --- a/js/pastFutureDateTags.js +++ b/js/preventPastOrFutureDates.js @@ -42,7 +42,7 @@ const reDates = /(\d{4}-\d{2}-\d{2}[^']*)/g; let isValidClick = false; $(document).ready(function () { - let module = ExternalModules['PastFutureDateTags'].ExternalModule; + let module = ExternalModules['PreventPastOrFutureDates'].ExternalModule; let preventFutureDateFields = JSON.parse(module.tt('preventFutureDateFields')); let preventPastDateFields = JSON.parse(module.tt('preventPastDateFields')); let attachedListeners = false; @@ -70,12 +70,17 @@ function applyDateRestrictions(dateFields, fn) { // Sets the maxDate option on the jQuery UI datepicker function preventFutureDate(field) { let $input = $(`#${field}-tr input`); - let val = $input.attr("onblur"); + let inputAttrVal = $input.attr("value"); + + // Prevent validation for vields that were set correctly before + if (inputAttrVal) return; + + let onBlur = $input.attr("onblur"); let dateFormat = $input.attr('fv'); let minDate = ''; // Maintain minDate if one is applied, otherwise leave blank try { - [minDate, _] = val.match(reDates); + [minDate, _] = onBlur.match(reDates); } catch (e) {} // REDCap uses both datepicker and datetimepicker resulting in odd behavior when applying min and max dates. @@ -102,12 +107,17 @@ function preventFutureDate(field) { // Sets the minDate option on the jQuery UI datepicker function preventPastDate(field) { let $input = $(`#${field}-tr input`); - let val = $input.attr("onblur"); + let inputAttrVal = $input.attr("value"); + + // Prevent validation for vields that were set correctly before + if (inputAttrVal) return; + + let onBlur = $input.attr("onblur"); let dateFormat = $input.attr('fv'); let maxDate = ''; // Maintain maxDate if one is applied, otherwise leave blank try { - [_, maxDate] = val.match(reDates); + [_, maxDate] = onBlur.match(reDates); } catch (e) {} @@ -161,7 +171,7 @@ function validate(ob, min, max, returntype, texttype, regexVal, returnFocus) { if (ob.value == '' || $.inArray(ob.value, missing_data_codes) !== -1) { ob.style.fontWeight = 'normal'; ob.style.backgroundColor='#FFFFFF'; - console.log('is blank or missing data code'); + //console.log('is blank or missing data code'); return true; } origVal = ob.value; @@ -236,7 +246,8 @@ function validate(ob, min, max, returntype, texttype, regexVal, returnFocus) { } // Evaluate value with regex - eval('var regexVal2 = '+regexVal+';'); + // Remove leading and trailing '/' + var regexVal2 = new RegExp(regexVal.slice(1,-1)); if (regexVal2.test(ob.value)) { // Passed the regex test!