Tuesday, December 29, 2009

jQuery Autocomplete and ColdFusion

While migrating my applications from Prototype to jQuery, I needed to replace the Ajax.Autocompleter that comes with script.aculo.us, the effects library written for Prototype.

After a quick Google search, I found a pretty good plugin that looked like it would do what I wanted. However, there was a pretty big difference in how the two plugins work. Ajax.Autocompleter must return an unordered list (<ul>), with each suggestion rendered inside its own list item (<li>). Piece of cake.

However, the jQuery Autocompleter expects a string, with each suggestion rendered on its own line. This threw me for a loop at first, since ColdFusion isn't always the best at dealing with whitespace. After a little thought, here's what I came up with.

index.cfm

<link rel="stylesheet" type="text/css" href="jquery.autocomplete.css" />
<script src="jquery-1.3.2.js"></script>
<script src="jquery.autocomplete.js"></script>

<script type="text/javascript">
$().ready(function() {
$('#search').autocomplete('UserService.cfc?method=autocomplete&_cf_nodebug=true', {
multiple: true,
formatItem: function(row) {
var user = JSON.parse(row.toString());
return user.name;
},
formatResult: function(row) {
var user = JSON.parse(row.toString());
return user.email;
}
});

$('#search').result(function(event, data, formatted) {
var user = JSON.parse(data.toString());
alert('You selected user '+user.id);
});

});
</script>

<input type="text" id="search" name="search" size="60" />


UserService.cfc

component {

remote void function autocomplete(required string q) {

// get the users
var users = searchUsers(arguments.q);
var result = [];
var i = "";

for (i=1; i <= arrayLen(users); i++) {

// build the user record
var user = {};

// maintain lowercase keys
user["id"] = users[i].id;
user["name"] = users[i].name & " (" & users[i].email & ")";
user["email"] = users[i].email;

// serialize the user record and append it to the result
arrayAppend(result, serializeJSON(user));

}

// convert the result from an array to a list, with each user on its own line
var html = arrayToList(result, chr(10));

// output the JSON result
writeOutput(html);

}

public array function searchUsers(required string search) {

// normally this would query the db,
// but for this demo I'll just create a static array of users
var users = [
{id="1", name="Adam", email="adam@email.com"},
{id="2", name="Bob", email="bob@email.com"},
{id="3", name="Brady", email="brady@email.com"},
{id="4", name="John", email="john@email.com"},
{id="5", name="Kaitlyn", email="kaitlyn@email.com"},
{id="6", name="Leanne", email="leanne@email.com"},
{id="7", name="Lisa", email="lisa@email.com"},
{id="8", name="Mike", email="mike@email.com"},
{id="9", name="Nate", email="nate@email.com"},
{id="10", name="Ryan", email="ryan@email.com"},
{id="11", name="Sean", email="sean@email.com"},
{id="12", name="Tony", email="tony@email.com"},
{id="13", name="Tyler", email="tyler@email.com"}
];

return users;

}

}


On a side note, JSON.parse() is a built-in function only available to newer browsers, like Firefox 3.5 and IE 8.0. Does anybody know of a good plugin to accommodate older browsers? Something similar to Prototype's String.evalJSON would be preferred.

Friday, December 18, 2009

Using onMissingMethod to Treat Properties More Like... Properties

Since I didn't come from a Java background, I'm relatively new to OOP best practices. One thing that I've grown to loathe in my short time working with objects in Hibernate is having to go through getters and setters to access my properties:


<cfset user = new User() />
<cfset user.setFirstName("Tony") />
<cfset user.setLastName("Nelson") />

<cfoutput>
My name is #user.getFirstName()# #user.getLastName()#
</cfoutput>


I'd much rather prefer true implicit getters and setters a la Groovy, ActionScript, C#, etc...


<cfset user = new User() />
<cfset user.firstName = "Tony" />
<cfset user.lastName = "Nelson" />

<cfoutput>
My name is #user.firstName# #user.lastName#
</cfoutput>


After working with jQuery a little more, I really like the convention used on their event helpers, like change() and change(fn). If you pass in an argument, it binds a new observer. If you don't pass in an argument, it triggers the event.

I was able to apply similar logic to my entities in ColdFusion with the help of onMissingMethod() inside a base class that my entities extend.


component {

public void function set(required string property, required any value) {

if (structKeyExists(this,"set#arguments.property#")) {

if (!structKeyExists(arguments,"value") || isNull(arguments.value) || (isSimpleValue(arguments.value) && arguments.value eq "")) {
evaluate("set#arguments.property#(javacast('NULL',''))");
}
else {
evaluate("set#arguments.property#(arguments.value)");
}

}

}

public any function get(required string property) {

if (structKeyExists(this,"get#arguments.property#")) {
local.value = evaluate("get#arguments.property#()");
}

if (!structKeyExists(local,"value")) {
local.value = "";
}

return local.value;

}

public any function onMissingMethod(required string missingMethodName, required struct missingMethodArguments) {

if (structIsEmpty(arguments.missingMethodArguments)) {
return get(arguments.missingMethodName);
}

set(arguments.missingMethodName,arguments.missingMethodArguments[1]);

return this;

}

}



Now if I pass in an argument, it sets the property. If I don't pass an in argument, it gets the property, leaving me with the following code:


<cfset user = new User() />
<cfset user.firstName("Tony") />
<cfset user.lastName("Nelson") />

<cfoutput>
My name is #user.firstName()# #user.lastName()#
</cfoutput>


And in case you didn't read the code, also included in the base class are generic get() and set() methods that relay calls to the actual property getters and setters in order to maintain proper encapsulation.

Wednesday, December 16, 2009

Migrating to jQuery: My First Plugin

A couple posts ago I wrote about how I couldn't justify switching from Prototype to jQuery. Well I take it back (sort of).

After spending more time with jQuery's event handling, I wanted more out of Prototype and I couldn't justify trying to port what I wanted over from jQuery and expect it to work across all browsers. I'd rather let jQuery and its large, active community help me out with testing. So I've begun the process of converting a good portion of my applications over to jQuery.

While updating the code is more tedious than anything, I found myself missing the convenience of Prototype's direct access to DOM elements. For example, I no longer have the following:


if ( $('checkboxA').checked ) {
$('checkboxB').checked = false;
}


Granted with jQuery there are multiple ways to accomplish the same thing but, while they're not too much longer, they're still longer:


if ( $('#checkboxA').attr('checked') ) {
$('#checkboxB').attr('checked',false);
}

if ( $('#checkboxA').is(':checked') ) {
$('#checkboxB').removeAttr('checked');
}

if ( $('#checkbox:checked').length ) {
$('#checkboxB').attr('checked',false);
}


Since I like to write as little code as possible and everybody else seems to really like jQuery's plugin architecture, I figured I'd try write my own plugin. And since it's my first plugin, please don't make too much fun of it.


jQuery.each(('checked,disabled').split(','), function(i, name) {
jQuery.fn[name] = function() {
return arguments ? this.attr(name, arguments[0]) : this.attr(name);
};
});


It works similar to the built-in jQuery event helpers, such as $(selector).change() and $(selector).click(fn). In my plugin, if you pass an an argument, it sets the attribute's value. If you don't pass in an argument, it returns the attribute's value.

Here's how I can write my sample code now:


if ( $('#checkbox').checked() ) {
$('#checkboxB').checked(false);
}


I'll admit it's not much shorter, but it reads a lot better to me. Plus, I don't have to directly call .attr() anymore, which is basically just a generic getter/setter. And if you didn't notice, I also added the same logic for .disabled() for good measure.

I haven't decided if I want to fully embrace a plugin that accomplishes something so trivial, but for some reason I like it.

Monday, December 14, 2009

Handling HTTP Request Parameters in ColdFusion

One thing that always kind of annoyed me in ColdFusion is the separation of HTTP request parameters, meaning parameters for GET requests are put into the url scope and POST parameters are put into the form scope. While this makes sense technically, I find it kind of annoying. For the most part, I don't care where the parameters come from.

Most ColdFusion frameworks alleviate this problem by combining the scopes for you. For example, Model-Glue and Mach-II create an event object that exposes parameters via event.getValue(key) or event.getArg(key). FW/1 combines the variables into request.context, which is then aliased as rc inside your controllers and views. ColdBox does a little bit of both, by having event.getValue(key) inside your controllers and rc inside your views.

While this makes working with request parameters easier, it still feels a little cumbersome having to go through getters and setters or cryptically-named structs (rc? really?). I'd rather have all the parameters combined into a single scope from the get-go.

One of the many things I like about Ruby on Rails and Grails is the params scope, which contains all GET and POST request parameters. Not surprisingly, CFWheels also has the params scope.

However, what happens if you're not using CFWheels, or even a framework at all? I want the same functionality, but without the overhead of adding a framework. With a little inspiration from Ben Nadel, here's my solution:

Application.cfc

component {

function onRequestStart() {

var params = {};
structAppend(params,url);
structAppend(params,form);
getPageContext().getFusionContext().hiddenScope.params = params;

}

}


Now all url and form variables are combined into a single params struct at the beginning of each request, which is then made globally accessible throughout my application, just like a built-in scope. Short and simple, no framework necessary.

On a final note, while this does work, I don't know if all the ColdFusion "experts" would consider it a best practice to add a new "scope" to the language. But in my opinion, it's isolated and helps solve a problem, so why not?

Thursday, December 10, 2009

Firing Native JavaScript Events using Prototype

I've been using Prototype as my JavaScript library of choice for a couple years now. While I'll admit I would probably choose jQuery if I were to rewrite all of my code from scratch, that's simply not an option at this point. I can't justify rewriting perfectly good code for the sake of rewriting it -- if it ain't broke, don't fix it.

For the most part, Prototype has everything I need: Ajax, CSS selectors, DOM manipulation, event handling, and a slew of convenience methods for working with objects and classes. However, I've always been a little bothered by the inability to fire native JavaScript events, meaning there's no way to fire an element's change event. While Prototype allows you to fire custom events using Element.fire, all event names must be namespaced using a colon, which rules out $(id).fire('change').

So today, after some quick Googling, I wrote my own Prototype-based version of jQuery's change() method in order to fire native events. Here's what I came up with:


(function() {
var methods = {};
var events = 'blur,change,click,dblclick,error,'+
'focus,keydown,keypress,keyup,load,'+
'mousedown,mouseenter,mouseleave,'+
'mousemove,mouseout,mouseover,mouseup,'+
'resize,scroll,select,submit,unload';
events.split(',').each(function(event) {
methods[event] = function(element, fn) {
if (fn == undefined) {
if(document.createEvent) {
var evt = document.createEvent('HTMLEvents');
evt.initEvent(event,true,true);
return !element.dispatchEvent(evt);
}
else {
var evt = document.createEventObject();
return element.fireEvent('on'+event,evt)
}
}
else {
element.observe(event,fn);
}
}
});
Element.addMethods(methods);
})();


With this, you can simply call $(id).change() to fire the element's change event or $(id).change(function(){...}) to add an observer to the element's change event.

I did a quick test and it seems to work in both IE8 and Firefox, but I can't guarantee anything about older browsers, so use at your own risk.

On a final note, I don't know why Prototype seems to be overlooked by so many people in favor of jQuery. I actually prefer the Prototype syntax in many cases, but that's probably because I've been working with it longer. Maybe it's because developers who weren't too familiar with JavaScript stumbled upon jQuery one day and decided they didn't have to look at any other libraries out there, which seems really wrong to me. That would be the same as choosing Mach-II without looking at Model-Glue or ColdBox. Or choosing PHP without looking at ColdFusion or Ruby. You really can't know if you've made the right decision unless you've evaluated all your options.

Wednesday, December 9, 2009

7 reasons I switched back to ColdFusion...

First, read this article: 7 reasons I switched back to PHP after 2 years on Rails.

Now, read it again, only this time replace the word "PHP" with "ColdFusion".

Languages are like frameworks: it doesn't really matter which one you use, they all pretty much do the same thing. There is no Holy Grail. In the end, it's all just a bunch of 0's and 1's that can help you get things done.

Monday, December 7, 2009

Check if a Value Exists in a Query

Sometimes you'll need to know if a certain value exists in a query. For example, let's say I have a query of colors.



For demonstration purposes, let's manually build the query:

<cfset colors = queryNew("color") />

<cfloop list="blue,red,green,yellow,blue,black,red,orange" index="color">
<cfset queryAddRow(colors) />
<cfset querySetCell(colors,"color",color) />
</cfloop>

Now let's say I wanted to know if the color "green" exists in my query. There are quite a few ways to do this.

One way to do this would be to create a list of all the colors, then check to see if "green" exists in the list.

<cfset colorList = valueList(colors.color) />

<cfif listFindNoCase(colorList,"green")>
green exists
<cfelse>
green does not exist
</cfif>

However, converting the values to a list might cause problems if one of the values has a comma in it. Granted that's not the case in this scenario, but it's worth mentioning.

Another problem might come up if the column name is dynamic. For example, the following code won't work.

<cfparam name="url.column" default="color" />

<cfset colorList = valueList(colors[url.column]) />

<cfif listFindNoCase(colorList,"green")>
green exists
<cfelse>
green does not exist
</cfif>

I'm not sure how often this situation comes up, but here's a trick to be able to use dynamic column names: use arrayToList() rather than valueList().

<cfparam name="url.column" default="color" />

<cfset colorList = arrayToList(colors[url.column]) />

<cfif listFindNoCase(colorList,"green")>
green exists
<cfelse>
green does not exist
</cfif>

Another way to check if "green" exists in the query would be to use a query of a query.

<cfquery name="getGreen" dbtype="query">
select *
from colors
where color = 'green'
</cfquery>

<cfif getGreen.recordCount gt 0>
green exists
<cfelse>
green does not exist
</cfif>

However, queries of queries can potentially hurt your application's performance if called multiple times. Plus, they're case-sensitive.

In order to increase performance, you could loop over the colors query and insert the values into a struct, then check to see if the key exists in the struct.

<cfset colorStruct = {} />
<cfloop query="colors">
<cfset colorStruct[color] = true />
</cfloop>

<cfif structKeyExists(colorStruct,"green")>
green exists
<cfelse>
green does not exist
</cfif>

While this should help your application's performance, there's still the issue of case-sensitivity. Fortunately, ColdFusion 9 helps solve this problem with the addition of arrayFindNoCase.

Once again, let's treat the query as an array. Normally accessing query["column"] is handled the same as query["column"][1] and would only return the value from the first record in the query, but apparently it references the entire array if used inside a function call.

<cfif arrayFindNoCase(colors["color"],"green")>
green exists
<cfelse>
green does not exist
</cfif>

Not only is this approach case-insensitive, but it's also the least amount of code. Win-win.

Tuesday, December 1, 2009

ColdFusion Reference Wikis

I recently stumbled upon a couple ColdFusion wiki sites that I thought were worth mentioning.

Both sites were created by Kevan Stannard, a ColdFusion developer from Australia. While the sites appear to be a work in progress, there's still quite a bit of useful, thorough, and user-friendly content to read, as well as links to other resources on the web. His personal development blog has some good posts that are worth checking out too.

I don't know Kevan and honestly I hadn't heard of him until running across his sites, which I find a little surprising considering the quality of the content. It was pretty refreshing to see some common programming techniques applied in ColdFusion and explained with a human touch. I wish I had found these sites when I was first starting out.

Anyways, I just thought he should get a quick shout out for his efforts.