{"id":2031,"date":"2011-10-30T05:17:38","date_gmt":"2011-10-30T11:17:38","guid":{"rendered":"http:\/\/bililite.nfshost.com\/blog\/?p=2031"},"modified":"2015-02-25T12:44:06","modified_gmt":"2015-02-25T18:44:06","slug":"a-flexcal-example","status":"publish","type":"post","link":"https:\/\/bililite.com\/blog\/2011\/10\/30\/a-flexcal-example\/","title":{"rendered":"A <code>flexcal<\/code> Example"},"content":{"rendered":"<script src=\"\/inc\/jquery.ui.subclass.js\"><\/script>\r\n<script src=\"\/inc\/jquery.textpopup.js\"><\/script>\r\n<script src=\"\/inc\/jquery.flexcal.js\"><\/script>\r\n<script src=\"\/inc\/jquery.scrollintoview.js\"><\/script>\r\n<script>\r\n  $('<link rel=stylesheet href=\"\/inc\/flexcal.css\" \/>').appendTo('head');\r\n<\/script>\r\n<p>I've had some questions about extending <a href=\"http:\/\/bililite.nfshost.com\/blog\/2009\/04\/03\/new-jquery-widget-flexcal\/\" title=\"New jQuery Widget: flexcal\"><code>flexcal<\/code><\/a> so I created one that combines filtering, output formatting and drop-down menus.<\/p>\r\n<p>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 (<code class=\"language-javascript\">flexcal('after', '_adjustHTML', function (cal){...<\/code>) we will subclass the original widget.<\/p>\r\n<!--more-->\r\n<p>Utility routines to create drop down menus (&lt;select&gt; elements):<\/p>\r\n<pre><code class=\"language-javascript demo\">function option(d, l10n, cal, isMonth, selected){\r\n  return [\r\n    '&lt;option',\r\n    selected ? ' selected=\"selected\"' : '',\r\n    ' value=\"', d.toString(), '\"&gt;',\r\n    isMonth ? l10n.monthNames[cal.m] : l10n.years(cal.y), \r\n    '&lt;\/option&gt;'\r\n  ].join('');\r\n}\r\nwindow.monthSelect = function(currentdate, l10n){\r\n  var f = l10n.calendar;\r\n  var currentcal = f(currentdate), ret = [option(currentdate, l10n, currentcal , true, true)], d = currentdate;\r\n  for (var cal = currentcal; d = cal.prev, cal = f(d), cal.y == currentcal.y; ){\r\n    ret.unshift(option(d, l10n, cal, true, false));\r\n  }\r\n  for (cal = currentcal; d = cal.next, cal = f(d), cal.y == currentcal.y; ){\r\n    ret.push(option(d, l10n, cal, true, false));\r\n  }\r\n  return $('&lt;select&gt;').html(ret.join(''));\r\n};\r\nwindow.yearSelect = function(currentdate, l10n, n){\r\n  var f = l10n.calendar;\r\n  var currentcal = f(currentdate), ret = [option(currentdate, l10n, currentcal , false, true)], d = currentdate;\r\n  for (var i = 0, cal = currentcal; d = cal.prevYear, cal = f(d), i < n; ++i){\r\n    ret.unshift(option(d, l10n, cal, false, false));\r\n  }\r\n  for (var i = 0, cal = currentcal; d = cal.nextYear, cal = f(d), i < n; ++i){\r\n    ret.push(option(d, l10n, cal, false, false));\r\n  }\r\n  return $('&lt;select&gt;').html(ret.join(''));\r\n};<\/code><\/pre>\r\n<pre><code class=\"language-html demo\">&lt;input id=\"fancyflexcalexample\"\/&gt;<\/code><\/pre>\r\n<p>Create the actual calendar:<\/p>\r\n<pre><code class=\"language-javascript demo\">var startdate = new Date;\r\nstartdate.setFullYear(startdate.getFullYear()-17);\r\n$.widget('bililite.fancyflexcal', $.bililite.flexcal, {\r\n  format: function(d){\r\n    \/\/ use the utility function from jquery UI\r\n    return $.datepicker.formatDate (this.options.dateFormat,d);\r\n  },\r\n  _adjustHTML: function(cal){\r\n    this._super(cal);\r\n    console.log(this.options.current);\r\n    cal.find('.ui-datepicker-month').html(monthSelect(this.options.current, this.o.l10n));\r\n    cal.find('.ui-datepicker-year').html(yearSelect(this.options.current, this.o.l10n, 5));\r\n    var self = this;\r\n    cal.find('select').bind('change', function(){\r\n      self._setDate(new Date($(this).val()))\r\n    }); \r\n  },\r\n  options: {\r\n    'class': 'multicalendar', \/\/ we'll need the extra room even with only one calendar\r\n    dateFormat: 'dd\/mm\/yy'\r\n  }\r\n}); \r\n$('#fancyflexcalexample').fancyflexcal({\r\n    position: 'bl',\r\n    calendars: ['en','he-jewish'],\r\n    filter: function(d) {return d >= startdate}, \/\/ filter is an option; set it in the instance\r\n    current: startdate\r\n});<\/code><\/pre>\r\n<h2>Some Subtlety<\/h2>\r\n<p>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:<\/p>\r\n<pre><code class=\"language-html demo\">&lt;input id=\"fancyflexcalexample2\"\/&gt;<\/code><\/pre>\r\n<pre><code class=\"language-javascript demo\">$('#fancyflexcalexample2').fancyflexcal({\r\n    position: 'bl',\r\n    'class': 'multicalendar',\r\n    calendars: ['en','he-jewish'],\r\n    filter: function(d) {return d >= startdate}, \/\/ filter is an option; set it in the instance\r\n    current: startdate\r\n}).keyup(function(){\r\n  $(this).fancyflexcal('option','current',this.value);\r\n});<\/code><\/pre>\r\n<p>But this doesn't work right: the input to the calendar simply uses <code class=\"language-javascript\">new Date(this.element.val())<\/code> internally and according to the <a href=\"http:\/\/www.ecma-international.org\/publications\/files\/ECMA-ST\/Ecma-262.pdf\">standard<\/a> (section 15.9.1.15), the Date element accepts ISO format for dates (yy-mm-dd) and\r\n<blockquote>If the String does not conform to that format the function may fall back to any implementation-specific heuristics or implementation-specific date formats.<\/blockquote> and all the browser I have use the 'm\/d\/yy' format, not the 'd\/m\/yy' format we want.<\/p>\r\n<p>To make this work, we need to override the internal methods for date strings. <code>_createDate(d,oldd)<\/code> returns a <code>Date<\/code> object from <code>d<\/code>; if it is not a valid date, returns <code>oldd<\/code> (if <code>oldd<\/code> is falsey then returns <code>new Date<\/code>). <code>_date2string(d)<\/code> returns the string representation of the <code>Date d<\/code> for use <em>internally<\/em>, as opposed to <code>format(d)<\/code> which returns the string representation of the <code>Date d<\/code> for output. The only requirement is that <code>_createDate(_date2string(d))==d<\/code>; <code>_createDate<\/code> has to understand the string that <code>_date2string<\/code> returns.<\/p>\r\n<p>We can use <code>datepicker<\/code>'s utility routines as well:<\/p>\r\n<pre><code class=\"language-html demo\">&lt;input id=\"fancierflexcalexample\"\/&gt;<\/code><\/pre>\r\n<pre><code class=\"language-javascript demo\">$.widget('bililite.fancierflexcal', $.bililite.fancyflexcal, {\r\n  _createDate: function(d, oldd){\r\n    \/\/ $.datepicker.parseDate either throws an error or returns null for invalid arguments\r\n    try{\r\n      return $.datepicker.parseDate (this.options.dateFormat, d) || this._super(d,oldd);\r\n    }catch(e){\r\n      return this._super(d,oldd);\r\n    }\r\n  },\r\n  _date2string: function(d){\r\n    return $.datepicker.formatDate (this.options.dateFormat, d);\r\n  }\r\n});\r\n$('#fancierflexcalexample').fancierflexcal({\r\n    position: 'bl',\r\n    'class': 'multicalendar',\r\n    calendars: ['en','he-jewish'],\r\n    filter: function(d) {return d >= startdate}, \/\/ filter is an option; set it in the instance\r\n    current: startdate\r\n}).keyup(function(){\r\n  $(this).fancierflexcal('option','current',this.value);\r\n});<\/code><\/pre>","protected":false},"excerpt":{"rendered":"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, [&hellip;]","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[10,5],"tags":[],"_links":{"self":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts\/2031"}],"collection":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/comments?post=2031"}],"version-history":[{"count":39,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts\/2031\/revisions"}],"predecessor-version":[{"id":3520,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts\/2031\/revisions\/3520"}],"wp:attachment":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/media?parent=2031"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/categories?post=2031"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/tags?post=2031"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}