I've had some questions about extending flexcal so I created one that combines filtering, output formatting and drop-down menus.

The filtering will only allow dates going back 17 years, and the calendar will start on that date. The formatting will use European dates (d/m/y). The drop-down menus will be the ones from the original post, but instead of using aspect-oriented programming (flexcal('after', '_adjustHTML', function (cal){...) we will subclass the original widget.

Utility routines to create drop down menus (<select> elements):

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="fancyflexcalexample"/>

Create the actual calendar:

var startdate = new Date;
startdate.setFullYear(startdate.getFullYear()-17);
$.widget('bililite.fancyflexcal', $.bililite.flexcal, {
  format: function(d){
    // use the utility function from jquery UI
    return $.datepicker.formatDate (this.options.dateFormat,d);
  },
  _adjustHTML: function(cal){
    this._super(cal);
    console.log(this.options.current);
    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()))
    }); 
  },
  options: {
    'class': 'multicalendar', // we'll need the extra room even with only one calendar
    dateFormat: 'dd/mm/yy'
  }
}); 
$('#fancyflexcalexample').fancyflexcal({
    position: 'bl',
    calendars: ['en','he-jewish'],
    filter: function(d) {return d >= startdate}, // filter is an option; set it in the instance
    current: startdate
});

Some Subtlety

This works as long as we're only setting the date using the calendar: the output format is what we expect. But we may want to allow the user to type a date into the input box and have it reflected in the calendar. We can watch the input box:

<input id="fancyflexcalexample2"/>
$('#fancyflexcalexample2').fancyflexcal({
    position: 'bl',
    'class': 'multicalendar',
    calendars: ['en','he-jewish'],
    filter: function(d) {return d >= startdate}, // filter is an option; set it in the instance
    current: startdate
}).keyup(function(){
  $(this).fancyflexcal('option','current',this.value);
});

But this doesn't work right: the input to the calendar simply uses new Date(this.element.val()) internally and according to the standard (section 15.9.1.15), the Date element accepts ISO format for dates (yy-mm-dd) and

If the String does not conform to that format the function may fall back to any implementation-specific heuristics or implementation-specific date formats.
and all the browser I have use the 'm/d/yy' format, not the 'd/m/yy' format we want.

To make this work, we need to override the internal methods for date strings. _createDate(d,oldd) returns a Date object from d; if it is not a valid date, returns oldd (if oldd is falsey then returns new Date). _date2string(d) returns the string representation of the Date d for use internally, as opposed to format(d) which returns the string representation of the Date d for output. The only requirement is that _createDate(_date2string(d))==d; _createDate has to understand the string that _date2string returns.

We can use datepicker's utility routines as well:

<input id="fancierflexcalexample"/>
$.widget('bililite.fancierflexcal', $.bililite.fancyflexcal, {
  _createDate: function(d, oldd){
    // $.datepicker.parseDate either throws an error or returns null for invalid arguments
    try{
      return $.datepicker.parseDate (this.options.dateFormat, d) || this._super(d,oldd);
    }catch(e){
      return this._super(d,oldd);
    }
  },
  _date2string: function(d){
    return $.datepicker.formatDate (this.options.dateFormat, d);
  }
});
$('#fancierflexcalexample').fancierflexcal({
    position: 'bl',
    'class': 'multicalendar',
    calendars: ['en','he-jewish'],
    filter: function(d) {return d >= startdate}, // filter is an option; set it in the instance
    current: startdate
}).keyup(function(){
  $(this).fancierflexcal('option','current',this.value);
});

Leave a Reply


Warning: Undefined variable $user_ID in /home/public/blog/wp-content/themes/evanescence/comments.php on line 75