User interface components in general are built with an expected user behavior. We used the jQuery-UI datepicker, to save time, but in an unusual way, which cost us a lot of time discovering what we’d missed. We re-learned a key insight, namely, month and year are navigation, not selection controls.
The rest of this post describes the implications we worked out. Short version
of the code is at the bottom of this post. There are other files attached in a gist version of this post at https://gist.github.com/dfkaye/f1efc4be64699df2d65c55e27b41f725
Background
Recently (November 2016) we ran into a design requirement that a dashboard report should cover a time period, with start and end dates and that the report should update when users change a month or a year in either the start or end datepicker.
In the interest of saving time, rather than roll our own control for that, we
chose to use the JQueryUI datepicker for each of these, but with an unusual
approach that we would not show the calendar control (the table with all the
clickable date links).
Of course, that unexpected approach led to unexpected and undesired behavior.
Setup
In our implementation, we used changeMonth: true
and changeYear: true
in the
datepicker(options)
argument, in order to display the month and year “
elements, and set let the datepicker display “inline” (i.e., always, without a
dialog – see http://jqueryui.com/datepicker/#inline).
We also set the defaultDate
to the current year and month, and use 1
as the
day, so that we’re always comparing the first of any month.
We also needed to keep the two datepickers synchronized, such that a user could
not set the start date to something greater than the end date. We do that in the
onChangeMonthYear
handler, re-setting either start’s maxDate
or end’s
minDate
after a change to the end or the start date, respectively.
Problem
The prev
and next
arrows worked just fine, as they are decrement/increment
handlers only. But we noticed that sometimes the onChangeMonthYear
handler
was called multiple times when we changed a date by using the month and year
select
elements. For example, we’d change the start year, then change the
end year. On that second change, we’d see the update to the start date’s
maxDate
– which seemed to trigger a onChangeMonthYear
call to the start
datepicker again, even though the start element had not been touched.
The result was that date ranges became unpredictable, even un-navigable.
Fix
It turns out the onChangeMonthYear
event results only in re-drawing the
calendar – it does not calculate a new date selection.
Translation: month and year are navigation controls; the calendar’s date links are the selection controls.
The expected behavior in the datepicker is that users select a date link inside
the calendar. Because we are not using the calendar, we have to re-set the date,
in code, on the datepicker for which the onChangeMonthYear
handler is called –
every time.
You can see most of the code fragments necessary to make the whole thing go by
looking at the other files posted in this gist. Here’s the condensed version of
the JavaScript we used:
onChangeMonthYear: function (year, month, instance) {
...
// Build a new Date from the display date, decrementing the display
// month by 1 for use in JavaScript's built-in Date().
var newDate = new Date(year, Number(month) - 1, 1);
// This one line prevents the buggy behavior noted above.
$(instance.input.context).datepicker('setDate', newDate);
...
}
This took quite a bit of time to reason out, but that’s all there was to it.