Event-Driven Programming: an MVC Example
I recently wrote a blog post on EDP (Event-Driven Programming).
Ray Camden asked me to come up with an example of using EDP with a Model-View-Controller architecture. Over the weekend, I did just that. As a bonus, I'm including a jQuery plugin that will allow you to make Ajax calls to the server without using ColdFusion's CFAJAXPROXY tag.
Let's start by looking at the finished product, available here: http://dev.citymind.com:8500/test/blog_jquery/day16/sample_page.cfm
Let's step through the code. Our initial page is sample_page.cfm:
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<script type="text/javascript" src="proxy.js"></script>
</head>
<body>
<div><a href="Mathomatic/add/addend1/10/addend2/20/context/result">Add 10 + 20</a></div>
<div><a href="Mathomatic/multiply/term1/10/term2/20/context/result">Multiply 10 + 20</a></div>
<div><a href="Quotomatic/quote/context/result">Get a random quote</a></div>
<div id="result"></div>
<cfinclude template="sample_page.pgm">
</body>
</html>
In the HEAD section, I've included a call to the base jQuery library and a call to "proxy.js" -- we'll get to that shortly.
Next, I have three A HREFs that point to non-existent resources. I'm going to "hijack" those links, using the HREF values and parsing them to see what event should be announced. Next, I have a DIV with an ID of "result" that will hold the results of our Ajax calls to the server (and our mini-MVC framework).
Now, about that HREF hijacking: the code for this is in a separate "sample_page.pgm" file:
<script type="text/javascript">
$('a').click(function(){
var link = $(this).attr('href').split('/');
var controller = link[0];
var methodName = link[1];
$.proxy(controller, methodName, jsonifyArray(link));
return false;
});
String.prototype.left = function(n){
if (n < 1 ){
return "";
} else if (n > String(this).length){
return this;
} else {
return String(this).substring(0, n);
}
};
Çspan style='color: #808080'ÈÇemÈ //PROXY
Ç/emÈÇ/spanÈ function jsonifyArray(linkArray){
var json = '{';
var argsPresent = false;
for (var i=2; i<linkArray.length; i++){
json += '"' + linkArray[i++] + '" : "';
json += linkArray[i] + '", ';
argsPresent = true;
};
if (argsPresent) {
json = json.left( json.length - 2) + '}'
} else {
json = 'none';
}
return json;
};
jQuery.proxy = function(controller, methodName, args){
jQuery.post(
'Proxy.cfc?method=proxy',
{"controller" : controller, "methodName" : methodName, "args" : args},
function(event){
$('##' + event.context).html(event.display);
},
'json'
);
};
</script>
</cfoutput>
The first part of the code is this:
var link = $(this).attr('href').split('/');
var controller = link[0];
var methodName = link[1];
$.proxy(controller, methodName, jsonifyArray(link));
return false;
});
This is a jQuery instruction, telling jQuery to find all A tags and assign an event handler to them. That event handler will parse the HREF attribute's value into a "controller" and a "method".
With that done, I need to communicate from my JavaScript to the ColdFusion code sitting on the server. This is done with the "$.proxy" call. This is a plugin to jQuery that will post information and receive a callback when processing on the server is done. It looks like this:
jQuery.post(
'Proxy.cfc?method=proxy',
{"controller" : controller, "methodName" : methodName, "args" : args},
function(event){
$('##' + event.context).html(event.display);
},
'json'
);
};
We'll look into what this file does shortly.
jQuery programmers are used to seeing jQuery plugins in separate files. This is, in general, an excellent idea; I've placed it on the "sample_page.pgm" simply to cut down on the number of files. (Detractors might call this lazy, but they're detractors after all: I have to give them something to detract.)
This plugin code creates a function called proxy that accepts three arguments: "controller", "method",and "args". The "controller" variable is the name of the controller that should have its "method" invoked, and the "args" are passed with that method invocation.
Back to the first bit of code and this line:
We can see where the controller came from: it's the first argument in an array called "link" that was created with this line of code:
As an example, the first link's HREF attribute is this: "Mathomatic/add/addend1/10/addend2/20/context/result". After the parsing is done, we'll have an array with this in it:
| Mathomatic |
| add |
| addend1 |
| 10 |
| addend2 |
| 20 |
| context |
| result |
Since I'm always going to create a link with the first element as the controller's name and the second element as the method name for that controller, I can extract these simply:
var methodName = link[1];
But the arguments will vary, depending on what event needs to be announced, so I need a more dynamic way of evaluating the name/value pairs. And, because I also need them turned into a JSON string, I've created a helper method, "jsonifyArray", that I can give the array to and receive back a nicely formatted JSON string.
Now, let's get back to that jQuery plugin code for the proxy function:
jQuery.post(
'Proxy.cfc?method=proxy',
{"controller" : controller, "methodName" : methodName, "args" : args},
function(event){
$('##' + event.context).html(event.display);
},
'json'
);
};
Having received the controller, method, and a JSON string representing any arguments needed by the controller's method, we're ready to talk with ColdFusion on the server.
When CFAJAXPROXY came out, a number of developers (including me) were excited about the prospects for communication between JavaScript on the client and ColdFusion on the server. It's a great tag -- so great I decided to mimic its functionality with jQuery so that I could use it on Railo and possibly pre-CF8 servers.
Using "jQuery.post", I can make a call to a file on the server.
The first argument to "jQuery.post" is the file to be called. (I've chosen to use a CFC because I found the code for CFCs to be very clear, but you can certainly call a .CFM file as well.)
The second argument is any data to be passed. I've turned this into a JSON string for easy transmission across the HTTP protocol.
The third argument is a callback function. When the server responds to the POST, anything it returns will be sent as an argument (here I've called the response "event") to the anonymous function (so-called because there's no named declaration of a function).
The last argument -- "json" in this case -- tells jQuery what format the returned information will be in.
Within the callback function's "event" (sent back from the server), is a property called "context". This points to where "event.display"'s information should be placed. You'll see that in the HREF properties of the A tags (on sample_page.cfm), a context of "result" (that's the DIV already on the page) is provided. Within "results", I'll place the "display" information sent back from the server.
Whew! That's a lot of information -- and we haven't even gotten to the ColdFusion server code. Let's stop for today to give you a chance to look over this code and ask any questions about event-driven programming in general. Tomorrow, we tackle the server!


A bit OT, but a question about your jQuery plugin. Am I seeing right - did you write a plugin to support a site specific feature? I had not really felt the need to study writing plugins in jQuery yet because I kept thinking that I needed to find a good use case, and the only good use case would be something that could be applied to multiple pages (something generic for example). Yet in this case, it looks like you used it to make handling the request simpler, and to still call stuff in the 'jquery way'.
Am I reading your code right?
Although there's a single point of entry, this isn't really an implementation of the Front Controller pattern that frameworks like Model Glue, Mach-II, etc. use. And since the controller is encoded into the URL, one could easily arrange to call the controller directly (provided it had at least one "remote" method.
Hope that helps.
Nice tutorials. One problem though, all the examples give an error"
File not found: /test/blog_jquery/day16/sample_page.cfm