August 30, 2005

make cfselect your sock puppet

i had the need to keep a cfselect synched up w/other cfform objects like grids and trees that were being changed by user inputs (managing IMAP mail folders for instance). digging around the flex docs again, it seems cfselect is part of the mx.controls.listclasses package and you can indeed manipulate it quite a bit using that package's DataSelector/DataProvider class methods. for me the interesting methods were:
  • getItemAt()
  • removeItemAt()
  • addItemAt()
  • replaceItemAt()

and the selectedIndex property. you can do a lot with a bit of action script:

<cfform name="testForm" format="Flash">
<cfselect name="test" size="1" visible="Yes" enabled="Yes" width="200">
<cfoutput>
<cfloop index="i" from="1" to="10">
<option value="#i#">test #i#</option>
</cfloop>
</cfoutput>
</cfselect>
<cfinput type="Button" name="a" value="length" visible="Yes" enabled="Yes"
   onclick="alert(test.length);">

<cfinput type="Button" name="b" value="get at 5" visible="Yes" enabled="Yes"
   onclick="alert('value@5: '+test.getItemAt(4).data);">

<cfinput type="Button" name="c" value="remove at 5" visible="Yes" enabled="Yes"
   onclick="test.removeItemAt(4); alert('removed');">

<cfinput type="Button" name="d" value="add at 5" visible="Yes" enabled="Yes"
   onclick='test.addItemAt(4,"newer test",5);alert("added");'>

<cfinput type="Button" name="e" value="change at 5" visible="Yes" enabled="Yes"
   onclick='test.replaceItemAt(4,"replaced item",5); alert("replaced");'>

<cfinput type="Button" name="f" value="select at 5" visible="Yes" enabled="Yes"
   onclick='test.selectedIndex=4; alert("selected");'>

</cfform>


and an update on the illegal ActionScript error, you can't use any of the restricted keywords even in AS comments. "//new delete" will also throw that error.

August 28, 2005

serendipitous flash forms

as most of the coldfusion/arcIMS usergroup knows, i have a thing about xml. i don't know why (perhaps too many fast balls to the head, one too many falls off my mountain bike, too much tequila in college, who knows) but whenever i see any complex xml my brain just freezes. try as i might, i just can't seem to get my head around anything but the most simple xml. it got to the point where i ported a whole java library to CFCs just so i wouldn't have to use xml for some GIS applications.

i've been struggling for a week now trying to work with the xml that cftree needs if you use it via remoting (and who wouldn't these days with flash forms in cf7)--yes you have to feed it xml rather than a cfquery, cfform like cfdocument, hides rather a lot complexity from us poor coldfusiuon developers. one of the things i'm trying to do was locate a given node in a cftree. while plowing thru the flex documentation i noticed methods to determine if a node was a branch (had nodes under it) and if that branch was open or closed. what you can programatically get at depends on whether a branch is open or closed. don't take my word, put up a ctree w/a button that does a
onclick="alert(cftreeObj.length);"

and see what you get as you open and close nodes in the tree. to make a long story short, while trying to figure out a way to traverse a cftree i stumbled on a method to open or close a cftree via some simple ActionScript.

<cfsavecontent variable="open">
var theTree=theTree; //cftree name
var i=0;
// prime the pump
var thisNode=theTree.getTreeNodeAt(i);
while (thisNode != undefined){
   if (theTree.getIsBranch(thisNode) && ! theTree.getIsOpen(thisNode)){
         theTree.setIsOpen(thisNode,true);
      }
      i++;
      thisNode=theTree.getNodeDisplayedAt(i);
   }
</cfsavecontent>

<cfsavecontent variable="close">
var theTree=theTree; //cftree name
var i=0;
// prime the pump
var thisNode=theTree.getTreeNodeAt(i);
while (thisNode != undefined){
      if (theTree.getIsBranch(thisNode) && theTree.getIsOpen(thisNode)){
         theTree.setIsOpen(thisNode,false);
      }
i++;
thisNode=theTree.getNodeDisplayedAt(i);
   }
</cfsavecontent>


you can see a simple example here. i imagine you could adapt this to open/close any bits you needed.

August 20, 2005

thou shall not....

use illegal ActionScript (AS) in flash forms. i'm posting this as it might save someone else time and frustration. in order to keep a lid on how wild you can get w/the super cool flash forms in cf7 there are certain AS keywords which you aren't allowed to use (in any way, shape or form):
  • __proto__
  • createTextField
  • loadMovie
  • attachMovie
  • Delete
  • New
  • createChild
  • duplicateMovieClip
  • registerClass

this is all documented in Creating Forms in Macromedia Flash/Using ActionScript in Flash forms section of cfdocs. but what the docs aren't saying is that you can potentially use AS in some (to me) strange places. two of the strangest places are in alert message text and tooltips. the tooltips nearly drove me mad trying to figure out why i was getting illegal AS errors in my flash form when none of my injected AS was using any of the restricted keywords. it turns out i had a couple of buttons with the seemingly innocent "delete" word in their tooltips.

so, pay attention. if you're getting seemingly insane illegal AS errors, look to your tooltips and/or your alert messages instead of head-butting your monitor (or bombarding mike nimer with emails).

August 15, 2005

somewhat less cheesy flash forms trick

everybody's doing it. you know you want to. so why not go ahead and get on the flash forms bandwagon? it seems like pretty much every cf developer on the block has now developed a penchant for using cf7's new flash forms. quite a large part of this popularity is due to the ActionScript injection gravy that's been posted on AS Fusion,cf_pim and cfform. who would have thought a few lines of ActionScript would have breathed that kind of life into something as bland as a form?

one of things that took some time for me to catch on to was that the AcstionScript we were injecting into our flash forms, wasn't quite "live". for example, what would the following alert show if injected into a form button that some cranked up monkey was taught to push once a minute for an hour?

<cfsavecontent variable="testInc">
var x=1;
x=x+1;
alert(x.toString());
</cfsavecontent>


while i suppose pretty much everyone else knew the correct answer (2), it took me almost an hour to figure that out. if you wanted to have that var updated you'd need to store it's values outside that snippet (which is re-run exactly as is whenever that crazy monkey mashed down it's button), the usual suspect being one flavor or other of cfinput. even in moderately complex apps this inevitably leads to a plethora of cfinputs named (in our naming scheme) like cheesyFolderHolder, cheesyServerStatus, cheesyIMAPserver, cheesyGrabBag, etc. the relationships and interactions between these can get kind of hairy for a cf developer more used to the seemingly cleaner environment of the back-end (well it seemed kind of by-the-seat-of-your-pants to me anyway).

luckily one of the more interesting bits of ActionScript that cfform does allow is sharedObject (you flash developers can avert your eyes while i try to explain this to my fellow cf developers, it's kind of like a cookie, ok you flash developers can look now). so you can do things like:

<cfsavecontent variable="startUp">
// Create a local shared object
var so = SharedObject.getLocal("imapClient");
so.data.username="joe@blow.com";
so.data.password="yup, still crazy";
so.data.host="blow.com";
so.data.port=143;
so.data.currentFolder="INBOX";
so.data.getHeaders=false;
so.data.debug=false;
// write it out to the client
so.flush();
}   
</cfsavecontent>


which makes the info contained in the data part of that "imapClient" sharedObject available to other ActionScript snippets:

<cfsavecontent variable="checkEmail">
var imapObj=SharedObject.getLocal("imapClient");
var mailArgs = {
   username:imapObj.data.username,
   password:imapObj.data.password,
   imapHost:imapObj.data.host,
   folder:imapObj.data.currentFolder,
   getHeaders:imapObj.data.getHeaders,
   debug:imapObj.data.debug,
   allMessages:true};
.
.
.
//get service
imapService = connection.getService("cfc.remoteIMAPFacade",responseHandler);
//make call
imapService.fetchProfile(mailArgs);   
</cfsavecontent>


one downside is that users can turn this functionality off like cookies in a browser, so you'd need to test for this and fallback accordingly (though i think this means the data won't be saved locally but still available between snippets).

thanks to seth duffy for suggesting this idea.

August 08, 2005

i18n calendars updated

i've updated the i18nCalendars CFCs to include the new coptic and ethiopic calendars added to icu4j 3.4. and that makes a total of nine calendars. i don't imagine having any use for either of these calendars right now but given the recent focus on africa and ethiopia in particular, you never know. if you're interested in using icu4j's new AcceptLanguage method, you'll need to wrapper it. this method makes use of an 'out-parameter' method to return a boolean as to whether the method used a fallback locale (ie. it couldn't find a suitable locale among the server's installed locales, so it returns a fallback locale instead). coldfusion won't pick up on that returned boolean array. below find some java code for this (it returns a structure with the selected locale and whether or not it was a fallback locale):
import java.util.*;
import com.ibm.icu.util.ULocale;

public class ULocaleAcceptLanguage {
/*   
   class:      ULocaleAcceptLanguage
   version:   15-jul-2005
   author:      Paul Hastings paul@sustainableGIS.com
   notes:      simple wrapper class for ICU4J acceptLanguage
*/

   public final static HashMap getULocale(String httpAcceptLanguage){
      HashMap results = new HashMap();
      boolean[] fallback = new boolean[1];
      ULocale thisLocale = ULocale.acceptLanguage(httpAcceptLanguage,fallback);
      Boolean fallB= new Boolean(fallback[0]);
      results.put("locale",thisLocale.toString());
      results.put("fallback",fallB.toString());
      return results;      
   }
   
}
compile this and drop it in your cfinstall classes dir. you can then make use of it:
<cfscript>
   aL=createObject("java","ULocaleAcceptLanguage");
   acceptLanguageStr="en-us,th;q=0.7,ar;q=0.3";
   uL=al.GetULocale(acceptLanguageStr);
</cfscript>

<cfdump var="#uL#">

August 06, 2005

more timezone stuff

i got an email from a coldfusion developer in iran (Behrang Noroozinia) complaining that my timezone CFC wasn't casting to his timezone (tz) correctly. double checking i found that was i was using decimal hours with cf's dateAdd function which only accepts integers. i guess nobody else noticed as most users live in tz w/whole number offsets from UTC. in iran however they have an offset of 3.5 hours (plus 1 hour for DST). interesting that dateAdd didn't throw an error but instead truncated the decimal hours. in any case, even if you don't live in iran, if you use the timezone CFC you should download this fixed version.

ps: the experimental DST bits are on hold now, s. isaac dealey was kind enough to review the logic and sent me a bunch of stuff to look over. hopefully i can get to it this weekend.

August 02, 2005

hot hot hot: icu4j 3.4 released

version 3.4 of icu4j, the super cool i18n java library, has just been released. if you do i18n work in coldfusion or java, this is the library. you can download it from here, it's readme file can be found here. and since i'm on a timezone craze this week, i also noticed that the timezone class has added generic timezones (like "Pacific Time", "United Kingdom", etc.) that should help simplify things a bit.

do youself a favor, get this library.