Friday, August 7, 2009

ColdFusion 8: CFINPUT DateField Experiment (Setting Minimum and Maximum Dates)

In a previous entry, I mentioned a question on stackoverflow.com about restricting the "selectable" dates in a calendar control. I eventually figured out how to customize a cfcalendar. But that got me to thinking about how you might do the same for an HTML datefield.

Not being very familiar with the inner workings of datefields, I poked around a bit and discovered that these controls are based on the YUI Calendar Library. After reviewing the YUI documentation, I realized it opened up a lot of possibilities for customization. To start with, the YUI documentation mentions you can limit the available dates by setting the configuration properties: mindate and maxdate. But since cfinput does not have those attributes, the question was how to customize the ColdFusion scripts to add those properties. It turns out it was easier than I thought.


My first step was to figure out how the controls are created. So I made a simple form with a datefield. While viewing the html source, I noticed that ColdFusion generates some javascript code to create the calendar control. The generated code calls a method named ColdFusion.Calendar.setUpCalendar(). The source of that method can be found in C:\ColdFusion8\wwwroot\CFIDE\scripts\ajax\package\cfcalendar.js. If you view the source of cfcalendar.js, you can clearly see how the function arguments correspond to cfinput attributes.



The next challenge was figuring out how to access the darn things from javascript! Arguably, one of the best things about CFForm controls is they are created "automagically", with minimal code. Ironically, it is also one of the worst things about CFForm controls. Because the objects are dynamically created behind the scenes, extending them is a bit of a challenge. But with a little work, it can be done.

Understandably, calendar objects are dynamically named by ColdFusion. But part of the object name is based on a counter number. That makes it a bit difficult if you are trying to locate the calendar instance tied to a specific form field. Now I searched high and low for a simple method or object that would return the calendar instance linked to a specific form field. But I could not find one. So I decided to make my own.

At the top of cfcalendar.js are a few lines that initialize the ColdFusion.Calendar object, if it does not already exist. Inside this section, I added a new object called lookupByID. As its name implies, it will store calendar instances by form field id, for easier access.


if(!ColdFusion.Calendar){
ColdFusion.Calendar={};
ColdFusion.Calendar.lookupByID={}; // NEW CODE
}

Further down in the file, new calendar instances are created and assigned to a rather cryptically named variable _14. Just after that section is where I added the rest of the custom code. First, I store new calendar instances in the look-up structure.

...

ColdFusion.Calendar.lookupByID[_e.id]=_14;

The next step is detecting whether a minimum or maximum date was specified for the current field. Then adding those dates to the calendar settings. Now obviously minDate and maxDate are not official attributes of <cfinput>. (If they were, there would be no need for this entry.) However, ColdFusion will simply ignore them and pass them through to the generated HTML. So it is easy to extract any extra attributes with a little DOM magic.

// Apply the minimum date to the calendar instance
if ( document.getElementById && _e.getAttribute("minDate") ) {
_14.cfg.setProperty("mindate", _e.getAttribute("minDate"));
}

// Apply the maximum date to the calendar instance
if ( document.getElementById && _e.getAttribute("maxDate") ) {
_14.cfg.setProperty("maxdate", _e.getAttribute("maxDate"));
}

Once the calendar settings are updated, any dates outside the allowed range will be disabled.



As users can also enter dates manually, I needed a validation function as well. Fortunately, the robust YUI library already contains a function for detecting whether a date is outside the allowed range. So with a small amount of code, I created a function that could be called from the onValidate event.


ColdFusion.Calendar.checkDate = function(_form, _field, _value) {
var cal = ColdFusion.Calendar.lookupByID[_field.id];
var str = _value.replace(/\s+/g, '');
var isOK = str.length == 0;

if (!isOK) {
var dtTime = Date.parse(_value);
if (dtTime && !isNaN(dtTime )) {
var dt = new Date(dtTime );
isOK = !cal.isDateOOB( dt );
}
}

return isOK;
}

A few form changes later, I had an instant, customized datefield. Now the code below is not highly tested. But I thought it would be useful to show an example of how you might customize datefields.

As always, any comments/questions or suggestions are welcome.


CF Example
<cfform format="html">

<cfinput type="datefield"
name="startDate"
id="startDate"
required="true"
mask="MM/dd/yyyy"
minDate="08/5/2009"
maxDate="09/15/2009"
onValidate="ColdFusion.Calendar.checkDate"
message="Start Date is invalid or out of range" >
<cfinput type="submit" name="submit" value="Submit Form">
</cfform>


CFCalendar.js (Changes only)
Notes:
1. If you are smart, you will make a backup before making any modifications ;)
2. For brevity, the code below uses Date.parse to convert input values to date objects. You may prefer to use something a bit more robust.

// ...

if(!ColdFusion.Calendar){
ColdFusion.Calendar={};
/////////////////////////////////////
// BEGIN CUSTOM CODE

ColdFusion.Calendar.lookupByID={};

// END CUSTOM CODE
/////////////////////////////////////
}

// ... more code

ColdFusion.objectCache[_11+_b]=_14;

/////////////////////////////////////
// BEGIN CUSTOM CODE

// Add this calendar to the lookup structure (key = form field element ID)
ColdFusion.Calendar.lookupByID[_e.id]=_14;

// Apply any minimum date to the calendar instance
if ( document.getElementById && _e.getAttribute("minDate") ) {
_14.cfg.setProperty("mindate", _e.getAttribute("minDate"));
}

// Apply any maximum date to the calendar instance
if ( document.getElementById && _e.getAttribute("maxDate") ) {
_14.cfg.setProperty("maxdate", _e.getAttribute("maxDate"));
}

// Verify the text field value is a date and is within range
ColdFusion.Calendar.checkDate = function(_form, _field, _value) {
// lookup the calendar instance for this field
var cal = ColdFusion.Calendar.lookupByID[_field.id];
var str = _value.replace(/\s+/g, '');
var isOK = str.length == 0;

// if the text box value is not empty ..
if (!isOK) {
var dtTime = Date.parse(_value);
// verify this is a valid date, and is not outside of the allowed range
if (dtTime && !isNaN(dtTime )) {
var dt = new Date(dtTime );
isOK = !cal.isDateOOB( dt );
}
}

return isOK;
}

// END CUSTOM CODE
/////////////////////////////////////

// ... more code

8 comments:

Braden August 24, 2009 at 1:47 PM  

I am having issues with this. I keep getting errors. I get this.

_11 is not defined

Please help.

cfSearching August 24, 2009 at 8:27 PM  

@Braden,

The custom code does not even contain variable _11. So it sounds like you have made the wrong changes to the file. I would restore your back-up and start over.

There are only two changes to the base file. They are marked in the example as //...BEGIN/END CUSTOM CODE.

There is a one line change just after line 10:

ColdFusion.Calendar={};

.. and another just after line 64:
ColdFusion.objectCache[_11+_b]=_14;

-Leigh

Braden August 24, 2009 at 8:29 PM  

I screwed it up. I figured it out. It does work and it is great. Thanks!

cfSearching August 25, 2009 at 11:49 AM  

@Braden,

Glad you figured it out!

-Leigh

Anonymous,  November 25, 2009 at 5:52 AM  

Thank you so much -- it works perfectly! You are a lifesaver =)

//Toby

sean hogge,  December 15, 2009 at 1:07 PM  

I just used this to constrict the selectable dates of a cfinput datefield in Coldfusion 8 by pasting in what you've created at the appropriate spots. Worked first try!

Thanks for sharing this solution.

Anonymous,  January 18, 2011 at 2:25 PM  

Yes, but how do we change the minDate or maxDate cfinput attributes dynamically based on the selection of calendar A to control another calender B?

cfSearching January 20, 2011 at 10:17 AM  

@Anonymous,

I do not know. My guess would be try binding the other fields to a javascript function. Then inside call setProperty() to update the "mindate" and "maxdate" values of the calendar and refresh.

  © Blogger templates The Professional Template by Ourblogtemplates.com 2008

Header image adapted from atomicjeep