From ac52cee7d0ee9db1f8888ea67566472afbf234bd Mon Sep 17 00:00:00 2001 From: pxpm Date: Tue, 18 Aug 2026 14:44:38 +0100 Subject: [PATCH] doc how to link date pickers --- 7.x/crud-fields.md | 3 ++ 7.x/crud-how-to.md | 93 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) diff --git a/7.x/crud-fields.md b/7.x/crud-fields.md index 47178374..46e3627b 100644 --- a/7.x/crud-fields.md +++ b/7.x/crud-fields.md @@ -1332,6 +1332,9 @@ To customize, publish the lang files and edit the `date_time` array. Any option from [air-datepicker's documentation](https://air-datepicker.com/docs) can be passed inside the `air-datepicker` config array. +To control the picker from JavaScript (eg. link two pickers so that one excludes the dates +of the other), see [How to link two air-datepicker fields](/docs/{{version}}/crud-how-to#how-to-link-two-air-datepicker-fields). + Input preview: ![CRUD Field - Air Datepicker](https://backpackforlaravel.com/uploads/docs/fields/air-datepicker.jpeg) diff --git a/7.x/crud-how-to.md b/7.x/crud-how-to.md index 1eed1502..504c9e7f 100644 --- a/7.x/crud-how-to.md +++ b/7.x/crud-how-to.md @@ -706,6 +706,99 @@ CRUD::field([ - We do not provide an interface to edit this relationship. We never needed it, nobody ever asked for it and it would be very difficult to create an interface that is easy-to-use and easy-to-understand for the admin. If you find yourself needing this, please let us know by opening an issue on GitHub. + +### How to link two air-datepicker fields (PRO) + +The `air-datepicker` field stores its air-datepicker instance on the visible input, and provides one JS helper to reach it: + +- `bpFieldAirDatepickerInstance(fieldName)` - returns the air-datepicker instance of a field, by field name (or `false`). With the instance you can use the [full air-datepicker API](https://air-datepicker.com/docs): `dp.update()`, `dp.selectDate()`, `dp.clear()`, `dp.onSelect`, etc. + +For example, let's prevent `end_date` from ever being before `start_date`, in a Create/Update form that has two single `air-datepicker` fields: + +```php +CRUD::field([ + 'name' => 'start_date', + 'label' => 'From date', + 'type' => 'air-datepicker', +]); + +CRUD::field([ + 'name' => 'end_date', + 'label' => 'To date', + 'type' => 'air-datepicker', +]); +``` + +Then add a script to the operation: + +```php +// in your CrudController, inside setupCreateOperation() and/or setupUpdateOperation() +Widget::add()->type('script')->content('assets/js/admin/forms/link-dates.js'); +``` + +```js +// assets/js/admin/forms/link-dates.js +$(function () { + // Backpack initializes its fields inside its own document-ready handler, + // registered after this one — defer with a macrotask so the instances + // already exist. + setTimeout(linkDates, 0); +}); + +function linkDates() { + var startDp = bpFieldAirDatepickerInstance('start_date'); + var endDp = bpFieldAirDatepickerInstance('end_date'); + + if (!startDp || !endDp) return; + + // What minDate/maxDate each picker was configured with in PHP ('' by default). + var startDefaultMaxDate = startDp.opts.maxDate; + var endDefaultMinDate = endDp.opts.minDate; + + // One way: "To date" can't be before "From date"... + linkAirDatepickers(startDp, endDp, 'minDate', endDefaultMinDate); + // ...and the other way: "From date" can't be after "To date". + linkAirDatepickers(endDp, startDp, 'maxDate', startDefaultMaxDate); +} + +function linkAirDatepickers(sourceDp, targetDp, limit, targetDefaultLimit) { + var originalOnSelect = sourceDp.opts.onSelect; // the field's own handler + + sourceDp.opts.onSelect = function (args) { + // keep the field's built-in behavior (updates the hidden input) + originalOnSelect.apply(this, arguments); + + if (args.date) { + targetDp.update({ [limit]: args.date }); + + // optional: clear the target if its current selection is now invalid + var targetSelected = targetDp.selectedDates[0]; + if (targetSelected && ( + (limit === 'minDate' && targetSelected.getTime() < args.date.getTime()) || + (limit === 'maxDate' && targetSelected.getTime() > args.date.getTime()) + )) { + targetDp.clear(); + } + } else { + // source cleared: restore the originally configured limit. + // NOTE: passing null throws in air-datepicker 3.6.0 — restore '' + // (or the original value) instead. + targetDp.update({ [limit]: targetDefaultLimit || '' }); + } + }; + + // also apply the link on page load, when editing an entry that already + // has a source value + var initialSource = sourceDp.selectedDates[0]; + if (initialSource) { + targetDp.update({ [limit]: initialSource }); + } +} +``` + + +See the field docs [here](/docs/{{version}}/crud-fields#air-datepicker). + ## Operations