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.

5 comments:

  1. This is really great news for me. Still the number of people that have not upgraded to CF9 outnumbers those with CF9.

    I wonder what the future plans are? Releasing as open source? And future features? Do you plan to integrate CF9's caching?

    ColdMVC!! well its your baby, but since you asked, how about calling it "Fire" - as in CF on Fire.

    If you can share the code that would be so awesome. sameer at codecurry dot com. thanks

    ReplyDelete
  2. I really like your framework Tony, I know I have brought it up before but the $ is still a bit confusing but I understand it's meaning. Maybe it would just take time to get used to

    ReplyDelete
  3. this great news. if you would like to share your code you can email me at kumkumbaba@yahoo.com

    Thanks
    Baba

    ReplyDelete
  4. Looks neat.

    Are you starting a list?

    ReplyDelete
  5. @John Allen,

    What do you mean "starting a list"?

    ReplyDelete