A recent question on stackoverflow.com prompted me to revisit the idea of restricting the selectable dates in a cfcalendar. For example, allowing only future dates to be selected. It is relatively easy if you are using flash forms. But it turned out to be a bit more challenging with html forms.
Your mission, should you choose to accept it ..
As CFCalendar is a flash control, you must use ActionScript to manipulate the calendar (for the most part). If you review the underlying Flex documentation, there are at least two properties available for restricting dates: selectableRange and disabledRanges. When you use cfcalendar's startRange or endRange attributes you are actually setting one of those properties: disabledRanges.
If you are not familiar with the underlying properties, selectableRange and disabledRanges are essentially a structure of date ranges, with the keys: rangeStart and rangeEnd. To indicate a property should apply to everything before or everything after a particular date, just omit one of the keys. That is easy enough to do with flash forms and a bit of actionscript. Unfortunately, it seems impossible to achieve using an html form and cfcalendar attributes alone.
Murder She Wrote
I ran into several problems with the html forms scenario. First, cfcalendar's date range attributes do not function the way you might expect. If you only provide one of the attributes (ie startRange or endRange), CF disables a single date. Not everything after the start date or everything before the end date. Again, not the behavior I would have expected.
Second, I do not know of any way to specify a null date using cfcalendar's date attributes (or any tag for that matter). Personally, I think there are cases where allowing a null date not only makes sense, but would be very useful. It would be great if CF had the concept of a NULL_DATE for use with tag attributes. But for now, it does not.
Finally, there is the issue of events. With flash forms you could use the form's onLoad method to initialize the calendar with the desired settings, via actionscript. From what I can tell, there is no way to do this from within an html form. CFCalendar does not have its own onLoad method, and while it does expose events like onFocus, onBlur and onChange, they unfortunately occur too late to be used for initialization code.
This basically leaves you with hacking the date range. You could supply a ridiculous date (like the year 1500 or the year 9999) to simulate and open ended range. I suppose that it is the simplest option. But it rubs me the wrong way, as it is not necessary when using actionScript directly.
Outer Limits
So back to the original goal: do not allow past dates to be selected. I decided to use a little trickery to simulate initialization code. Since cfcalendar only displays one month at a time, I realized all I needed to handle was the current month initially. So I could to disable prior dates in the current month when the calendar loaded. Then disable previous dates when the calendar month is changed. That would create the illusion the calendar was initialized with desired settings.
While it does involve more code, it has one interesting advantage over the date range hack. When using selectableRange, the navigation arrows are disabled for months that cannot be selected.
<!--- disable all dates in the current month, prior to today --->
<cfset endRange = dateAdd("d", -1, now())>
<cfset startRange = dateAdd("d", -( day(endRange)-1 ), endRange)>
<!--- generate the actionScript to set the date ranges (once) --->
<cfsavecontent variable="asCode">
<cfoutput>
if (myCalendar["rangeInitialized"] == undefined) {
var displayDate = new Date(myCalendar.displayedYear, myCalendar.displayedMonth, 1);
var range = {};
#ToScript(endRange, "range.rangeStart", false, true)#;
myCalendar.selectableRange = range;
// modifying selectableRange alter the display month. so restore the previous selection
myCalendar.displayedMonth = displayDate.getMonth();
myCalendar.displayedYear = displayDate.getYear();
myCalendar["rangeInitialized"] = true;
}
</cfoutput>
</cfsavecontent>
<cfform format="html">
<cfcalendar name="myCalendar"
onFocus="#asCode#"
startRange="#startRange#"
endRange="#endRange#"
width="300"
height="200"
/>
</cfform>
Get Smart
If you would prefer to leave the navigation arrows enabled, you could use similar code with disabledRanges. But ... there is a catch. By default, cfcalendar displays today's date with a different background color. For some reason, when you modify disabledRanges, it causes that day of the month to disappear from subsequent months. (Really the font color is probably changed to "ffffff", making it appear invisible).
The only work-around I could find was to disable the highlighting. Not perfect, but it does work. (Now if only I could find that magical style setting...)
<cfset endRange = dateAdd("d", -1, now())>
<cfset startRange = dateAdd("d", -( day(endRange)-1 ), endRange)>
<cfsavecontent variable="asCode">
<cfoutput>
if (myCalendar["rangeInitialized"] == undefined) {
var range = {};
#ToScript(endRange, "range.rangeEnd", false, true)#;
myCalendar.disabledRanges = [ range ];
myCalendar.showToday = false;
myCalendar["rangeInitialized"] = true;
}
</cfoutput>
</cfsavecontent>
<cfform format="html">
<cfcalendar name="myCalendar"
onFocus="#asCode#"
startRange="#startRange#"
endRange="#endRange#"
width="300"
height="200"
/>
</cfform>
Ah, cfcalendar. "Good Times. Good Times." ;)
...Read More