Event-Driven Programming: an MVC Example, Part II
Yesterday, I showed an example of an EDP implementation on the client. Let's do a quick review of the main points and then look at the server code.
1. Quasi-RESTful URLs indicate the controller, event name, and arguments for a particular action.
2. The URL is hijacked, parsed, and turned into an event object that is sent via Ajax to the server.
3. The server's response is parsed and any display is rendered.
In a comment yesterday, Ray asked if I was writing site-specific code as a jQuery plugin. It does look like this (since the included file with the plugin was named "sample_page.pgm"). I explained that the plugin was taken out of a full-scale EDP library that we use. It's too much to get into now, but you can think of abstracting that plugin code to a function, "proxy", that is universally accessible (along with others like "announce" and "listen").
Today, I want to show you how the code on the server works. As promised, this is an MVC implementation. Here's the file structure for this tiny "app":

Let me give a general disclaimer so that I don't inadvertently mislead people: this code was written as a stand-alone example. In a real application, the file structure is significantly expanded to encompass directories for individual modules, shared code components, images, JavaScript, etc. In other words, what you're seeing is not very indicative of what a real application would look like, but is rather a redacted version. The same is true for the code presented.
When the client makes a request to the server, it does so through jQuery's "post" function. The submission is done through the XHTTP request object (Ajax). All requests are sent to a "Proxy.cfc" file, where the request will be routed to the appropriate controller. First, though, a look at the essential Application.cfc file:
<cfset this.name = "EDPDemonstration">
<cffunction name="onApplicationStart" access="public">
<cfset application.Mathomatic = CreateObject('component', 'test.blog_jquery.day16.controllers.Mathomatic').init()>
<cfset application.Quotomatic = CreateObject('component', 'test.blog_jquery.day16.controllers.Quotomatic').init(CreateObject('component', 'test.blog_jquery.day16.model.QuoteService').init())>
</cffunction>
<cffunction name="onRequestStart" access="public">
<cfif StructKeyExists(url, 'reinit')>
<cfset onApplicationStart()>
</cfif>
</cffunction>
</cfcomponent>
You can see that when the application starts, I instantiate the needed controllers. Since controllers are almost always stateless, it makes sense to put them into the application scope to avoid unnecessary and costly per-request instantiation.
The Proxy.cfc code is quite simple:
<cffunction name="proxy" access="remote" output="false" returnformat="json">
<cfargument name="controller" required="true">
<cfargument name="methodName" required="true">
<cfargument name="args" required="true">
<cfset var response = "">
<cfset var myArgs = "">
<cfif IsJson( arguments.args )>
<cfset myArgs = DeserializeJson(arguments.args)>
</cfif>
<cfinvoke component="#application[arguments.controller]#" returnvariable="response" method="#arguments.methodName#" argumentcollection="#myArgs#" />
<cfset response['context'] = myArgs.context>
<cfreturn response>
</cffunction>
</cfcomponent>
The "proxy" method of this component is set as "remote", allowing it to be called through the jQuery "post" method. I prefer this proxying method to making direct calls to individual controllers as it (1) limits the number of remotely-accessible server methods and (2) avoids duplicate code across different controllers.
What does the "proxy" method do? It accepts three arguments:
- the name of the controller. This will be used to route the request to.
- the name of the method within the controller to be called.
- a JSON representation of all other arguments needed by the controller to do its job and return a response.
It then creates an empty structure, "response" and deserializes the JSON "args" argument into a ColdFusion structure called "myArgs".
Next, the specified controller is CFINVOKEd, passing myArgs.
When the controller returns its response, a new property, "context", is added, its value extracted from the args variable passed in.
Finally, the response struct is returned. One very nice feature of ColdFusion 8 is the ability to specify different RETURNFORMATs. Simply by specifying that I want the response returned as JSON, ColdFusion takes care of converting the ColdFusion structure, "response", into a properly-formatted JSON string.
From there, as we saw yesterday, the jQuery plugin code reads the CONTEXT property of the struct, using that as a container for the response's DISPLAY contents.
What about the controllers? Their job is to process the request and return a response structure with, at minimum, EVENT and DISPLAY properties.
Here's the code for Mathomatic.cfc:
<cffunction name="init" access="public" output="false">
<cfreturn this>
</cffunction>
<cffunction name="add" access="public">
<cfargument name="addend1" required="true">
<cfargument name="addend2" required="true">
<cfset var str = StructNew()>
<cfset str['event'] = "added">
<cfset str['display'] = "The sum is #Val(addend1 + addend2)#">
<cfreturn str>
</cffunction>
<cffunction name="multiply" access="public">
<cfargument name="term1" required="true">
<cfargument name="term2" required="true">
<cfset var str = StructNew()>
<cfset str['event'] = "multiplied">
<cfset str['display'] = "The product is #Val(term1 * term2)#">
<cfreturn str>
</cffunction>
</cfcomponent>
And for Quotomatic.cfc:
<cfset variables.instance.quoteService = "">
<cffunction name="init" access="public" output="false">
<cfargument name="quoteService" required="true">
<cfset variables.instance.quoteService = arguments.quoteService>
<cfreturn this>
</cffunction>
<cffunction name="quote" access="public">
<cfset var str = StructNew()>
<cfset var display = "">
<cfset var randomQuote = variables.instance.quoteService.quote()>
<cfsavecontent variable="display">
<cfinclude template="../views/quote.cfm">
</cfsavecontent>
<cfset str['event'] = "quoted">
<cfset str['display'] = display>
<cfreturn str>
</cffunction>
</cfcomponent>
These are both quite simple. Note that Quotomatic holds an instance of a QuoteService, a model component. Note, too, that the Quotomatic controller calls "quote.cfm", a view file that provides some minimal formatting for the raw quote data returned by the QuoteService.
With all the code on the screen, you might have missed the fact that I've had each controller return an EVENT property in its response -- and that nowhere am I using it.
Tomorrow, I'll explain the very important reason this EVENT property exists, how it will be used, and what I see as the game-changing aspects of event-driven programming.


There are no comments for this entry.
[Add Comment]