Friday, August 28, 2009

No, my glasses do not need buffing. They just need a little javacast (StringBuffer gotcha)

I was working with the StringBuffer class earlier and ran into a small gotcha. I needed to create a StringBuffer with a capacity larger than default (16 characters). So I used one of the alternate constructors:


<cfscript>
sb = createObject("java", "java.lang.StringBuffer").init( 256 );
</cfscript>


As I tested the results, I noticed some numeric characters kept appearing at the end of the string. At first I thought was it was a mistake in my code. The base data did contain a lot of numbers. (Did I mention the string was reversed as well?). Yes, eventually I realized the value I kept seeing was .. wait for it .. the capacity value.

I was a bit surprised. The last time I checked 256 was a numeric value, and well within the range of Integer.MIN_VALUE and Integer.MAX_VALUE. However, there is another constructor for the StringBuffer class that accepts a java.lang.String and apparently that constructor won out in the "battle of the data types". So my object was initialized with the contents "256" rather than a capacity of 256 characters.

So lesson of the day: Sometimes it pays to use javacast. Even when you think you do not need it.

...Read More

Wednesday, August 12, 2009

An episode of CSI that I actually *would* watch

Like most people, my brain is conditioned to tune out ad banners. But while reading a blog entry today, it did register something seemed off with one of the rotating images. So I actually waited, and watched all of the rotating ads, until one in particular came around again. When I saw the CSI logo, I laughed out loud. No wonder it caught my attention ;)

http://www.fusion-reactor.com/events/cfunited09.cfm

(Nice to know my ColdFusionOnlyFilter is working properly)

...Read More

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

...Read More

Thursday, August 6, 2009

Mission Impossible: Restrict Selectable Dates in CFCalendar / Format="html"

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

  © Blogger templates The Professional Template by Ourblogtemplates.com 2008

Header image adapted from atomicjeep