Thursday, August 25, 2011

Combine jQuery Datepicker and Spinner for fast day incrementation / decrementation

Is it possible to add + / - buttons to jQuery UI Datepicker in order to increment / decrement days in a comfortable way? Yes, it's possible. I have done this task - see screenshot.


This is an extended PrimeFaces Calendar with added "dateSpinner" mode. I would like to omit JSF part and only show HTML / CSS / JavaScript part of such combined calendar. All what you need is to wrap the datepicker markup with a span element, add themable up / down arrows and use datepicker utility functions $.datepicker.formatDate(format, date, settings) and $.datepicker.parseDate(format, value, settings).

HTML
Original HTML with a calendar icon (called icon trigger in jQuery Datepicker) looks very simple
 
<input id="datepicker" class="hasDatepicker" type="text">
<img class="ui-datepicker-trigger" src="images/calendar.gif" alt="..." title="...">
 
You have to extend this as follows
<span id="datepickerWrapper" class="ui-ccalendar ui-widget ui-corner-all">
	<input id="datepicker" class="hasDatepicker" type="text">
	<img class="ui-datepicker-trigger" src="images/calendar.gif" alt="..." title="...">
	<a class="ui-ccalendar-button ui-ccalendar-up ui-corner-tr ui-button ui-widget ui-state-default ui-button-text-only">
		<span class="ui-button-text">
			<span class="ui-icon ui-icon-triangle-1-n"></span>
		</span>
	</a>
	<a class="ui-ccalendar-button ui-ccalendar-down ui-corner-br ui-button ui-widget ui-state-default ui-button-text-only">
		<span class="ui-button-text">
			<span class="ui-icon ui-icon-triangle-1-s"></span>
		</span>
	</a>
</span>

CSS
The most CSS classes above are jQuery UI classes. I marked own classes with ui-ccalendar. In the CSS part is very important to shift spinner's arrow buttons to left. I have shifted absolute positioned buttons with CSS statement right: 17px;. We achieve with this displacement that the calendar icon is visible.
.ui-ccalendar {
    display: inline-block;
    overflow: visible;
    padding: 0;
    position: relative;
    vertical-align: middle;
}

.ui-ccalendar-button {
    cursor: default;
    display: block;
    font-size: 0.5em;
    height: 50%;
    margin: 0;
    overflow: hidden;
    padding: 0;
    position: absolute;
    right: 17px;
    text-align: center;
    vertical-align: middle;
    width: 16px;
    z-index: 100;
}

.ui-ccalendar .ui-icon {
    left: 0;
    margin-top: -8px;
    position: absolute;
    top: 50%;
}

.ui-ccalendar-up {
    top: 0;
}

.ui-ccalendar-down {
    bottom: 0;
}

.ui-ccalendar .ui-icon-triangle-1-s {
    background-position: -65px -16px;
}

.ui-ccalendar .ui-icon-triangle-1-n {
    margin-top: -9px;
}

JavaScript
In this part I use the mentioned above utility functions formatDate() / parseDate() and setDate() / getDate() API of Date object to increment / decrement a single day. Month and year boundaries are considered automatically and in- / decremented if necessary. In- / decrementation logic is bound to mousedown event on spinner buttons.
var datepickerInput = $('#datepicker');
datepickerInput.datepicker({dateFormat: 'yy-mm-dd', ... other configuration if needed ...});

$('#datepickerWrapper').children('.ui-ccalendar-button').mouseover(function() {
	$(this).addClass('ui-state-hover');
}).mouseout(function() {
	$(this).removeClass('ui-state-hover');
}).mouseup(function() {
	$(this).removeClass('ui-state-active');
}).mousedown(function() {
	var el = $(this);
	el.addClass('ui-state-active');
	try {
		// get configured date format
		var dateFormat = datepickerInput.datepicker("option", "dateFormat");

		// extract a date from a string value with a specified format
		var date = $.datepicker.parseDate(dateFormat, datepickerInput.val());
		if (el.hasClass('ui-ccalendar-up')) {
			// increment day
			date.setDate(date.getDate() + 1);
		} else {
			// decrement day
			date.setDate(date.getDate() - 1);
		}

		// format a date into a string value with a specified format
		var strDate = $.datepicker.formatDate(dateFormat, date);
		datepickerInput.val(strDate);
	} catch (err) {
		// ignore and nothing to do
	}
});
Important is here "dateFormat" option. Date format normally depends on user locale and should be passed from outside.

P.S. Just now I found a desktop example of such combined calendar too. So, you see, it's an useful and established widget :-).

3 comments:

  1. Great addition... Will propose this internally as wel...

    ReplyDelete
  2. I used this example with an existing jquery-ui datepicker (with image) that I had already implemented. This worked very well with a few CSS tweaks. I just had to make sure I put my style CSS on the page AFTER I had called in the jquery-ui CSS and JS files. Maybe someone else will find that tip useful. Otherwise, thank you very much for sharing your implementation of the datepicker-spinner combo!

    ReplyDelete

Note: Only a member of this blog may post a comment.