jQuery DatePicker Tweaks: Master The Date Picker

Show Unavailable Date and Select Date Range

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:

  • Allow for unavailable dates. These dates will be colored in different color and will not be selectable.
  • Allow the user to select a date range into the same date picker. The date range will be colored. The user will be able to change the selected date range.

Screenshot of the calendar

See a live example of the tweaks here: http://wp-hostel.com/demo/twin-private-ensuite/

Let's see how to do both things.

Managing Unavailable Dates

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:

  • maxDate sets the maximum selectable date. Setting it to "+1y" means the user can select dates up to one year from today. (See the API for full information)
  • minDate is 0 which means date selection starts from today. Of course you don't want to allow people book past dates in your hotel or hostel! But if you are doing a different app, you may need different setting here.

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.

Selecting Date Range

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:

  • An "onselect" call to a function that will do the work
  • Hidden fields which will store the selected dates
  • Another hidden field to store the currently setting operation (more info in the function below)
  • CSS class to be used for highlighting the dates

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!