While working on our plugin we are constantly meeting interesting challenges. We'll share what we learn from them in a set of tutorials. In this tutorial we'll discuss tweaking the jQuery UI DatePicker in a way that allows two things:
See a live example of the tweaks here: http://wp-hostel.com/demo/twin-private-ensuite/
Let's see how to do both things.
When handling hostel reservations you will have some dates that shouldn't be selectable. These are obvious dates in the past, dates when the given room is occupied, and probably dates you have selected to be on holidays. So, let's have a date picker field in a form first:
<input type="text" name="date" id="datePicker">
Now, let's make it work as date-picker and allow selecting dates from today to one year ahead:
jQuery('#datePicker').datepicker(function{
yearRange: "2014:2015",
maxDate: "+1y",
minDate: "0",
dateFormat: 'mm/dd/yy',
});
We have added a couple of options here like range of years and date format, but you should pay most attention to two:
Handling the other unavailable dates however requires a bit of coding. This is where the function beforeShowDay comes into the game. The function is called once for each date shown on the calendar and lets you do something with the date. If you check the API again you will see it returns array of 3 elements, and all of them come handy.
If the first element of the returned array is false, it means the date is not available and can't be selected. So you just need to check the date agains your pre-defined (dynamically or not) array of unavailable dates and return true or false. Let's see how:
jQuery('#datePicker').datepicker(function{
yearRange: "2014:2015",
maxDate: "+1y",
minDate: "0",
dateFormat: 'mm/dd/yy',
beforeShowDay: function(date) {
/* This is your array of unavailable dates. It might be coming from the database, called by ajax, or whatever */
var unavailableDates = ['2014-12-25', '2014-15-26', '2014-12-31'];
var result = checkUnavailable(date, unavailableDates); // here we pass the comparison to another function
if(!result[0]) return result; // don't check further the unavailable dates
}
});
The function checkUnavailable is called with the current date (once for each date on the shown calendar) and receives also the array of unavailable date. Here is the function:
function checkUnavailable(date, unavailableDates) {
// break the selected date to month, year and day to prepare MySQL format
var m = date.getMonth(), d = date.getDate(), y = date.getFullYear();
m = m+1; // because months start from 0
// add leading zeros
m = m.toString();
if(m.length < 2) m = '0' + m;
d = d.toString();
if(d.length < 2) d = '0' + d;
// now go through the array to see if the date is in the array of unavailable dates or not
for (i = 0; i < unavailableDates.length; i++) {
if(jQuery.inArray(m + '-' + d + '-' + y, unavailableDates) != -1) {
return [false];
}
}
return [true];
}
Note how the function returns true or false as array of one element. This is because beforeShowDay expects the result as array of up to 3 values.
As we solved the problem with the unavailable dates, now we have to allow the user to select a date range from a single calendar. The standard behavior of the calendar expects you to select only one date per calendar. This is not as cool as the thing I'm going to show you.
To achieve a date range selection in a single calendar we'll need a couple of things:
Let's go!
jQuery('#datePicker').datepicker(function{
yearRange: "2014:2015",
maxDate: "+1y",
minDate: "0",
dateFormat: 'mm/dd/yy',
beforeShowDay: function(date) {
/* This is your array of unavailable dates. It might be coming from the database, called by ajax, or whatever */
var unavailableDates = ['2014-12-25', '2014-15-26', '2014-12-31'];
var result = checkUnavailable(date, unavailableDates); // here we pass the comparison to another function
if(!result[0]) return result; // don't check further the unavailable dates
},
onSelect: function(date) {
selectDate(date) // we call another function here to make things clearer
}
});
And here is the function:
function selectDate(date) {
// is this date "from" or "to"?
// the older date is always "from"
// for this we'll check the hidden fields
// they need to be fields with ID "fromDate" and "toDate"
var fromDate = jQuery('#fromDate').val();
var toDate = jQuery('#toDate').val();
// now convert them to Javascript date objects
var fromParts = fromDate.split('-');
fromDate = new Date(fromParts[0], fromParts[1]-1, fromParts[2]);
var toParts = toDate.split('-');
toDate = new Date(toParts[0], toParts[1]-1, toParts[2]);
// convert also the user selected to date object to compare
var selParts = date.split('/');
var selDate = new Date(selParts[2], selParts[0]-1, selParts[1]);
// are we setting from or to? This is the third hidden field
// we use it to store what we are currently setting. It should be populated with "from" by default
var currentlySetting = jQuery('#currentlySetting').val();
// figure out where to set (hidden field)
if(currentlySetting=='from' || selDate < fromDate) {
// selDate is "from"
jQuery('#fromDate').val(selParts[2] + '-' + selParts[0] + '-' + selParts[1]);
}
else {
// selDate is "to"
jQuery('#toDate').val(selParts[2] + '-' + selParts[0] + '-' + selParts[1]);
}
// when setting is in between, let's change currentlySetting
currentlySetting = (currentlySetting == 'from') ? 'to' : 'from';
jQuery('#currentlySetting').val(currentlySetting);
} // end select date
Most of this function is well explained inside the comments. I only want you to pay special attention to the "currentlySetting" field. This small hidden fields helps you know whether the last click was setting "from" or "to" date. So when you first click on 2nd June for example your second click on 8th June will be considered a click to set the "to" date. But if you then click on 11th June it will just extend your selection instead of re-starting it.
It works cool, just give it a try!