Bug 28937: Use Flatpickr on circulation and patron pages

This patch replaces the use of jQueryUI's datepicker on circulation and
patron-related pages.

The patch modifies Flatpickr's default configuration (in calendar.inc)
so that it has the following features:

 - A Flatpickr input with a "futuredate" class will require that the
   selected date be after today.
 - The Flatpickr input field will be wrapped in a container to
   facilitate better CSS styling.
 - Generic handling of paired date fields is enabled using
   ".flatpickrfrom" and ".flatpickrto" field classes. This mimics the
   same feature we have for jQueryUI datepickers using ".datepickerfrom"
   and ".datepickerto".

This patch also removes an unused function which was repeated in three
templates: validate1.

To test, apply the patch and rebuild the staff interface CSS
(https://wiki.koha-community.org/wiki/Working_with_SCSS_in_the_OPAC_and_staff_client).

- Go to Circulation and check out to a patron.
  - Open the Restrictions tab and click "Add manual restriction."
    - In the "Expiration" field, test that the Flatpickr widget works
      correctly and limits to dates after today.
  - Enable the SpecifyDueDate preference if necessary.
    - Test the behavior of the SpecifyDueDate controls: Setting a date,
      clearing a date, session persistence.
- Enable the SuspendHoldsIntranet system preference if necessary.
  - Check out to a patron with existing holds.
    - Open the Holds tab and click the "Suspend" button for one of the
      holds.
      - In the modal window which appears, check that the Flatpickr
        widget works correctly and limits to dates after today.
      - At the bottom of the table of holds, test that the "Suspend all
        holds" Flatpickr works correctly and limits to dates after
        today.
        - Perform this same test from the patron details page.
- Enable the  BatchCheckouts system preference if necessary.
  - Open a patron record and click "Batch check out" in the left-hand
    sidebar menu.
    - Test that the "Hard due date" Flatpickr works correctly as a date
      and time picker.
- Go to Circulation -> Overdues.
  - Test that the date due filters in the sidebar work correctly and
    are linked, e.g. the "to" field cannot be before the "from" field.
     - Perform the same test here: Circulation -> Holds to pull; and
       here: Circulation -> Hold ratios.
- Enable the  HouseboundModule system preference if necessary.
  - Check out to or view details of a patron.
    - Click "Housebound" in the sidebar menu.
      - Save delivery day and frequency settings for that patron.
      - Click "Add a new delivery."
      - Test that the "Date" Flatpickr widget works correctly.
- Go to Patrons -> A patron record -> Edit.
  - Test that Flatpickr widgets work on the following fields:
    - Date of birth
    - Registration date & Expiration date (linked).
    - Patron restrictions -> Add manual restriction -> Expiration.
- View a bibliographic record and start the process of placing a hold.
  - After selecting a patron, test the "Hold starts on" and "Hold
    expires on" date fields. The fields should be linked and each
    should limit to future dates.
  - Confirm that the dates are saved correctly when you submit the
    hold.
- Locate a bibliographic record with multiple holds and view the holds.
  - In the table of holds, test each date field: Date, expiration, and
    suspend-until.
- Test that Flatpickr's static "formatDate" method is working
  correctly:
  - Locate a bibliographic record's item so that there is text in both
    the "Public note" and "Non-public note" field.
  - Check that item out to a patron.
  - After the page reloads the public and non-public notes should be
    shown under the checkout title highlighted in red.
- Check for references to a "validate1" function. There should be none.

Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
This commit is contained in:
Owen Leonard 2021-08-31 19:23:21 +00:00 committed by Jonathan Druart
parent 98f32c074e
commit e5f1ada249
19 changed files with 106 additions and 107 deletions

View File

@ -958,3 +958,7 @@ span.flatpickr-weekday {
.flatpickr-day {
border-radius: 0;
}
.flatpickr_wrapper {
white-space: nowrap;
}

View File

@ -58,6 +58,10 @@ a {
&.clear_date {
color: #C33;
font-size: 130%;
&:hover {
color: #FF9090;
}
}
&.close {
@ -219,7 +223,8 @@ aside {
width: 100%;
}
.hasDatepicker {
.hasDatepicker,
.flatpickr-input {
margin-right: 3px;
width: calc(100% - 20px);
}

View File

@ -60,8 +60,10 @@
<legend>Add manual restriction</legend>
<ol>
<li><label for="rcomment">Comment:</label> <input type="text" id="rcomment" name="comment" /></li>
<li><label for="rexpiration">Expiration:</label> <input name="expiration" id="rexpiration" size="20" value="" class="datepicker" type="text" />
<a href="#" class="clear-date" id="clear-date-rexpiration">Clear date</a></li>
<li>
<label for="rexpiration">Expiration:</label>
<input name="expiration" id="rexpiration" size="20" value="" class="flatpickr futuredate" type="text" />
</li>
</ol>
<fieldset class="action"><input type="submit" value="Add restriction" /> <a href="#" class="cancel" id="cancel_manual_restriction">Cancel</a></fieldset>
</fieldset>

View File

@ -31,7 +31,12 @@
},
onReady: function( selectedDates, dateStr, instance ){
/* When flatpickr instance is created, automatically append a "clear date" link */
if( $(instance.input).hasClass("futuredate") ){
instance.set("minDate", new Date().fp_incr(1));
}
$(instance.input)
/* Add a wrapper element so that we can prevent the clear button from wrapping */
.wrap("<span class='flatpickr_wrapper'></span>")
.after( $("<a/>")
.attr("href","#")
.addClass("clear_date")
@ -48,5 +53,19 @@
validate_date( selectedDates, instance );
},
});
$(document).ready(function(){
$(".flatpickr").flatpickr();
var startPicker = $(".flatpickrfrom").flatpickr({
onClose: function( selectedDates, dateText, instance) {
validate_date( selectedDates, instance );
endPicker.set('minDate', selectedDates[0]);
}
});
var endPicker = $(".flatpickrto").flatpickr({
onClose: function( selectedDates, dateText, instance) {
validate_date( selectedDates, instance );
},
});
});
</script>
[% END %]

View File

@ -106,12 +106,14 @@
<td>[% hold.notes | html | html_line_break %]</td>
<td>
[% IF Koha.Preference('AllowHoldDateInFuture') %]
<input type="text" class="datepicker" value="[% hold.date | $KohaDates %]" required="required" size="10" name="reservedate" />
<input type="text" class="flatpickr" value="[% hold.date | $KohaDates %]" required="required" size="10" name="reservedate" />
[% ELSE %]
[% hold.date | $KohaDates %]
[% END %]
</td>
<td><input type="text" class="datepicker futuredate" value="[% hold.expirationdate | $KohaDates %]" size="10" name="expirationdate" /></td>
<td>
<input type="text" class="flatpickr futuredate" value="[% hold.expirationdate | $KohaDates %]" size="10" name="expirationdate" />
</td>
<td>
[%- IF ( hold.found ) -%]
[%- IF ( hold.atdestination ) -%]
@ -196,8 +198,7 @@
<input type="button" value="[% IF ( hold.suspend ) %]Unsuspend[% ELSE %]Suspend[% END %]" onclick="window.location.href='request.pl?action=toggleSuspend&amp;reserve_id=[% hold.reserve_id | html %]&amp;borrowernumber=[% hold.borrowernumber | html %]&amp;biblionumber=[% hold.biblionumber | html %]&amp;date=[% hold.date | html %]&amp;suspend_until=' + $('#suspend_until_[% hold.reserve_id | html %]').val()" />
[%- IF Koha.Preference('AutoResumeSuspendedHolds') -%]
<label for="suspend_until_[% hold.reserve_id | html %]">[% IF ( hold.suspend ) %] on [% ELSE %] until [% END %]</label>
<input type="text" name="suspend_until_[% hold.reserve_id | html %]" id="suspend_until_[% hold.reserve_id | html %]" size="10" value="[% hold.suspend_until | $KohaDates %]" class="datepicker suspend_until_datepicker" />
<a href='#' onclick="document.getElementById('suspend_until_[% hold.reserve_id | html %]').value='';">Clear date</a>
<input type="text" name="suspend_until_[% hold.reserve_id | html %]" id="suspend_until_[% hold.reserve_id | html %]" size="10" value="[% hold.suspend_until | $KohaDates %]" class="suspend_until_datepicker" />
[%- ELSE -%]
<input type="hidden" name="suspend_until_[% hold.reserve_id | html %]" id="suspend_until_[% hold.reserve_id | html %]" value=""/>
[%- END -%]

View File

@ -636,7 +636,6 @@
[% ELSE %]
<input type="checkbox" id="stickyduedate" onclick="this.form.barcode.focus();" name="stickyduedate" />
[% END %]
<button class="btn btn-default btn-xs action" id="cleardate" name="cleardate" onclick="this.checked = false; this.form.duedatespec.value = ''; this.form.stickyduedate.checked = false; this.form.barcode.focus(); return false;" >Clear</button>
</div>
[% END %]
[% END %]
@ -884,7 +883,7 @@
[% IF Koha.Preference('AutoResumeSuspendedHolds') %]
<label for="suspend_until">until</label>
<input type="text" size="10" id="suspend_until" name="suspend_until" class="datepicker futuredate"/>
<input type="text" size="10" id="suspend_until" name="suspend_until" class="flatpickr futuredate"/>
<span class="hint">Specify date on which to resume [% INCLUDE 'date-format.inc' %]: </span>
[% END %]
</form>
@ -1013,21 +1012,6 @@
function toggle_onsite_checkout(){
if ( $("#onsite_checkout").prop('checked') ) {
$("#duedatespec").val("[% todaysdate | $KohaDates with_hours => 1 %]")
[% IF !Koha.Preference('SpecifyDueDate') %]
$("#duedatespec").datetimepicker('destroy');
[% END %]
} else {
$("#duedatespec").datetimepicker({
onClose: function(dateText, inst) {
if (validate_date(dateText, inst) ) {
$("#barcode").focus();
}
},
hour: 23,
minute: 59
}).on("change", function(e, value) {
if ( ! is_valid_date( $(this).val() ) ) {$(this).val("");}
});
}
}

View File

@ -359,14 +359,10 @@
}));
[% IF Koha.Preference('SpecifyDueDate') %]
$("#duedatespec").datetimepicker({
onClose: function(dateText, inst) {
validate_date(dateText, inst);
},
hour: 23,
minute: 59
}).on("change", function(e, value) {
if ( ! is_valid_date( $(this).val() ) ) {$(this).val("");}
$("#duedatespec").flatpickr({
enableTime: true,
dateFormat: flatpickr_dateformat_string + " " + flatpickr_timeformat_string,
defaultDate: new Date().setHours(23, 59, 0, 0) /* Today a 11:59PM */
});
[% END %]
});

View File

@ -456,15 +456,6 @@
[% END %]
}
function validate1(date) {
var today = new Date();
if ( date < today ) {
return true;
} else {
return false;
}
};
function loadPatron(barcode) {
$('#oldissues').hide();
$('#session-issues').hide();

View File

@ -156,11 +156,11 @@
<li class="date_due_filter">
<label for="from">From:</label>
<input type="text" id="from" name="dateduefrom" size="10" value="[% filters.dateduefrom | $KohaDates %]" class="datepickerfrom" />
<input type="text" id="from" name="dateduefrom" size="10" value="[% filters.dateduefrom | $KohaDates %]" class="flatpickrfrom" />
</li>
<li class="date_due_filter">
<label for="to">To:</label>
<input type="text" id="to" name="datedueto" size="10" value="[% filters.datedueto | $KohaDates %]" class="datepickerto" />
<input type="text" id="to" name="datedueto" size="10" value="[% filters.datedueto | $KohaDates %]" class="flatpickrto" />
</li>
</ol>
</fieldset>

View File

@ -271,12 +271,12 @@
<label for="from">
Start date:
</label>
<input type="text" size="10" id="from" name="from" value="[% from | $KohaDates %]" class="datepickerfrom" />
<input type="text" size="10" id="from" name="from" value="[% from | $KohaDates %]" class="flatpickrfrom" />
</li>
<li><label for="to">
End date:
</label>
<input type="text" size="10" id="to" name="to" value="[% to | $KohaDates %]" class="datepickerto" />
<input type="text" size="10" id="to" name="to" value="[% to | $KohaDates %]" class="flatpickrto" />
</li>
</ol>

View File

@ -164,12 +164,12 @@
<li>
<label for="from">Start date:</label>
<input type="text" size="10" id="from" name="from" value="[% from | $KohaDates %]" class="datepickerfrom" />
<input type="text" size="10" id="from" name="from" value="[% from | $KohaDates %]" class="flatpickrfrom" />
</li>
<li>
<label for="to">End date:</label>
<input size="10" id="to" name="to" value="[% to | $KohaDates %]" type="text" class="datepickerto" />
<input size="10" id="to" name="to" value="[% to | $KohaDates %]" type="text" class="flatpickrto" />
</li>
</ol>
(inclusive)

View File

@ -217,11 +217,11 @@
<li>
<label for="date" class="required">Date: </label>
[% IF ( visit ) %]
<input type="text" id="date" class="datepicker required"
<input type="text" id="date" class="flatpickr required"
value="[% visit.appointment_date | $KohaDates %]"
name="date" size="20" required="required"/>
[% ELSE %]
<input type="text" id="date" class="datepicker required"
<input type="text" id="date" class="flatpickr required"
value="" name="date" size="20" required="required"/>
[% END %]
<span class="required">Required</span>

View File

@ -347,7 +347,7 @@ legend:hover {
[% END %]
Date of birth:
</label>
<input type="text" id="dateofbirth" name="dateofbirth" size="20" value="[% dateofbirth | html UNLESS opduplicate %]" class="datepicker" />
<input type="text" id="dateofbirth" name="dateofbirth" size="20" value="[% dateofbirth | html UNLESS opduplicate %]" class="flatpickr" />
[% IF ( mandatorydateofbirth ) %]
<span class="required">Required</span>
[% END %]
@ -1055,7 +1055,7 @@ legend:hover {
[% END %]
Registration date:
</label>
<input type="text" id="from" name="dateenrolled" maxlength="10" size="10" value="[% dateenrolled | html %]" class="datepickerfrom" />
<input type="text" id="from" name="dateenrolled" maxlength="10" size="10" value="[% dateenrolled | html %]" class="flatpickrfrom" />
[% IF ( mandatorydateenrolled ) %]
<span class="required">Required</span>
[% END %]
@ -1079,9 +1079,9 @@ legend:hover {
Expiry date (leave blank for auto calc):
</label>
[% UNLESS ( opadd ) %]
<input type="text" id="to" name="dateexpiry" maxlength="10" size="10" value="[% dateexpiry | html UNLESS opduplicate %]" class="datepickerto" />
<input type="text" id="to" name="dateexpiry" maxlength="10" size="10" value="[% dateexpiry | html UNLESS opduplicate %]" class="flatpickrto" />
[% ELSE %]
<input type="text" id="to" name="dateexpiry" maxlength="10" size="10" value="[% dateexpiry | html %]" class="datepickerto" />
<input type="text" id="to" name="dateexpiry" maxlength="10" size="10" value="[% dateexpiry | html %]" class="flatpickrto" />
[% END %]
[% IF ( mandatorydateexpiry ) %]
<span class="required">Required</span>
@ -1373,7 +1373,7 @@ legend:hover {
</li>
<li>
<label for="debarred_expiration">Expiration: </label>
<input name="debarred_expiration" id="debarred_expiration" size="10" value="" class="datepicker" type="text" />
<input name="debarred_expiration" id="debarred_expiration" size="10" value="" class="flatpickr" type="text" />
<a href="#" id="clear_debarred_expiration">Clear date</a>
</li>
</ol>

View File

@ -810,7 +810,7 @@
[% IF Koha.Preference('AutoResumeSuspendedHolds') %]
<label for="suspend_until">until</label>
<input type="text" size="10" id="suspend_until" name="suspend_until" class="datepicker futuredate"/>
<input type="text" size="10" id="suspend_until" name="suspend_until" class="flatpickr futuredate"/>
<span class="hint">Specify date on which to resume [% INCLUDE 'date-format.inc' %]: </span>
[% END %]
</fieldset>
@ -952,15 +952,6 @@
}
}
}
function validate1(date) {
var today = new Date();
if ( date < today ) {
return true;
} else {
return false;
}
};
</script>
[% END %]

View File

@ -492,17 +492,15 @@
[% IF ( reserve_in_future ) %]
<li>
<label for="from">Hold starts on date:</label>
<input name="reserve_date" id="from" size="10" class="datepickerfrom" type="text" >
<input id="reserve_date" name="reserve_date" id="from" size="10" type="text" >
<input type="hidden" class="datepickerfrom_hidden" />
<a href="#" id="clear-date-from" class="clear-date">Clear date</a>
</li>
[% END %]
<li>
<label for="to">Hold expires on date:</label>
<input name="expiration_date" id="to" size="10" class="datepickerto" type="text" />
<input id="expiration_date" name="expiration_date" id="to" size="10" type="text" />
<input type="hidden" class="datepickerto_hidden" />
<a href="#" id="clear-date-to" class="clear-date">Clear date</a>
</li>
[% UNLESS ( multi_hold ) %]
@ -1141,14 +1139,34 @@
[% END %]
[% IF AutoResumeSuspendedHolds %]
$(".suspend_until_datepicker, .datepickerfrom, .datepickerto").datepicker("option", "minDate", 1);
$(".suspend_until_datepicker").flatpickr({
minDate: new Date().fp_incr(1) /* Require that "until date" be in the future */
});
[% END %]
$(".datepickerto").datepicker("option", "altField", ".datepickerto_hidden");
$(".datepickerto").datepicker("option", "altFormat", "yy-mm-dd");
$(".datepickerfrom").datepicker("option", "altField", ".datepickerfrom_hidden");
$(".datepickerfrom").datepicker("option", "altFormat", "yy-mm-dd");
var startPicker = $("#reserve_date").flatpickr({
minDate: new Date().fp_incr(1), /* Require that "Hold starts on date" be in the future */
onChange: function(selectedDates, dateStr, instance) {
altFormat = instance.formatDate(selectedDates[0], "Y-m-d");
$(".datepickerfrom_hidden").val( altFormat ); /* Populate hidden field with ISO-formatted date */
},
onClose: function( selectedDates, dateText, instance) {
validate_date( selectedDates, instance );
endPicker.set('minDate', selectedDates[0]);
}
});
var endPicker = $("#expiration_date").flatpickr({
minDate: new Date().fp_incr(1), // Require that "Hold expires on date" be in the future
onChange: function(selectedDates, dateStr, instance) {
altFormat = instance.formatDate(selectedDates[0], "Y-m-d");
$(".datepickerto_hidden").val( altFormat ); /* Populate hidden field with ISO-formatted date */
},
onClose: function( selectedDates, dateText, instance) {
validate_date( selectedDates, instance );
},
});
var my_table = $("#requestspecific").dataTable($.extend(true, {}, dataTablesDefaults, {
'bPaginate': false,

View File

@ -186,14 +186,6 @@ $(document).ready(function () {
yearRange: "c-100:c+10"
});
$("#dateofbirth").datepicker({
yearRange: "c-100:c"
});
$(".futuredate").datepicker({
minDate: 1, // require that hold suspended until date is after today
});
$(".datepicker").datepicker({
onClose: function (dateText, inst) {
validate_date(dateText, inst);

View File

@ -233,7 +233,7 @@ $(document).ready(function() {
return false;
});
var ymd = $.datepicker.formatDate('yy-mm-dd', new Date());
var ymd = flatpickr.formatDate(new Date(), "Y-m-d");
$('#issues-table').hide();
$('#issues-table-actions').hide();
@ -349,7 +349,7 @@ $(document).ready(function() {
if ( oObj.itemnotes ) {
var span_class = "text-muted";
if ( $.datepicker.formatDate('yy-mm-dd', new Date(oObj.issuedate) ) == ymd ) {
if ( flatpickr.formatDate( new Date(oObj.issuedate), "Y-m-d" ) == ymd ){
span_class = "circ-hlt";
}
title += " - <span class='" + span_class + " item-note-public'>" + oObj.itemnotes.escapeHtml() + "</span>";
@ -357,7 +357,7 @@ $(document).ready(function() {
if ( oObj.itemnotes_nonpublic ) {
var span_class = "text-danger";
if ( $.datepicker.formatDate('yy-mm-dd', new Date(oObj.issuedate) ) == ymd ) {
if ( flatpickr.formatDate( new Date(oObj.issuedate), "Y-m-d" ) == ymd ){
span_class = "circ-hlt";
}
title += " - <span class='" + span_class + " item-note-nonpublic'>" + oObj.itemnotes_nonpublic.escapeHtml() + "</span>";
@ -740,7 +740,7 @@ $(document).ready(function() {
if ( oObj.itemnotes ) {
var span_class = "";
if ( $.datepicker.formatDate('yy-mm-dd', new Date(oObj.issuedate) ) == ymd ) {
if ( flatpickr.formatDate( new Date(oObj.issuedate), "Y-m-d" ) == ymd ){
span_class = "circ-hlt";
}
title += " - <span class='" + span_class + "'>" + oObj.itemnotes.escapeHtml() + "</span>"
@ -748,7 +748,7 @@ $(document).ready(function() {
if ( oObj.itemnotes_nonpublic ) {
var span_class = "";
if ( $.datepicker.formatDate('yy-mm-dd', new Date(oObj.issuedate) ) == ymd ) {
if ( flatpickr.formatDate( new Date(oObj.issuedate), "Y-m-d" ) == ymd ){
span_class = "circ-hlt";
}
title += " - <span class='" + span_class + "'>" + oObj.itemnotes_nonpublic.escapeHtml() + "</span>"

View File

@ -44,7 +44,7 @@ $(document).ready(function() {
if ( oObj.itemnotes ) {
var span_class = "";
if ( $.datepicker.formatDate('yy-mm-dd', new Date(oObj.issuedate) ) == ymd ) {
if ( flatpickr.formatDate( new Date(oObj.issuedate), "Y-m-d" ) == ymd ){
span_class = "circ-hlt";
}
title += " - <span class='" + span_class + "'>" + oObj.itemnotes.escapeHtml() + "</span>"
@ -335,7 +335,9 @@ $(document).ready(function() {
</div>\
");
$("#suspend-modal-until").datepicker({ minDate: 1 }); // Require that "until date" be in the future
$("#suspend-modal-until").flatpickr({
minDate: new Date().fp_incr(1) // Require that "until date" be in the future
});
$("#suspend-modal-clear-date").on( "click", function() { $("#suspend-modal-until").val(""); } );
$("#suspend-modal-submit").on( "click", function( e ) {

View File

@ -1,3 +1,4 @@
/* global flatpickr_dateformat_string flatpickr_timeformat_string */
$(document).ready(function() {
$("#CheckAllExports").on("click",function(){
$(".export:visible").prop("checked", true);
@ -38,17 +39,19 @@ $(document).ready(function() {
}).on("change", function(e) {
if ( ! is_valid_date( $(this).val() ) ) {$(this).val("");}
});
$("#duedatespec").datetimepicker({
onClose: function(dateText, inst) {
if ( validate_date(dateText, inst) ) {
$("#barcode").focus();
}
},
hour: 23,
minute: 59
}).on("change", function(e, value) {
if ( ! is_valid_date( $(this).val() ) ) {$(this).val("");}
$("#duedatespec").flatpickr({
enableTime: true,
dateFormat: flatpickr_dateformat_string + " " + flatpickr_timeformat_string,
onClose: function() {
$("#barcode").focus();
}
});
$(".clear_date").on("click", function(){
$("#stickyduedate").prop( "checked", false );
});
$("#export_submit").on("click",function(){
export_checkouts($("#issues-table-output-format").val());
return false;
@ -136,12 +139,3 @@ function export_checkouts(format) {
document.getElementById("output_format").value = format;
document.issues.submit();
}
function validate1(date) {
var today = new Date();
if ( date < today ) {
return true;
} else {
return false;
}
}