Wednesday, August 5, 2009

ColdFusion 9 Wishlist

After playing around with ColdFusion 9 for a little bit, here's my wishlist for future enhancements:

• If you run entityLoadByPK("User",1) and there isn't a User with an ID of 1, it would return an empty User. Either that or add a new method, entityGet("User",1), that would accomplish the same result.

• Add cacheExists(key) that would check to see if a certain key exists in the cache. You can currently use cacheGetAllIds() to return an array of all the keys, then use arrayFind(), but that's a little tedious.

• Allow the ORM event handlers preLoad(), preInsert(), preUpdate(), and preDelete() to return a value or prevent the actual event (load/insert/update/delete) from firing. This would allow you to fetch from the cache before hitting the database a lot easier.

• Add syntax support for dynamic method invocation, similar to user["get#property#"](). You can get around this by using <cfinvoke component="#user#" method="get#property#" />, but it's so verbose.

• Add an implicit "instance" scope to components to keep instance data separate from methods, then update the implicit getters/setters to reference the instance scope. That way, if you wanted the component's instance data (aka memento), you could simply return the instance scope.

• Add support for <cfloop query="#users#" index="user"> where user would be a struct containing the current row's data.

• Add support for <cfloop collection="#users#" index="user"> rather than having to use item="user".

• When looping over a struct, it should loop the keys in alphabetical order. Yeah I know if you want a collection to maintain an order, you should probably use an array, but looping alphabetically is better than looping arbitrarily. This would come in handy when working with API's that use the OAuth protocol.

• Allow you to set the default output value to "false" for all components and methods. Not sure why the default value is "true". Doesn't make much sense.

• Allow you to select a setting to automatically trim all form/url variables. I hate trailing whitespace...

• Maintain the case of keys in structs. Kinda lame how user.name turns into user.NAME but user["name"] stays like it should.

That's all I can think of for now. And although it might appear like I'm complaining a lot, I'm actually really excited about CF9. Lots of good stuff ahead.

Tuesday, August 4, 2009

Handling JavaScript Cookies with Prototype

When dealing with cookies, it's important to be aware of certain browser limitations that might affect your user's experience. For example, Internet Explorer can only handle up to 20 cookies per domain at a time. If your site relies heavily on cookies, this could become a problem if the browser reaches its max and starts tossing cookies unexpectedly. In ColdFusion, this could mean losing your CFID and CFTOKEN cookies and getting logged out for no apparent reason.

To address this, I created a simple Cookie manager with the help of the Prototype library. The basic idea is to store multiple cookies in a single cookie using a JSON key-value pair associative array.

The methods available are:


<script type="text/javascript">
Cookie.get(key, default);
Cookie.set(key, value);
Cookie.clear(key);
Cookie.exists(key);
</script>


For example, I might have the following code to toggle the visibility of a menu:


<script type="text/javascript">
toggleMenu = function(set) {
var visible = Cookie.get('menu', true);
if (set) {
visible = !visible;
Cookie.set('menu', visible);
}
visible ? $('menu').show() : $('menu').hide();
}

Event.observe(window,'load',function(){
toggleMenu(false);
});
</script>


If I were to output the cookie, I would see it's being stored as:


{"menu": true}


If I wanted to add similar functionality to handle the visibility of a sidebar, my code might look like following:


<script type="text/javascript">
toggleItem = function(item, set) {
var visible = Cookie.get(item, true);
if (set) {
visible = !visible;
Cookie.set(item, visible);
}
visible ? $(item).show() : $(item).hide();
}

Event.observe(window, 'load', function(){
toggleItem('menu', false);
toggleItem('sidebar', false);
});
</script>


And my cookies would be stored as:


{"menu": true, "sidebar": false}


Not only is it storing multiple key-value pairs in a single cookie, but we have the added bonus of a really clean API for managing cookies. If you're curious, here's the full Cookie class.


<script type="text/javascript">
var Cookie = {

key: 'cookies',

set: function(key, value) {
var cookies = this.getCookies();
cookies[key] = value;
var src = Object.toJSON(cookies).toString();
this.setCookie(this.key, src);
},

get: function(key){
if (this.exists(key)) {
var cookies = this.getCookies();
return cookies[key];
}
if (arguments.length == 2) {
return arguments[1];
}
return;
},

exists: function(key){
return key in this.getCookies();
},

clear: function(key){
var cookies = this.getCookies();
delete cookies[key];
var src = Object.toJSON(cookies).toString();
this.setCookie(this.key, src);
},

getCookies: function() {
return this.hasCookie(this.key) ? this.getCookie(this.key).evalJSON() : {};
},

hasCookie: function(key) {
return this.getCookie(key) != null;
},

setCookie: function(key,value) {
var expires = new Date();
expires.setTime(expires.getTime()+1000*60*60*24*365)
document.cookie = key+'='+escape(value)+'; expires='+expires+'; path=/';
},

getCookie: function(key) {
var cookie = key+'=';
var array = document.cookie.split(';');
for (var i = 0; i < array.length; i++) {
var c = array[i];
while (c.charAt(0) == ' '){
c = c.substring(1, c.length);
}
if (c.indexOf(cookie) == 0) {
var result = c.substring(cookie.length, c.length);
return unescape(result);
};
}
return null;
}
}
</script>


Enjoy!

Monday, August 3, 2009

ColdFusion 9 ORM Event Handlers

Included with Hibernate is a set of event handlers that can be invoked when loading, inserting, updating, and deleting records from Hibernate. My first thought was to use these event handlers as interceptors to pull data from the cache rather than hitting the database. After further review, it appears as if this isn't possible since all of the event handlers return void. Bummer. On the plus side, you can still use the event handlers to put data into the cache, just not pull it out.

Sunday, August 2, 2009

Hibernate, ColdFusion 9, and DAOs

It's been a long weekend, so this post might seem a little scattered and more-or-less random thoughts. Bear with me.

Since Hibernate gives us a consistent interface for data access, most of the Data Access Objects (DAOs) that we write in CF9 will/should look pretty similar. With that in mind, I've been playing around with creating a generic Data Access Object using simple code generation that could be used and decorated by all persistent objects in the application.

The general idea is that all DAOs are generated by a DAO Factory using a skeleton CFC as a template and basic keyword replacement. The factory would then write the DAOs to disk, where the newly created components would be instantiated and returned to ColdSpring as part of a factory-method.

Here's what my ColdSpring definition looks like:


<beans>

<bean id="userService" class="app.services.UserService" />

<bean id="userDAO" factory-bean="daoFactory" factory-method="getUserDAO" />

<bean id="daoFactory" class="utils.orm.DAOFactory">
<constructor-arg name="relativePath">
<value>/app/proxy/dao</value>
</constructor-arg>
</bean>

</beans>


You can even extend the generated DAO by defining the bean like this:


<bean id="userDAO" factory-bean="daoFactory" factory-method="getUserDAO">
<constructor-arg name="extends">
<value>app.model.user.UserDAO</value>
</constructor-arg>
</bean>



The factory would use the onMissingMethod event handler to determine the correct entity to load by stripping "get" and "DAO" from the factory-method being called. In this case, the entity would be "User". The factory would then do really simple text replacement in the DAO template CFC to generic the UserDAO.

Here's what my DAO template looks like:


<cfcomponent>

<cffunction name="get" access="public" output="false" returntype="any">
<cfargument name="id" required="true" />

<cfreturn EntityLoadByPK("${Entity}",arguments.id) />

</cffunction>

<cffunction name="save" access="public" output="false" returntype="void">
<cfargument name="${entity}" required="true" />

<cfset EntitySave(arguments.${entity}) />

</cffunction>

<cffunction name="delete" access="public" output="false" returntype="void">
<cfargument name="${entity}" required="true" />

<cfset EntityDelete(arguments.${entity}) />

</cffunction>

</cfcomponent>


And here's what the final UserDAO looks like:


<cfcomponent>

<cffunction name="get" access="public" output="false" returntype="any">
<cfargument name="id" required="true" />

<cfreturn EntityLoadByPK("User",arguments.id) />

</cffunction>

<cffunction name="save" access="public" output="false" returntype="void">
<cfargument name="user" required="true" />

<cfset EntitySave(arguments.user) />

</cffunction>

<cffunction name="delete" access="public" output="false" returntype="void">
<cfargument name="user" required="true" />

<cfset EntityDelete(arguments.user) />

</cffunction>

</cfcomponent>


Hopefully that makes some sense. I haven't fully tested everything yet, but the theory is there. The next step would be to update the DAO template to add some more complex logic, like adding validation and leveraging CF9's new caching enhancements. Granted you could always use ColdSpring's AOP to add caching, but that's a different topic.

If anyone is interested in seeing more of the code, let me know.

Thursday, July 23, 2009

Looping in ColdFusion

I really wish you could loop an array, query and struct in a consistent manner in ColdFusion. In truth, you're just iterating over a collection of data. Why does it need to be so different? For example, here's looping over a collection of Users and retrieving a single User record.


<cfloop array="#users#" index="user">

</cfloop>



<cfloop collection="#users#" item="i">
<cfset user = users[i] />
</cfloop>



<cfloop query="users">
<!--- convert the row into a struct in order to access all the user's data in a single collection --->
<cfset user = {} />
<cfloop list="#users.columnList#" index="i">
<cfset user[i] = users[i][currentRow] />
</cfloop>
</cfloop>


I really wish I could do...


<cfloop collection="#users#" index="user">

</cfloop>



<cfloop query="#users#" index="user">

</cfloop>


... but that might make too much sense.

Seriously, someone just needs to take all the nice parts of ColdFusion and re-write the damn language. It would make me so much happier. Also, don't get me started on the lack of #'s in cfloop query.