Skip to content

New jQuery Widget: flexcal

Updated 2011-10-30 to version 2.1, with some bug fixes

Just what the world needs—another date picker

Download the code.

The current incarnation of the jQuery UI datepicker works fine, but I needed something that could handle the Jewish calendar. This is not the same as localizing datepicker to use Hebrew;that just translates the month and day names but uses the same Gregorian calendar as everyone uses. I needed a calendar that could switch between multiple, completely different calendar systems.

The algorithms for converting dates from Jewish to Gregorian are readily available open-source, so that was easy. textpopup is exactly what I need to create the pop-up box for the calendar, and I had already created my own date picker for my old bililite site (don't look at it too closely; it's ugly and I used an old version of Prototype. I was still learning! It works, though). So the pieces were all there.

And so was born flexcal. In its simplest form, it looks like datepicker:

<span>Using <code>datepicker</code>: </span><input id="date1"/>
<br/>
<span>Using <code>flexcal</code>: </span><input id="date2"/>
$('#date1').datepicker();$('#date2').flexcal();

But it can show multiple calendars and allows localization to different calendar systems:

<span>Jewish/civil calendar: </span><input id="date3"/>
$('#date3').flexcal({
  position: 'bl',
  calendars: ['en', 'jewish', 'he-jewish'],
  'class': 'multicalendar'
});

And it allows animated transitions (which I think is in the works for datepicker as well):

<span>Fading transition: </span><input id="date3-1"/>
$('#date3-1').flexcal({
  position: 'bl',
  transition: function(o){
    o.elements.eq(o.currSlide).fadeOut(o.duration);
    o.elements.eq(1-o.currSlide).fadeIn(o.duration);
  },
  transitionOptions: {duration: 'slow'}	
});

Options

It's a subclass of textpopup, so all the options from textpopup and ajaxpopup are available. The url defaults to /inc/flexcal.html, which contains:

Note that all the required CSS is included in the html file, and that it uses the themeable jQuery classes. Setting the 'class' option to multicalendar makes the date rectangles longer, giving more room for the tab bar.

The flexcal options themselves are:

calendars {Array(String || Object)}
Array of calendars to display. Each string is a key into the $.ui.flexcal.l10n object. Default is ['en'], which means that the default is one calendar, using the localization of $.ui.flexcal.l10n.en. If the item is an object then it is the localization object itself
calendarNames {Array(String)}
Names to display on the tabs for the calendars. If empty, uses the default name from the localization object
tab {Number}
Index into the calendars array of the one to display when initially shown
current {Date|String}
If the element value is not set to a valid date, then use this as the initial display date.
filter {undefined|Function}
Function to filter dates. Function is of the form f(d) where d is a Date object; it should return true enable that date or false to disable it. this is set to the <a> element that contains the date number. See the examples below.
transitionOptions
Options to pass to the transition function, below
transition {Function}
Function to handle the transition from one calendar or month to another. Signature is function(options), where options is copied from transitionOptions, augmented by the following fields:
options.$cont
jQuery object that is the container for the calendars
options.elements
jQuery object that has two elements, each an absolutely-positioned <div> one on top of the other, that are the calendars to be transitioned
options.currSlide
The index of the current calendar. Thus, options.elements.eq(options.currSlide) is the currently-showing calendar and options.elements.eq(1-options.currSlide) is the currently hidden calendar that needs to be shown
options.rev
Boolean to indicate that the animation should show a "reverse" transition, going to a previous month.
options.l10n
Localization object for the calendar to be displayed (options.elements.eq(1-options.currSlide)). The transition function can use options.l10n.isRTL to determine if "next month" should be animated right-to-left or left-to-right
The default is function(o){ o.elements.eq(o.currSlide).hide(); o.elements.eq(1-o.currSlide).show(); }
hidetabs {true|false|'conditional'}
True to hide the tab bar, false to show it and 'conditional' to hide it if there is only one calendar to display. Default is 'conditional'.
reposition {true|false}
True to reposition the calendar with every transition; useful if the calendar is above the text element and transitions to months with more weeks obscures the box. Default is True.
l10n {Object (see below)}
Localization object to use if one of the fields in the calendar array item is undefined

The localization (l10n for short) object is the key to the whole thing. $.flexcal.l10n contains objects, each of which is a localization object with the following fields (named to match the corresponding fields in $.datepicker.regional):

name {String}
Default display name, if not overridden by calendarNames
calendar {Function}
Calendar generating function, defaults to Gregorian calendar. Takes a Date object, d, and returns an object with the following fields:
first {Date}
First date of the month containing d
last {Date}
Last date of the month containing d
prev {Date}
Date one month before d
next {Date}
Date one month after d
prevYear {Date}
Date one year before d
nextYear {Date}
Date one year after d
y
Number of the year of d
m
0-indexed number of the month of d
dow
0-indexed number of the day of the week of first
monthNames {Array(String)}
Names of the months
dayNamesMin {Array(String)}
Names of the days of the week
isRTL {Boolean}
True if the calendar should display right-to-left
prevText {String}
Wording on the "previous month" button. The CSS for jQuery UI replaces this with an icon but still uses the text for the title. By the jQuery UI guidelines, it should not include an arrow or chevron
nextText {String}
Wording on the "next month" button
years {Function}
Function to convert a year number to the displayed string. Default is function(n) {return n}
dates{Function}
Function to convert a date number to the displayed string. Default is function(n) {return n}

// default l10n calendar
$.ui.flexcal.prototype.options = {
	name: 'flexcal',
	calendar: function(d){
		var m = d.getMonth(), y = d.getFullYear(), date = d.getDate(), first = new Date (y, m, 1);
		var prev = new Date (y, m-1, date), next = new Date (y, m+1, date);
		if (prev.getDate() != date) prev = new Date (y, m, 0); // adjust for too-short months
		if (next.getDate() != date) next = new Date (y, m+2, 0);
		return {
			first: first,
			last: new Date (y, m+1, 0),
			prev: prev,
			next: next,
			m: m,
			y: y,
			dow: first.getDay()
		};
	},
	monthNames: ['January','February','March','April','May','June',
		'July','August','September','October','November','December'],
	dayNamesMin: ['Su','Mo','Tu','We','Th','Fr','Sa'],
	isRTL: false,
	prevText: 'Previous',
	nextText: 'Next',
	years: function(n) {return n},
	days: function(n) {return n}
};

The API (publicly accessible methods) is documented in a later post.

The plugin comes with three localizations defined (the ones I wanted): $.ui.flexcal.l10n.en (English-language civil calendar), $.ui.flexcal.l10n.jewish (Jewish calendar with English names) and $.ui.flexcal.l10n['jewish-he'] (Jewish calendar with Hebrew names), and two calendar-generating functions: $.ui.flexcal.calendars.gregorian and $.ui.flexcal.calendars.jewish.

The options object that is passed to the transition function was designed to allow drop-in use of Mike Alsup's excellent cycle plugin, with $(selector).flexcal({transition: $.fn.cycle.next}), though I haven't tested this yet.

Examples

French/English calendar. We grab the French localization from the datepicker svn.

<input id="date4"/>
$.getScript('/inc/jquery.ui.datepicker-fr.js', function(){
  $.datepicker.setDefaults($.datepicker.regional['']);
  $.ui.flexcal.l10n.fr = $.datepicker.regional.fr;
  $('#date4').flexcal({
    position: 'rt',
    calendars: ['fr', 'en'],
    calendarNames: ['Français', 'Anglais']
  });
});

The French Revolutionary calendar. This is just to show off how flexible the widget is; 10-day weeks and 5-day months are no problem. View source to see the calendar algorithms.

<input id="date5"/>
$('#date5').flexcal({
  position: 'rt',
  calendars: ['en', 'jacobin']
});

flexcal with fancier transitions and using my scrollIntoView plugin (put the input box at the bottom of the window to see the scrolling effect)

<input id="date6"/>
$('#date6').flexcal({
	position: 'bl',
	calendars: ['en', 'jewish'],
	transition: function(o){
		var dir = o.rev ^ o.l10n.isRTL;
		var first = o.elements.eq(o.currSlide), second = o.elements.eq(1-o.currSlide);
		var h = o.$cont.height(), w = o.$cont.width();
		first.css({zIndex: 1});
		second.css({zIndex: 0}).show();
		first.animate({foo: 0}, { // the {foo:0} seems necessary because we need to animate some property, even if it isn't real
			duration: o.speed,
			step: function(now, fx){
				if (fx.state == 0){
					fx.start = now = dir ? 0 : w;
					fx.end = dir ? w : 0;
				}
				if (dir){
					first.css('clip', 'rect(0px '+w+'px '+h+'px '+now+'px)');
					second.css('clip', 'rect(0px '+now+'px '+h+'px 0px)');
				}else{
					first.css('clip', 'rect(0px '+now+'px '+h+'px 0px)');
					second.css('clip', 'rect(0px '+w+'px '+h+'px '+now+'px)');
				}
			},
			complete: function() {
				// clip is so inconsistently implemented. This way works in FF3, Opera 9, Safari 3,  IE7
				first.hide().css('clip', 'rect(auto)');
				second.css('clip', 'rect(auto)');
			}
		});
	},
	transitionOptions: {speed: 'slow'},
	shown: function() {$(this).flexcal('box').scrollIntoView()}
});

Using draggable. The option cancel: '.ui-state-default' makes sure that clickable elements aren't used as drag handles. A real draggable calendar probably should have the cursor change on hover also.

<input id="date7"/>
<input id="date8"/>
$('#date7').flexcal({
  position: 'rt',
  calendars: ['en', 'fr'],
  calendarNames: ['', 'French'],
  reposition: false
}).flexcal('box').draggable({cancel: '.ui-state-default', cursor: 'move'});

$('#date8').flexcal({
  position: 'lt',
  calendars: ['en', 'fr'],
  calendarNames: ['', 'French'],
  reposition: false
}).flexcal('box').draggable({
  cancel: '.ui-state-default',
  cursor: 'move',
  start: function(){
    $(this).css({right: 'auto', bottom: 'auto'});
  }
});

Differences from datepicker

I intentionally gave this widget fewer options than datepicker; I just included what I thought I would need. Since it uses my subclass-able widget framework, it can easily be extended to be more capable. One thing that is still definitely lacking is keyboard accessibility; I don't know anything about that and have to start experimenting. To be added in some later version, for sure.

The power of Extending Widgets

Some examples of extending flexcal to be more datepicker-like.

Date formatting

The format for the date that is inserted into the text box is very simple; mm/dd/yyyy. You can subclass flexcal to use datepicker's formatting (as with any subclassing, you should look at the source code to figure out what the code is doing):

<input id="date9"/>
<input id="date10"/>

$.ui.flexcal.subclass('ui.fancyflexcal', {
  format: function(d){
    return $.datepicker.formatDate (this.options.dateFormat, d);
  },
  options: {
    dateFormat: $.datepicker.W3C
  }
});

$('#date9').fancyflexcal();
$('#date10').fancyflexcal({dateFormat: 'D, M d, yy'});
Filtering dates

Filter dates with the filter option:

<input id="date11"/>
// allow weekdays only
$('#date11').flexcal({
  filter: function(d){ return d.getDay() != 0 && d.getDay() != 6; }
});

Using the filter option to manipulate the css of the calendar:

<input id="date12"/>
// Put a border around every other Thursday (it's payday!)
$('#date12').flexcal({
  filter: function(d){
    // is it Thursday and is it an even numbered week since the epoch?
    if (d.getDay() == 4 && Math.floor(d.getTime()/(1000*60*60*24*7)) %2 == 0){
      $(this).css('border', '2px solid purple'); // put a nice border on it
    }
    return true; // don't disable anything
  }
});
Drop-down menus

Creating drop-down menus is a bit more complicated, because we can't assume that all the month names in the monthNames array are present in every year, and the definition of the localization calendar routine does not provide with us with a way to get the alternate calendar date for a given Date. The following routines help:


function option(d, l10n, cal, isMonth, selected){
  return [
    '<option',
    selected ? ' selected="selected"' : '',
    ' value="', d.toString(), '">',
    isMonth ? l10n.monthNames[cal.m] : l10n.years(cal.y), 
    '</option>'
  ].join('');
}
window.monthSelect = function(currentdate, l10n){
  var f = l10n.calendar;
  var currentcal = f(currentdate), ret = [option(currentdate, l10n, currentcal , true, true)], d = currentdate;
  for (var cal = currentcal; d = cal.prev, cal = f(d), cal.y == currentcal.y; ){
    ret.unshift(option(d, l10n, cal, true, false));
  }
  for (cal = currentcal; d = cal.next, cal = f(d), cal.y == currentcal.y; ){
    ret.push(option(d, l10n, cal, true, false));
  }
  return $('<select>').html(ret.join(''));
};
window.yearSelect = function(currentdate, l10n, n){
  var f = l10n.calendar;
  var currentcal = f(currentdate), ret = [option(currentdate, l10n, currentcal , false, true)], d = currentdate;
  for (var i = 0, cal = currentcal; d = cal.prevYear, cal = f(d), i < n; ++i){
    ret.unshift(option(d, l10n, cal, false, false));
  }
  for (var i = 0, cal = currentcal; d = cal.nextYear, cal = f(d), i < n; ++i){
    ret.push(option(d, l10n, cal, false, false));
  }
  return $('<select>').html(ret.join(''));
};
<input id="date13"/>

$('#date13').flexcal({'class': 'multicalendar', calendars: ['en','he-jewish']}).flexcal('after', '_adjustHTML', function (cal){
  cal.find('.ui-datepicker-month').html(monthSelect(this.options.current, this.o.l10n));
  cal.find('.ui-datepicker-year').html(yearSelect(this.options.current, this.o.l10n, 5));
  var self = this;
  cal.find('select').bind('change', function(){
    self._setDate(new Date($(this).val()))
  }); 
});

{ 29 } Comments

  1. Erik | April 3, 2009 at 2:28 am | Permalink

    Great Widget!

  2. sravan | May 16, 2009 at 3:07 am | Permalink

    i need the code for the clip notes widget.
    could you please mai it to my email id.

  3. Danny | May 17, 2009 at 2:16 pm | Permalink

    @sravan:
    What clip notes widget? I don’t think I ever wrote anything called that.
    –Danny

  4. Axel | August 14, 2009 at 6:59 am | Permalink

    I have a problem with your code. I cannot make my example page run well.

    Could you please give me an email address where I could write to you ?
    I would like to send you my test.html page to check what is missing.

    Regards

  5. Danny | August 14, 2009 at 3:55 pm | Permalink

    @Axel:
    I unfortunately don’t have a lot of free time for support, but I can try to help. It would be easier if you posted a link to your website rather than emailing me code; most problems come from the interaction of little things that are hard to track down without looking at the whole thing in its “natural habitat”.
    –Danny

  6. Ed | September 10, 2009 at 3:08 am | Permalink

    Hi Danny, firstly thanks so much for creating this datepicker – incredibly useful!

    I am having a minor issue with it…I have set it up to use Joda time (http://joda-time.sourceforge.net/) supplied calendar information – so that any calendar system it supports, is supported by flexcal.

    My problem is that AJAX requests are sent off to a JSP file that returns information (using Joda time) about months for a particular system, to then determine what the calendar object returned should be. If the request is too slow, the calendar object falls back to the gregorian object you implemented. What I want to do is that as soon as that request has been processed, refresh the date picker to display the calendar using the correct calendar system.

    I have been trying to do this by literally invoking setDate(the last d) once the request is complete but it keeps flicking back to the date that it first opened with (today’s date). Any ideas? Is there another method I should be invoking to refresh the calendar?

    Thanks in advance.

  7. Danny | September 10, 2009 at 8:36 am | Permalink

    @Ed:
    Using AJAX to get the calendar from the server sounds cool. flexcal as it stands isn’t set up to deal with asynchronous changes, but it ought to be possible to override setdate to do that. Can you send me your Javascript code and I can try to think about it? I’m real busy, so no promises, but I’m intrigued enough to ignore my real responsibilities to look at it.
    At worst, you could do the AJAX synchronously, but that would mean a seriously unresponsive page.
    –Danny (d.wachss@prodigy.net)

  8. Alexandr | December 29, 2009 at 1:22 am | Permalink

    Hi, Examples don`t work

  9. Danny | December 31, 2009 at 9:39 pm | Permalink

    @Alexandr:
    What system are you using? They work for me in IE8 and FF3.5.
    –Danny

  10. yisman | April 17, 2011 at 5:00 am | Permalink

    hi
    looks like an excellent widget
    i appreciate
    but i cant get it to work
    i made a super-simple website with one page like this

    $(function () { $(‘#date3′).flexcal(); });

    but when i put focus in the textbox i just get a loading spinner and nothing shows up
    when i use datepicker instead, it does work, but flexcal not.
    can u please try to help me?
    am i missing some file? link? thanks

  11. yisman | April 17, 2011 at 5:02 am | Permalink

    oh i see all the html markup got deleted
    here it is encoded

    <html >
    <head>
    <title></title>
    <script type="text/javascript" src="Scripts/jquery-1.4.4.min.js"></script>
    <script type="text/javascript" src="Scripts/jquery-ui.min.js"></script>
    <script type="text/javascript" src="Scripts/jquery.ui.subclass.js"></script>
    <script type="text/javascript" src="Scripts/jquery.textpopup.js"></script>
    <script type="text/javascript" src="Scripts/jquery.flexcal.js"></script>
    </head>
    <body>
    <script type="text/javascript">
    $(function () { $('#date3').flexcal(); });
    </script>
    <input id="date3" type="text" />
    </body>
    </html>

  12. Danny | April 17, 2011 at 3:11 pm | Permalink

    @yisman:
    Sounds like you’re missing the template HTML file that is pulled in with AJAX, so it needs to be on the same server as your page. You have to set the url parameter to the file name (it defaults to /inc/flexcal.html); you can copy http://bililite.com/inc/flexcal.html.
    Hope this makes sense.
    –Danny

  13. Ronny | June 27, 2011 at 11:16 am | Permalink

    hi , i’m having the same problem as yisman , do you mind putting all the needed files in a .zip file
    cause that will be just wonderful

  14. Danny | June 27, 2011 at 2:03 pm | Permalink

    @Ronny:
    I’m not going to have the time to keep it updated if I update the originals, but I put jquery.flexcal.js, the template flexcal.html, and the dependency files jquery.textpopup.js and jquery.ui.subclass.js into a ZIP file: flexcal-archive.zip
    –Danny
    Update 2011-11-07: flexcal-archive.zip no longer exists. The new archive is jquery.flexcal-package.js.

  15. ronny | June 28, 2011 at 5:48 am | Permalink

    still at lost ,
    this is my super-basic code :

    $(document).ready(function() {
    $('#date1').datepicker();
    $('#date2').flexcal();
    });

    Using datepicker:

    Using flexcal:

  16. Danny | July 1, 2011 at 6:04 am | Permalink

    @Ronny:
    That looks right. Can you put up a page with your code and I’ll try to take a look at it?
    –Danny

  17. chanan | October 21, 2011 at 2:06 am | Permalink

    Hi,
    I found this plugin very useful, but I could’nt find out how to set the starting date (in the jquery UI there is a `current` option)
    Can you give a hint how can I modify this?
    10x

  18. Chanan | October 22, 2011 at 10:08 am | Permalink

    Hi Danny,

    I found this a great datepicker, but I looking for a way to set the starting date (the jquery UI datepicker has a `current` property in its settings), or even allow only a range of dates. can you point me out how can this be done?

    10x
    chanan

  19. Danny | October 23, 2011 at 9:40 pm | Permalink

    @Chanan:
    First, make sure you have the most recent version (1.3); the previous version had a bug that I only found when I was trying to answer your comment (thanks!).
    To set the starting date, either set the value of the input before creating the calendar:

    $('input.date').val('10/1/2001').flexcal();

    or use the setDate function after the calendar is created:

    $('input.date').flexcal().flexcal('setDate','10/1/2011');

    As I write this, I realize that setDate and the rest of the public API isn’t documented; I’ll try to get to that as soon as possible.
    For allowing a range of dates, it was possible in the previous version but kind of complicated, so I created a new option, filter, that can be set to a function that takes a Date and returns true to enable that date and false to disable it:

    $('input.date').flexcal({
    	filter: function(d) {return d >= startDate && d <= endDate}
    });

    Hope this helps,
    Danny

  20. Danny | October 26, 2011 at 1:38 pm | Permalink

    @Chanan:
    The big change for version 2.0 (which you inspired) is the addition of the ‘current’ option, so now

    $('input.date').flexcal({current:  '10/1/2011'});

    works.
    However, the value of the input element overrides that, so you can change the option after the fact to force it:

    $('input.date').flexcal().flexcal('option','current','10/1/2011');

    .
    setDate no longer works.
    –Danny

  21. Chanan | October 28, 2011 at 2:11 am | Permalink

    @Danny:

    Thanks for adding this new current property to the options.
    I just tried using it but it does’nt really work for me. I might miss some thing, but I’ve looked into the code and it looks as the _setOption function is never called, so also if I set current hardcoded in the default options it does’nt work.

  22. Danny | October 28, 2011 at 3:06 am | Permalink

    @Chanan:
    Arrgh! That’s what I get for not testing thoroughly. It should work now. Please let me know.
    Danny

  23. Chanan | October 28, 2011 at 3:26 am | Permalink

    @Danny:

    One more question, Is there any easy way to format date format (‘d/m/y’ instead of ‘m/d/y’ for exampe)?

    10x

  24. Danny | October 28, 2011 at 12:34 pm | Permalink

    @Chanan:
    See the section on “date formatting” in the post. You can subclass the widget to use the jQuery UI datepicker formatting code, or just override the format method to return d.getDay()+’/'+d.getMonth()+’/'+d.getFullYear()
    –Danny

  25. Adrian | May 1, 2012 at 3:37 pm | Permalink

    Hello Danny,

    Excellent work here, and write-up. I’m looking to see if there is a way to make the calendar appear for multiple inputs with the same id/name. For example, I’m working on a project where the application is required to display the initial form with one calendar input. Then the user can duplicate the input within the form, to have a second set of, say, computer inventory information. Thus, the form data is passed to the server as an array.
    Can I have something like:
    [code]

    [/code]
    Then when the user clicks on the two input fields, two separate calendars appear. And the date would go through as an array as expected (ex: date=01/01/2010,02/02/2012).

    I tried the datepicker supplied by JQuery’s core ui, but couldn’t get it to work (or maybe I’m not understanding JQuery enough to make it generate a new calendar for each input with matching id/name). So, I’m checking you JQuery plug-in to see if it can do what I’m looking for.

    If your plug-in can do this, would you be so kind to provide a mini example, or explain how I can make this work?

    Thank you!
    Adrian

  26. Adrian | May 1, 2012 at 3:43 pm | Permalink

    noticed the stuff in the code blocks didn’t work. heheh. sorry. Trying again:

  27. Danny | May 1, 2012 at 3:54 pm | Permalink

    @Adrian:
    I don’t understand what you are trying to do without the sample code. Don’t try to format it or anything, just type it in.
    –Danny

  28. Adrian | May 1, 2012 at 3:56 pm | Permalink

    On the second one I did just that… I typed it in.. *sigh*

  29. Adrian | May 1, 2012 at 4:01 pm | Permalink

    I think I answered my own question just now… I meant multiple inputs with the same name. Not ID, sorry. But I believe I figured out how to make this work. Sorry to have wasted your time there. :)

    Thanks!

Post a Comment

Your email is never published nor shared. Required fields are marked *

Notify me of followup comments via e-mail. You can also subscribe without commenting.