On one of my ongoing web projects, we have forms that require users to specify a date. The input field is a single text field with an expected format (as opposed to a trio of selectors corresponding to day, month, and year). On form submit, we perform server-side validation. All straightforward stuff.
In the spirit of progressive enhancement – or graceful degradation, depending upon whether you see your glasses as half-full or half-empty – we add some client-side enhancements.
The first bit of client-side enhancement is just validation on the expected format. Nothing special here. The second bit is a date picker with which to easily select dates.
Better, stronger, faster
In the first round of development, we used a popup window containing a server-generated HTML page, as is done in phpMyAdmin. It worked fine for a while but there were several downsides to our particular implementation:
- As a popup, it was subject to possible blocking
- As server-generated content, it was a bit slow, especially on our internet connections here in Thailand
Also, it was more than a bit old-fashioned. I mean, it’s like wearing your grandpa’s sweater: it may keep you warm, but it sure as heck ain’t gonna impress Mary Lou the cheerleader down at the soda shop.
The best part of it is ease of use: For any fields to which we want to bind a datepicker, we simply give that field a particular CSS class. So we could have HTML markup of the form:
<input type="text" name="mydate" id="mydate" class="dp">
Companions: Checking-in and Checking-out together
But like all things web-related, that’s not the end of the story. Turns out that there are places on the site where two date input fields are related, paired as companions, typically in a check-in / check-out scenario, like booking a hotel room or a flight.
Certainly it is possible for these fields and their datepickers to function in a fully independent manner. If the user tries to check-put before he checks in, both the client-side and the server-side validation kick in to ensure data integrity.
But it is somewhat sub-optimal to even allow the user to select such a situation in the first place.
Even better, don’t you love it when you go to a site and after you select the check-in date, the check-out date automatically sets itself to the day after check-in?
Or, on the other side, don’t you hate when: you are booking a room or a flight way in the future, like next year; you select the first date, perhaps by navigating through a sequence of months, and then are forced to do the same thing on the second datepicker?
[Actually, it's probably the case that the user shouldn't even have to do this on the first datepicker, that in addition to prev-month and next-month navigation, the datepicker control should have its own selectors that allow the user to jump to a desired year and month. Good, full-featured, datepickers actually have this as well.]
The selection of the first date (the preceder) inherently puts a lower bound on the acceptable values for the second date (the succeeder). So why not bake that into the operation of the form?
And, this thinking works in the other direction, too. Suppose I chance the check-out date to sometime before the current check-in date? It seems reasonable that I should change the check-in date to be something before the check-out date, and the most reasonable choice is the day before check-out. If the user wants to push forward further than that, then he his certainly free to do so, but at least we put him in a reasonable ballpark.
Of course, all date changes do not warrant changing the companion. If I already have a reasonable check-in that precedes a check-out, and then I push back my check-out to a later date, there is certainly no need to change my check-in. Symmetrically, if I move my check-in forward to an earlier date, I certainly have no basis for concluding that the check-out should change.
So what I need is a way to pair these two input fields to each other so that a change in one can automatically create a change in the other, but only when it is appropriate to do so. And I’d like to handle it all with a single set of DRY handlers. And I’d like it to be valid HTML markup. And I’d like it to be completely unobtrusive.
Es posible? Si!
My Companion Approach
The jQuery Datepicker UI plugin has the option to set an onSelect callback option. That is, when a date is actually selected by the user in the datepicker, this function will be executed by the plugin. This is the key to the handling.
But we need some way to identify pairs, and especially which one is the preceder and which one is the succeeder.
I created a class naming scheme as follows.
Consider paired fields representing check-in and check-out:
<input type="text" name="mycheckin" id="mycheckin">
<input type="text" name="mycheckout" id="mycheckout">
As before, we want them to be bound to their own datepickers, so we add the class “dp” yielding:
<input type="text" name="mycheckin" id="mycheckin" class="dp">
<input type="text" name="mycheckout" id="mycheckout" class="dp">
Now, we want to specify that mycheckin is a preceder, bound to the succeeder mycheckout. And vice versa. So, I add classes as follows:
<input type="text" name="mycheckin" id="mycheckin" class="dp dp_1_mycheckout">
<input type="text" name="mycheckout" id="mycheckout" class="dp dp_2_mycheckin">
The 1 and the 2 represent flags to help determine which is a preceder and which is a succeeder.
The part trailing the numeric preceder/succeeder flags is the object id of the companion.
Then I define a bunch of helper functions to:
- parse the class names to extract this information
- detect when a change in one actually warrants a change in the other
- add/subtract a day from a user selected date
- output dates in the desired format
and then use all these helpers in the single DRY datepicker onSelect handler.
Aaaah, sweet success.
If I get a chance – or an overwhelming deluge of requests – I’ll post some sample code in more detail.