Saturday, February 20, 2010

Another ColdFusion Framework?

Over the past couple weeks, I've spent some free time creating a simple MVC Front Controller framework for ColdFusion 9. What? Another framework for ColdFusion? Why would you want to do such a thing? While there are plenty of solid frameworks out there to choose from, none of them really showcase the power and elegance of ColdFusion 9 (and specifically ORM). Besides, it was fun to write.

While the framework is loosely based on Grails and Ruby on Rails, I've borrowed inspiration and concepts from other ColdFusion (ColdBox, ColdFusion on Wheels, Framework One, Mach-II, Model-Glue, QuickSilver) and non-ColdFusion (Spring, jQuery, Swiz) frameworks, as well as incorporated some of my own tips and tricks, too. Best of all, it's powered by ColdFusion 9, Hibernate, and ColdSpring.

Without going into too many specifics, here are some key concepts in the framework:
* convention over configuration
* MVC design pattern
* automatic creation of controller beans
* implicit invocation of controller actions
* centralized event dispatching
* metadata-driven AOP
* dynamic finders
* global helpers available using $
* implicit rendering of views and layouts
* helper tags and method plugins for views
* form data binding
* params and flash scopes

Finally, here's a sample application I created called UserDirectory, which performs your basic user CRUD.



Application.cfc

/**
* @extends coldmvc.Application
*/
component {

}


config/settings.ini

[default]
controller=users

[development]
development=true


app/controllers/UserController.cfc

/**
* @action list
* @extends coldmvc.Controller
*/
component {

function list() {

var paging = $.paging.options();

var options = {
sort = "firstName",
order = "asc",
max = paging.max,
offset = paging.offset
};

var search = $.params.get("search");

if (search != "") {
params.users = _User.findAllByFirstNameLikeOrLastNameLike(search, search, options);
params.count = _User.countByFirstNameLikeOrLastNameLike(search, search);
}
else {
params.users = _User.list(options);
params.count = _User.count();
}

}

function edit() {

var userID = $.params.get("userID");
params.user = _User.get(userID);

}

function save() {

var user = _User.get(params.user.id);
user.populate(params.user);
user.save();

flash.message = "User saved successfully";
redirect("edit", "userID=#user.id()#");

}

function delete() {

var user = _User.get(params.userID);
user.delete();

flash.message = "User deleted successfully";
redirect("list");

}

}


app/model/User.cfc

/**
* @extends coldmvc.Model
* @persistent true
*/
component {

property id;
property firstName;
property lastName;
property email;

}


app/views/users/list.cfm

<cfoutput>
<form>
Search: <input name="search" value="#search#" wrapper="false" />
</form>

<table label="Users">
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Edit</th>
<th>Delete</th>
</tr>
<each in="#users#" value="user" index="i">
<tr index="#i#">
<td>#user.firstName()#</td>
<td>#user.lastName()#</td>
<td>#user.email()#</td>
<td><a href="#linkTo('edit','userID=#user.id()#')#">Edit</a></td>
<td><a href="#linkTo('delete','userID=#user.id()#')#">Delete</a></td>
</tr>
</each>
<cfif users.size() eq 0>
<tr>
<td colspan="3">No users have been added yet</td>
</tr>
</cfif>
</table>
<paging records="#count#" />
<a href="#linkTo('edit')#">Add a User</a>
</cfoutput>


app/views/users/edit.cfm

<cfoutput>
<fieldset label="User Information">
<form action="save" bind="user">
<hidden name="id" />
<input name="firstName" />
<input name="lastName" />
<input name="email" />
<submit />
<cfif user.exists()>
<a href="#linkTo('delete','userID=#user.id()#')#">Delete</a>
</cfif>
<a href="#linkTo('list')#">Back to List</a>
</form>
</fieldset>
</cfoutput>


app/layouts/users.cfm

<cfoutput>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html lang="en-us" xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>User Directory</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
#renderCSS("reset.css")#
#renderCSS("style.css")#
#renderJS("jquery.1.4.2.js")#
</head>
<body>
<cfif structKeyExists(params, "message")>
<div class="flash">
#params.message#
</div>
</cfif>
<div class="content">
#render()#
</div>
</body>
</html>
</cfoutput>


If you couldn't tell from the code, I've named the framework ColdMVC. Pretty boring, right? If someone has a better idea, I'm open for suggestions. I've written a handful of small sample applications with the framework already and, to be honest, it's made programming fun again. Plus, writing everything in cfscript syntax makes ColdFusion finally feel like a real scripting language.

Eventually I'd like to release the framework as open source, but I haven't had the time to make it happen yet. Plus there are a couple more features I'd like to finalize first, like validation and more robust route mapping. In the meantime, if anyone is interested in learning more or seeing some more code, shoot me an email or feel free to track me down at cf.Objective() in a couple weeks.

Tuesday, February 16, 2010

The Passionate Programmer

If you're a software developer and you care about your career and your craft, I highly recommend reading The Passionate Programmer: Creating a Remarkable Career in Software Development by Chad Fowler. It's one of the best books I've read in a long time.

Saturday, February 13, 2010

Using Custom Tags to Render HTML Elements

I'm a big fan of using custom tags to render form fields. This isn't a particularly new idea, as Grails, Ruby on Rails, CFWheels, and Mach-II (to name a few) all have similar features. However, each has their own slightly different syntax for how the tags are called. Depending on the language/framework, you could use <g:textField />, <%= text_field %>, text_field(), textField(), or <form:input />.

So which would I choose? Personally I prefer keeping the tags as tags, so text_field() and textField() are out. And since we're using ColdFusion, we'll need to throw out the Rails syntax too. That leaves us with <g:textField /> and <form:input />. I don't know about you, but I like to write as little code as possible. So if given the choice, I would probably combine the two and use <g:input />.

While the g prefix works well for Grails, it doesn't really apply to ColdFusion. So naturally I tried using a cf prefix.


<cfimport prefix="cf" taglib="tags/" />

<cf:input type="text" name="firstName" value="Tony" />


ColdFusion didn't like this very much. Here's the ever-so-friendly error message: A tag starting with 'CF' has been detected. This tag is not supported by this version of ColdFusion. Please verify your typo and try again. Unknown tag: cfmodule.

So I decided to look up the valid naming rules for the cfimport tag. While doing so, I stumbled upon this gem: If you import a CFML custom tag directory and specify an empty value, "", for this attribute, you can call the custom tags without using a prefix. Intrigued, I tested it out.


<cfimport prefix="" taglib="tags/" />

<input type="text" name="firstName" value="Tony" />


Sure enough, ColdFusion sent the call to /tags/input.cfm, which looks like this:


<cfoutput>
<cfif thisTag.executionMode eq "start">
<cfset html = [] />
<cfset html.add("input") />
<cfloop collection="#attributes#" item="attribute">
<cfset html.add('#lcase(attribute)#="#attributes[attribute]#"') />
</cfloop>
<cfelse>
<#arrayToList(html, " ")# />
</cfif>
</cfoutput>


I'll admit this isn't the most intuitive solution, since it appears as if you're rendering a normal HTML element. However, if used correctly, this could be extremely powerful. For starters, you could automatically wrap all dynamic values in htmlEditFormat(). You could also set the "id" attribute to the "name" attribute if it wasn't specified, set a default "title" attribute, wrap all the fields with an appropriate label, or even add in your own custom attributes. Better yet, you could create your own "HTML" elements with their own custom behavior. Take the following hypothetical form for example:


<form controller="users" action="update" bind="user">
<hidden name="id" />
<input name="firstName" />
<input name="lastName" />
<email name="email" />
<date name="birthDate" />
<phone name="phoneNumber" />
<address name="homeAddress" />
<radio name="gender" options="Male,Female" />
<checkbox name="subscribeToNewsletter" />
<submit label="Update Account" />
</form>


Yeah it might take some time getting used to, but I think the power might outweigh the initial learning curve in the long run.

Friday, February 12, 2010

Underlying Java Methods for ColdFusion Data Types

Every now and then in blog posts I'll see people using Java methods rather than ColdFusion functions in their code examples. For instance, they might use <cfset users.add("Tony") /> rather than the typical <cfset arrayAppend(users, "Tony") />. I never gave it much thought before, since I didn't know what all was available. Today I decided to spend a little time playing around with the underlying Java methods available for different ColdFusion data types. I came up with the following script that shows all the available methods.


<cfset dataTypes = {} />
<cfset dataTypes["array"] = [] />
<cfset dataTypes["boolean"] = true />
<cfset dataTypes["date"] = now() />
<cfset dataTypes["integer"] = 1 />
<cfset dataTypes["numeric"] = 1.5 />
<cfset dataTypes["query"] = queryNew("id") />
<cfset dataTypes["string"] = "" />
<cfset dataTypes["struct"] = {} />

<cfset metaData = {} />

<cfloop collection="#dataTypes#" item="dataType">

<cfset metaData[dataType] = {} />
<cfset metaData[dataType].class = dataTypes[dataType].getClass().toString() />
<cfset metaData[dataType].methods = {} />

<cfset classMethods = dataTypes[dataType].getClass().getMethods() />

<cfloop array="#classMethods#" index="classMethod">

<cfset method = {} />
<cfset method.string = classMethod.toString() />
<cfset method.name = listLast(listFirst(method.string, "("), ".")/>

<cfset method.parameters = listToArray(listFirst(listLast(method.string, "("), ")")) />

<cfloop from="1" to="#arrayLen(method.parameters)#" index="i">
<cfset method.parameters[i] = listLast(method.parameters[i], ".") />
</cfloop>

<cfset method.returnType = listToArray(listFirst(method.string, "("), " ") />
<cfset method.returnType = listLast(method.returnType[arrayLen(method.returnType)-1], ".") />

<cfset metaData[dataType].methods[method.name & "(" & arrayToList(method.parameters) & ")"] = method />

</cfloop>

</cfloop>


If you were to <cfdump var="#metaData#" />, you'd get something that looks like this:



I haven't spent too much time playing around with all methods, but let's take a look at a couple examples:

Check to see if a start date is before an end date:

<cfset startDate = now() />
<cfset endDate = dateAdd("d", 1, startDate) />

<cfif dateCompare(startDate, endDate) eq -1>
The start date is before the end date.
<cfelse>
The start date is not before the end date.
</cfif>


Same thing, but using Java:

<cfset startDate = now() />
<cfset endDate = dateAdd("d", 1, startDate) />

<cfif startDate.before(endDate)>
The start date is before the end date.
<cfelse>
The start date is not before the end date.
</cfif>


Check to see if a name ends with "Nelson":

<cfset name = "Tony Nelson" />

<cfif right(name, 6) eq "Nelson">
The name ends with "Nelson".
<cfelse>
The name does not end with "Nelson".
</cfif>


Same thing, but using Java:

<cfset name = "Tony Nelson" />

<cfif name.endsWith("Nelson")>
The name ends with "Nelson".
<cfelse>
The name does not end with "Nelson".
</cfif>


Get the number of elements in a struct:

<cfset states = {} />
<cfset states["MN"] = "Minnesota" />
<cfset states["ND"] = "North Dakota" />

<cfoutput>
#structCount(states)#
</cfoutput>


Same thing, but using Java:

<cfset states = {} />
<cfset states["MN"] = "Minnesota" />
<cfset states["ND"] = "North Dakota" />

<cfoutput>
#states.size()#
</cfoutput>


Append the items of one array onto another array:

<cfset states = [] />
<cfset states[1] = "Minnesota" />
<cfset states[2] = "North Dakota" />

<cfset newStates = [] />
<cfset newStates[1] = "South Dakota" />

<cfloop array="#newStates#" index="state">
<cfset arrayAppend(states, state) />
</cfloop>


Same thing, but using Java:

<cfset states = [] />
<cfset states[1] = "Minnesota" />
<cfset states[2] = "North Dakota" />

<cfset newStates = [] />
<cfset newStates[1] = "South Dakota" />

<cfset states.addAll(newStates) />


Granted the differences are all pretty minor, yet the Java examples all read better to me.