Archive

Off to CFUNITED

Tomorrow morning I hop on a plane to CFUNITED in Washington DC.  Four days of learning, chatting, getting far too little sleep, and generally being a programmer geek 24 hours a day.  Even better, Joshua and Koen (two coworkers) are coming as well.  It's been a hellish past few weeks at Mentor, and getting a little decompression will be a welcome change.

Scriptlets in CF Anyone?

My last post about Comparators via CF Groovy was simplistic in nature, but the underlying concept is incredibly powerful.  Here's a similar snippet (from the demo app), this time using a Comparator class:

<g:script>
Collections.sort(variables.a, new ReverseDateKeyComparator())
</g:script>

So what do we have here?  Why it's a snippet of Java embedded directly in your CFML page, and it gets dynamically compiled and executed just like the CFML.  Let me say that again: it's a snippet of Java embedded directly in your CFML page.  It's even better than that, though, because it's Groovy, which is Java plus a whole lot more.

In addition to the scriptlet functionality, CF Groovy provides a classloading framework for Groovy scripts and classes, in addition to the ones provided by the CFML runtime and the JVM.  You can define an extra Groovy classpath for your scriptlets like this:

<g:setPath path="#expandPath('/groovy')#,#expandPath('/packages')#" />

There's no way to tell how the ReverseDateKeyComparator class in the first example is implemented, but it happens to be a Groovy class that I've added to the classpath with the <g:setPath> tag.  That class, just like the scriptlet, is dynamically compiled and executed just like the CFML.  This is also very powerful, because it eliminates the compile/build/deploy cycle that is usually required for Java code in a JEE environment.

Combining these two, you can use <g:script> to execute arbitrary scripts from the Groovy classpath, instead of inlining them:

<g:script name="myScript.groovy" />

This lets you reuse scripts across CFML pages, as well as reusing classes between scripts.

I've added CF Groovy to my Projects page, so that's the place for the latest info, as well as some documentation and the all-important Subversion information.

Comparators in CF with Groovy

I don't know about anyone else, but wanting to create little ad hoc Java classes in my CF apps in a common occurrence for me.  With my CF Groovy project, it's both possible and very easy.  No Java, no compiling, no classloading hacks.

Here's a simple example.  I'm going to create an array of structs and then sort the array based on a specific field value (the 'date' field) of the structs.  Doing this in CF usually means making a lookup struct, sorting that, and then rebuilding the array (but only if you have unique values), or converting to a query and using QofQ to do it.  Both have serious drawbacks, as well as being very circuitous/obscuring.

First the array construction (six items, each with 'letter' and 'date' fields, holding what you'd expect).  Nothing very interesting here.

<cfscript>
a = [];
month = createDate(year(now()), month(now()), 1);
for (i = 1; i LTE 6; i = i + 1) {
    s = {
        letter = chr(65 + randRange(0, 25)),
        date = dateAdd("d", randRange(0, 30), month)
    };
    arrayAppend(a, s);
}
</cfscript>

Now, using the <g:script> tag from CF Groovy, we'll sort it by date using a java.util.Comparator:

<g:script>
Collections.sort(variables.a, {o1, o2 ->
    o1.date.compareTo(o2.date)
} as Comparator)
</g:script>

If you want to compare based on the letter, just change the two ".date" references to ".letter".  You can, of course, make the comparator as complicated as you'd like, including referencing other Groovy classes through CF Groovy's classloading, or other Java classes on your classpath.

You can see it in action at the demo page, or get the full source from Subversion at https://ssl.barneyb.com/svn/barneyb/cfgroovy/trunk/demo/.

Speaking at CFDevCon

I'm pleased to say that I'll be speaking at CFDevCon this September over in Brighton, England. The conference should be a great one: lots of great speakers, lots of great topics, and what should be a great location. They also announced last Thursday that early bird pricing has been extended to the end of the month, so if you haven't got a ticket, now's a good time.

I'll be presenting on two topics at the conference: AOP for ColdFusion with ColdSpring, and leveraging Java from ColdFusion. Both really ought to be "CFML" instead of "ColdFusion"; I expect I'll be using either Open BlueDragon or Railo for the presos, but time will tell.

In related news, I've since learned that the lighthouse in my blog header is about 20 miles (I'm from the US, what do you want?) from Brighton. We'll see if I can swing a trip over there to get a picture of me with it. It's kind of humorous that I'd been using that photo for well over four years without knowing where it was, and now I'm going to be flying half way around the world and end up 20 miles from it.

CF Groovy

Those of you who remember CF Rhino will recognize the name for my latest little project. I whipped up a small proof-of-concept integration for Groovy into CF apps tonight while playing with some classloading issues within Groovy itself.

Groovy has a number of advantages for this type of integration of JavaScript, the biggest one being that Groovy IS Java, and carries Java's semantics almost perfectly. So unlike with Rhino, no major conversions are needed moving in and out of Groovy, which makes things enormously easier. CF still brings it's special blend of "insane" to the party, but it works quite well overall. Definitely has more real-world potential than CF Rhino ever did.

CF Groovy is not a whole framework like CF Rhino was, it's really just a proof of concept for integrating the two together and sharing resources. You can get the demo (which includes the engine via svn:externals) from Subversion at https://ssl.barneyb.com/svn/barneyb/cfgroovy/trunk/demo/. Just check it out to a directory named 'cfgroovy' in your webroot, drop the groovy JAR from the 'groovyEngine' subdirectory into your /WEB-INF/lib folder, and you should be done.  You can see it in action at the demo page, though without source to browse, it's not very interesting.

Also, unlike CF Rhino, there's no dependencies on the internals of ColdFusion (the Adobe product). To do some of the bridging to JavaScript, I had to invoke internal APIs of ColdFusion, so it wouldn't run on BlueDragon, Railo, etc. I built CF Groovy with Open BlueDragon and Railo 3 on Tomcat simply because I didn't have a CFML runtime on my box, and CF is like 400MB while oBD is 11MB and Railo is 25MB.

Unfortunately, all three runtimes have bugs that prevent them from working as expected.

Railo was the worst: it has some recursion issue with the JEE.include() call. It also doesn't seem to implement java.util.Map with it's struct implementation, and throws on missing keys rather than returning null.

Open BlueDragon initializes ResultSets from manually assembled queries (and possibly "real" queries) with the row pointer pointed at the first row, rather than before the first row as the spec says it should be.

ColdFusion doesn't create java.util.Date objects with the createDate built in, so trying to call ResultSet.getDate() throws a ClassCastException. Using ResultSet.getObject() and letting the ducks work their magic fixes this issue. It's also 15-35 times larger than the others.

Except for the download size issue, I've got conditionals to cover all the cases in the app, so it should be checkout and run on all three platforms. I haven't tested NA BD, CF7-, or Railo 2. I don't know why they wouldn't work, though, since I'm just doing standard Java stuff. I'm not doing any version conditionals, only platform ones, just in case.

FB3 Lite Updates

After 6 months of use in the wild, I decided to consolidate and republish a couple mods I've made to the framework. The core functionality is unchanged, so for background, check my original post from last year. The enhancements, in no particular order, are as follows:

  • Changed the config at the top of index.cfm to use CFPARAM instead, so the framework can be parameterized by calling code.
  • Most significant is the addition of an 'appSearchPath' control variable, which means index.cfm (the core file) and fbx_Switch.cfm no longer have to be in the same directory. This allows you to put index.cfm in some arbitrary directory (probably with svn:externals) and use it from there. It defaults to ".,.." (this directory and the parent directory), but can be set to whatever is needed.
  • Added the context path and script name to the front of the 'self' variable. I realize this breaks the core on non-JEE CFML implementations, but it fixes some weird redirect issues with IE, so I feel it's worth it. If you don't want it, just remove everything before the question mark.
  • Removed defaulting of the empty fuseaction. Now only a missing fuseaction will be set to the configured default.
  • Added CFABORT to the end of the location() UDF. I went back and forth on this one for quite a while (like measured in days) when I was originally writing the framework, and finally decided to leave it out. After using the framework for a while, I've decided that was the wrong decision, so I've put it in there in this version.
  • Added ability to use multiple circuits via the new 'allowMultipleCircuits' control variable. It defaults to true, which means any slashes in your fuseactions will be treated as path segments, potentially breaking existing apps. If you need to use slashes in your fuseactions, set it to false. When true, a fuseaction of "circuit/fusection" will be converted into an include of "circuit/fbx_Switch.cfm" with attributes.currentFuseaction set to "fuseaction". I've intentionally NOT used the dot separator because they are not circuit aliases as in real Fusebox, but are simply path segments. The include() UDF has always had this ability do to it's strictly path-based nature, though it was undocumented.
  • Added some comments to index.cfm.

There is a project page for the framework, as well as Subversion access available. Current utilization (that I'm aware of) is about 15 distinct applications, most of which I either own or am a contributor to.

Design Patterns in Dynamic Langauges

I went to the Portland JUG meeting this evening to hear Neal Ford talk about design patterns in dynamic languages. Unlike Joshua Marinacci a couple months ago, Neal actually seemed to understand what he was talking about, which was a relief. He gave a quick run-through of dynamic vs. static languages (or ceremonial vs. essential, as he says), and then stepped through a bunch of the GoF design patterns, what they're about, and how they're largely irrelevant in Groovy and/or Ruby because the language provides the "pattern" as part of core language constructs.

What I found most interesting was the different ways that Ruby and Groovy do similar things. While I don't know that I've blogged much about it, those who interact with me in meat-space know all about my aversion for Ruby's ghastly syntax. (I swear Matz was drunk when he was doing the initial implementation and decided it should be a design goal to use every friggin' printable character in ASCII.) Seeing some of it's power compared with Groovy gave me some more appreciation for the language, even if it did nothing for it's appeal. Decorator implementations and method interception were particularly interesting.

Bottom line, however, is that design patterns (at least th GoF's) are incredibly relevant to dynamic languages. In fact, I'd say they're more relevant than ever. With "old" langauge (Java, C++, etc. that the GoF book was targeted at), you could either recognize the patterns and leverage their nomenclature, or you could not recognize them, lose a bit of productivity, but not really be too worse for wear. However, with the dynamic languages having many of the pattern implementations built in, you're really stuck if you don't understand the patterns and what they're about, because you can't use the language without that knowledge.

To use my favorite metaphor (carpentry tools), anyone can use a hammer and a chisel bevel the edge of a board (one specific board, mind you, no retries). It probably won't be very pretty unless you're quite skilled, but anyone can do it given time and patience. Give someone a table router and you've got all kinds of potential to hurt yourself, not to mention chances to ruin their one board, but you can do it quick if you know how. If you don't understand how the more powerful tool works, you're going to be in a world of hurt, but if you can leverage it's strengths, you'll be amazingly better off.

So don't skip the GoF book just because you use a dynamic language. You better know it inside out, just ignore the implementation details.

Google Search Update

After converting to Google Search and giving Google a chance to get everything active, ads stared appearing on the results.  That's part of the deal for getting free search, so they were expected, but after seeing them, I realized the layout wasn't too hot.  With both my sidebar and the ads, the top couple search results were crammed into around 250px of horizontal space - definitely not ideal.

Like the obstacles before, the solution was simple.  I added a couple little jQuery snippets to hide the contents of the sidebar (aside from the search widget) and remove the vertical rule.  Then I set the search results IFRAME's width to take up the entire body of the page, rather than just the main column, and it's done.

Like all compromises, it's not without downsides.  Foremost, there is a brief flicker on page load where the sidebar appears and then vanishes.  The markup for the sidebar is also included in the page, so it has to be generated server-side as well as sent over the wire, just to be ignored when it gets to the browser.  However, contrasted against having to go build a custom page template just for the search results page, two lines of JS and a little network overhead seems a cheap price.

Google Site Search

After frustrations with the K2/WordPress search on my site, I've switched to Google custom search.  I'd never really used the built-in stuff, but when I was setting up my project pages, I did a bunch of searching, and it just didn't seem to work very smoothly.  In particular, you have to manually URL-escape your search string (e.g. replace spaces with pluses) for it to work right.

The Google interface is devoid of such issues, of course, and has the added benefit of searching my full domain, not just my blog posts.  I also included my public Subversion repositories in the search scope, though I'm not sure I'm going to leave that.  The high-churn nature of source code repositories makes for often-outdated results.

The integration was a snap: drop the form code in a Text Widget on the sidebar, and drop the results code in the body of a new Search Results page and I was done.  There were three minor gotchas I had to resolve:

  1. The form didn't repopulate with the current search query on the results page.  That was easily addressed with a little JavaScript in the result page body.
  2. The results page IFRAME was too wide for the site layout.  Again, easily addressed with a little CSS in the result page body.
  3. WordPress lists all top-level pages across the top, and I didn't want the Search Results page showing up there, since clicking it doesn't make sense.  Addressed with a little CSS in the search form widget.

Checkbox Range Selection Update

Just a little update to my checkbox range selection jQuery plugin to allow chaining.  I'd forgotten to return 'this' at the end of the function.  Here's the full source, including the mod:

(function($) {
  $.fn.enableCheckboxRangeSelection = function() {
    var lastCheckbox = null;
    var $spec = this;
    $spec.bind("click", function(e) {
      if (lastCheckbox != null && e.shiftKey) {
        $spec.slice(
          Math.min($spec.index(lastCheckbox), $spec.index(e.target)),
          Math.max($spec.index(lastCheckbox), $spec.index(e.target)) + 1
        ).attr({checked: e.target.checked ? "checked" : ""});
      }
      lastCheckbox = e.target;
    });
    return $spec;
  };
})(jQuery);

You can check the project page as well, for full history and updates.