Disabled Dates Pikaday Date Picker
Tags: javascript, pikaday, dates, laravel
Published: 2021-08-30 20:59:58

One feature that we've been talking about for some time now, but has taken a back seat while the mobile app was being built / tested / updated, is to update our date picker to block off days where charters are unavailable.  Our date picker was used as a custom livewire component that is powered by pikadays lightweight javascript date picker.  That particular component is used throughout our web app but on the individual charters listing page is where we need to "block" dates on the picker so that if a captain has a booking, or has blocked it off on the web or mobile app, we need it to reflect our date picker, so that we can have a more accurate availability.  

[Pikaday](https://github.com/Pikaday/Pikaday) is a pretty neat and has some features but the biggest thing is how lightweight it is and it has worked for us.  
```
    <script>
        document.addEventListener('livewire:load', function () {
        let livewireComponent = @this;
        let disabledDates = @this.disabledDates;
        let picker = new window.Pikaday({
            field: document.getElementById(@this.elementId),
            format: 'dd/MM/yyyy',
            defaultDate: window.moment(@this.date).toDate(),
            minDate: window.moment(@this.minDate).toDate(),
            onSelect: function(date) {
                @this.date = window.moment(date).format('MM/DD/YYYY');
            },
        });
    })
    </script>
```
So this our basic syntax for our date picker, the callback onSelect sets the date to the livewire components date property and emits the event up to whatever parent component where out date picker is used.  Pikaday provides a few other callbacks, including `disableDayFn: function(date){}`, where you can return true or false to block a date.  I kinda wanted to see what the `date` variable is so I did some console logging and it is literally every single date on the picker.  You can see it all below here:
```
Wed Sep 01 2021 00:00:00 GMT-0400 (Eastern Daylight Time)
VM83 deepwater-assassins:379 Thu Sep 02 2021 00:00:00 GMT-0400 (Eastern Daylight Time)
VM83 deepwater-assassins:379 Fri Sep 03 2021 00:00:00 GMT-0400 (Eastern Daylight Time)
VM83 deepwater-assassins:379 Sat Sep 04 2021 00:00:00 GMT-0400 (Eastern Daylight Time)
VM83 deepwater-assassins:379 Sun Sep 05 2021 00:00:00 GMT-0400 (Eastern Daylight Time)
VM83 deepwater-assassins:379 Mon Sep 06 2021 00:00:00 GMT-0400 (Eastern Daylight Time)
VM83 deepwater-assassins:379 Tue Sep 07 2021 00:00:00 GMT-0400 (Eastern Daylight Time)
VM83 deepwater-assassins:379 Wed Sep 08 2021 00:00:00 GMT-0400 (Eastern Daylight Time)
VM83 deepwater-assassins:379 Thu Sep 09 2021 00:00:00 GMT-0400 (Eastern Daylight Time)
VM83 deepwater-assassins:379 Fri Sep 10 2021 00:00:00 GMT-0400 (Eastern Daylight Time)
VM83 deepwater-assassins:379 Sat Sep 11 2021 00:00:00 GMT-0400 (Eastern Daylight Time)
VM83 deepwater-assassins:379 Sun Sep 12 2021 00:00:00 GMT-0400 (Eastern Daylight Time)
VM83 deepwater-assassins:379 Mon Sep 13 2021 00:00:00 GMT-0400 (Eastern Daylight Time)
VM83 deepwater-assassins:379 Tue Sep 14 2021 00:00:00 GMT-0400 (Eastern Daylight Time)
VM83 deepwater-assassins:379 Wed Sep 15 2021 00:00:00 GMT-0400 (Eastern Daylight Time)
VM83 deepwater-assassins:379 Thu Sep 16 2021 00:00:00 GMT-0400 (Eastern Daylight Time)
```
Since we are using moment I formatted the date to the pickers format for easy comparison. `let formattedDate = moment(date).format('MM/DD/YY');`  And now if I can grab the dates that are unavailable / available from the specific charter I could compare them.

Originally I had planned on making a call every time the month was changed and do try and make this picker as "dynamic" as possible but I soon found out that this callback function can not be done asynchronously and I realized that I am going to have to grab every date/event of the charter from the min date to eternity.  Not really sure if this is efficient or not but I have tested it out with 20+ events each month for a 2 year period and it did not slow down / lag at all.  That being said this might have to change in the future if for some reason it becomes sluggish.  Our main competitors site and our old site was super slow / annoying and speed is our main priority so hopefully this will be a great feature for us and hopefully this will up our booking acceptance rate as well.  

I decided to break out the functionality to grab these "events" into a Service, instead of directly on the Charter models so I could theoretically pass in any charter / date and get events.  I'm still kinda second guessing this since the only time it has come up at this point is directly on the listings page but so I don't have to rewrite code breaking it out into it's own little service seems reasonable.  I also decided that I'd make each piece of functionality it's own, so that I could grab blocked dates or bookings individually if needed.  This seems to be better since it follows the single responsibility principle.  I'm not gonna go into super detail about those queries in my new Service, but the basics is in one method I grab all bookings after the min date of the picker, and the other I grab all the blocked dates after the min date of the picker.  I also make sure in each function after I grab them to map them into specific format and then flatten / unique collection methods to make sure there are no duplicates.  

My new Service will be called on our TripSearch livewire component.  I grab these dates then pass them into the date picker as seen here:
`$this->disabledDates = (new DisabledDates($this->charter, Carbon::parse(now()->addHours(36)->format('m/d/Y'))))->getAllEvents();`
then in my livewire component:
`<livewire:date-picker :date="$date" :disabledDates="$disabledDates"/>`
Now I just need to modify the datepicker to accept an array (disabledDates) or set it to null if not.  

```
    public function mount(string $class = null, string $date = null, array $disabledDates = [])
    {
        $this->date = ($date) ? Carbon::parse($date)->format($this->format) : now()->addHours(36)->format($this->format);
        $this->class = ($class) ? $class : $this->defaultClass;
        $this->elementId = Str::random();
        $this->minDate = now()->addHours(36)->format($this->format);
        $this->disabledDates = $disabledDates;
    }
```

So now for the final piece of functionality I need to run the `dates` variable that is passed into the disabledDates callback function of pikaday to see if the dates exist in my disabledDates array, passed into the DatePicker, and as we can see if nothing is passed in, it's an empty array meaning all dates are enabled!!!!!!

```
disableDayFn: function(date) {
                let formattedDate = moment(date).format('MM/DD/YY');
                return disabledDates.includes(formattedDate) ? true : false;
            }
```

So I formate the date and return if it is included in the array and if it is true (disabled) or false (enabled).  So now I have taken a feature that's been on the back burner and made it happen.  Excited to see if this really does improve booking acceptance rates, as I honestly do not know.  I don't wanna say I could care less since more bookings means more money which means job security, but it's not my job to make those decisions.  If the admins think this will help then we make it happen and hope for the best.  Unfortunately this past week has been kinda rough since now I have to rebuild / redesign our search service which literally powers our entire App.  At first we were told to make search only show available Charters and now I was told we want it to show all Charters and if they are unavailable, they need to be opaque with some type of "next available date" or "click to see availability", but unfortunately this completely contradicts our initial build and now being the only developer here and being a "mid" level engineer, I am kinda nervous about it.  There will definitely be a post about this in the next couple of weeks.