A Look at Event Handlers - ColdBox Series Part 3
In my last post, we set up a base ColdBox application using the Application Template. So now we have a basic application that welcomes us to ColdBox. Not very useful.
So let's look at our Event Handlers and see if we can get them to do something more interesting.
What is an Event?
Since ColdBox is an Event-Driven framework, and since we are going to be "handling" events with our event handlers (clever name), we probably need to understand what an event is.As I understand it, an event is an Object that is dispatched by the controller (ColdBox) and passed to the event handler when a request for the event is made. In ColdBox, the event object itself is of type coldbox.system.beans.requestContext and comes with a lot of built it methods and properties, plus it holds the request collection which:
...is a central repository of information that is refreshed on every user request with the request's information. This is how data gets moved around from event handlers to views and layouts to plugins and anything running inside of the framework and in the MVC layers.- From the ColdBox Website
So an end user makes a request for an event like this:
http://todolists:81/index.cfm?event=lists.getAllLists
The controller (ColdBox), then receives this request and create a event object (requestContext) and dispatches it to the event handler. The event handler in this case is the method getAllLists() inside of lists.cfc which is inside of the /handlers folder. In other words, it calls the getAllLists() method and passes in the event object: getAllLists(event)
The event handler, then receives the event object and can add or remove data using the built in event methods, like getCollection(), getValue() and setValue(). The event handler can then dispatch another event, or call a view for display (passing along the event object for both).
One important thing to note, any POST or GET requests sent to ColdBox are added to the Request Collection inside of the event object. So in our event handlers, we will never be accessing the FORM or URL variables directly. Their values are automatically placed in the Event Object's Request Collection.
For more on events, event handling and the RequestContext object, check here:
Event Handlers
So now to look at the event handlers in our Application Template. By default, two event handler CFCs are in our /handlers directory. general.cfc and main.cfc. Main.cfc contains event handling methods for the implicit events that occur during the applications lifecycle. These are onApplicationStart(), onRequestStart(), etc. I personally, have only needed to change these to add Transfer to an application I was working on. Perhaps some of our expert ColdBox users would leave some comments about why else we might change anything in here, but for right now, since we are not using Transfer, I am just going to leave this file alone.Which brings us to general.cfc. General.cfc is a regular old ColdBox event handler, and we do not "have" to use it. Personally, I don't like the name general, but that is a matter of preference. To keep things simple, we will leave the name the way it is.
Here is the default code for general.cfc
<cfcomponent name="general" extends="coldbox.system.eventhandler" output="false">
<!--- This init is mandatory, including the super.init(). --->
<cffunction name="init" access="public" returntype="general" output="false">
<cfargument name="controller" type="any">
<cfset super.init(arguments.controller)>
<!--- Any constructor code here --->
<cfreturn this>
</cffunction>
<cffunction name="index" access="public" returntype="void" output="false">
<cfargument name="Event" type="any">
<!--- Do Your Logic Here to prepare a view --->
<cfset Event.setValue("welcomeMessage","Welcome to ColdBox!")>
<!--- Set the View To Display, after Logic --->
<cfset Event.setView("home")>
</cffunction>
<cffunction name="doSomething" access="public" returntype="void" output="false">
<cfargument name="Event" type="any">
<!--- Do Your Logic Here, call to models, etc.--->
<!--- Set the next event to run, after Logic, this relocates the browser--->
<cfset setNextEvent("general.index")>
</cffunction>
</cfcomponent>
So here we have two event handler methods, index() and doSomething(). All doSomething() does is call index() using the setNextEvent() method, so let's delete it right now.
Now, let's look at the index() handler. Since the index() method is inside of the general.cfc file, we would request the event like this:
http://todolists:81/index.cfm?event=general.index
So when the request is made, ColdBox passes the event object (request context) to the event handler method, which accepts it as an argument.
Then it adds a value called "welcomeMessage" to the event object with this line:
<cfset Event.setValue("welcomeMessage","Welcome to ColdBox!")>
Then it calls for a view (which we will look at in just a moment) named "home":
<cfset Event.setView("home")>
This will show the view to the end user. We can look at the view by looking in the /views directory that was part of the Application Template. It is at /views/home.cfm
Here is what the home.cfm looks like:
<cfoutput>
<h1>#Event.getValue("welcomeMessage")#</h1>
<h5>You are running #getSetting("codename",1)# #getSetting("version",1)# (#getsetting("suffix",1)#)</h5>
</cfoutput>
In the <h1> tag we get the welcome message that we created in the event handler by calling event.getValue("welcomeMessage"). Then the view calls the getSetting() method to get some settings out of the coldbox.xml file, which we will look at in my next post.
Pretty simple, huh?
Views
Wait, wait, wait, wait, wait. THAT's our view? An <h1> and an <h5>? Where's the <html>? Where's the <head>? Where's the <body>? That's not a very well-formed document, is it?Well, let's go to the page in our browser and see what is actually getting rendered. There is a lot of debugging crap in there, but here is the important part:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
<title>Welcome to Coldbox!!</title>
<script language="javascript" src="/index.cfm?sbContent=js" type="text/javascript"></script><link rel="stylesheet" href="/index.cfm?sbContent=css" type="text/css" media="screen"></head>
<body>
<h1>Welcome to ColdBox!</h1>
<h5>You are running ColdBox 2.6.1 (FAITH)</h5>
</body>
</html>
Looks like valid XHTML to me. But where did all that other code come from? It came from our default layout. Which you will find in the /layouts directory. It is called Layout.Main.cfm. We will discuss layouts more in another post, I just wanted you to understand where that code is coming from.
Modifying our Event Handler
So let's modify our index() event handler method and our home.cfm view to show us something other than a simple welcome message.Our index() handler method will change to:
<cffunction name="index" access="public" returntype="void" output="false">
<cfargument name="Event" type="any">
<!--- Do Your Logic Here to prepare a view --->
<cfset Event.setValue("pageTitle","My To-Do List")>
<cfset Event.setValue("today", Now())>
<!--- Set the View To Display, after Logic --->
<cfset Event.setView("home")>
</cffunction>
And our view will change to:
<cfoutput>
<h1>#Event.getValue("pageTitle")#</h1>
<h5>Today is #DateFormat(Event.getValue("today"), 'mmmm d, yyyy')#</h5>
</cfoutput>
I have deleted the "welcomeMessage" value and created a "pageTitle" value instead, I have also added a "today" value and inserted a value using the Now() function.
In our view I have replaced the getValue() call for the "welcomeMessage" value and asked for the "pageTitle" value instead. And I have replace all of those getSetting() calls with a getValue() call for the "today" value and formatted it with the DateFormat() function.






@Doug & @Chris - I have no intention of stopping. I enjoy this too much and I think I get just as much out of it as you do. I love teaching and will take any chance I can get to do more of it.
What do you think?
Thanks,
Christine
So your question about service layers is a good one. I asked the same question myself, and in one of my first apps, even though I did use a service layer, it was really a dumb one because I was doing most of my processing at the controller anyway.
So technically, yes, it is possible to have the controller be the service layer. The next question is, "should you?".
I would say no. I still feel the service layer is a very important piece in the OO puzzle for separating the model from the controller. By having your controller talk directly to your model you are tightly coupling those to pieces which breaks some of the modular aspects that OO offers.
These are just my opinions, and I am sure that there are some advocates out there that will say "if Ya Ain't Gonna Need It..." (YAGNI). But I personally like the service layer and I would get rid of the beans and DAO's (replaced with an ORM) before I would get rid of the service layer.
That said, whether or not you add service objects, ColdBox can still be used as a service layer for remote applications or AJAX apps by using the ColdBox Proxy. It's very cool and worth looking into. But I would still have service objects in my app.
The ability to use ORM would be based on the assumption of a solid Relational Data Structure. At this point, given the data structure I am working with, I think I'd rather be tightly coupled with my controller than my database... which is why I'm adding the "extra" layer for the data model to the business model. In the interest of balancing real world needs and optimal OO design, I think YAGNI is more likely to apply to worries over the coupling between my controller and model than between my model and data structure...
The hardest part about figuring out the design patterns you should use for a specific real world scenario is that the ideal that we seem to spend so much time discussing often does not exist in reality... I love the black and white discussions of where and how good design principals should be applied as much as the next programmer, but my experience to this point leads me to believe that implementation is usually based in varying shades of grey.
Thanks again and I look forward to reading more from you!
Christine
"but it seems that if I'm using Lightwire and/or Coldspring for my DI and use the framework's integration for those DI handlers it seems that I'd have to have my handlers act as the service layer."
The way I have my handlers work is to have them receive the event object, and then have the handler instantiate the service object and pass data to it from the Event object.
In my case, the controller is really "dumb". All it is doing is receiving data, passing it to the service layer from processing and persistence, and then getting data back for display in a view. I do not have any business logic in the controller, it is all either in the service layer or in the model objects.
Hopefully, I will be demonstrating what I am doing in the coming weeks as I have time to blog about it.
Also, take a look at the ColdBoxReader sample application that is included with the ColdBox download.
Again, I am not saying that I am doing it the "right" way. Just trying to explain how I have been doing it.
Thanks for your comments, you're really helping me think this through!
Christine
Just because the handler is taking care of DI in our application, does not mean that nothing else can handle DI, even in the same model. Since we have separated our service layer from our controller, our service object is completely oblivious about how it is being called, or how it gets the objects it needs. We set up our service layer so that it get instantiated and receives the objects it needs.
So the service object does not care how it receives the objects it depends on, so long as it receives them. Whether it comes from ColdBox, Mach-II, Mate for Flex, or a ColdSpring remote proxy.
So just because ColdBox happens to be handling DI at one moment that the service object is called, does not mean that something else could not handle it at another moment.
At least, that is my understanding.
Thanks again,
Christine
I don't think you would be doing anything wrong by using your handlers as the service layer. But what I do think you would want to reconsider is not putting an additional service layer between your controller and your model. While technically your application would work just fine if your controller was talking directly with your model, it just doesn't feel right to me.
You mentioned that you were planning to use the ColdBox Proxy. Does that mean you have a Flex front end? Or is it heavy on the Ajax?
I just wanted to point out for others that there is mention to home.cfc, which should probably be home.cfm - right?
Yes, you are right, that should be home.cfm, not home.cfc. I have corrected the post. Thanks
This tutorial is awesome. I have been meaning to learn a ColdBox for a while now and this tutorial is really helping me out. Thanks!!!
I was recently called back to one of my clients who is upgrading to CF9 and wanted to implement a framework. I suggested ColdBox because a few friends, notably Peter Bell and Rich DesCombaz, suggested it is perhaps the best choice. I have no experience with it, so this series is proving a god send.
I am building a proof of concept right now using CB 3.0M5. I want to let other readers know that some things have changed since your original post, chiefly the best practice of returning the collection to a variable and using that. Below is how my index looks:
<cffunction name="index" returntype="void" output="false" hint="My main event">
<cfargument name="event" required="true">
<cfset var rc = event.getCollection()>
<cfset rc.pageTitle = "My To-Do List">
<cfset rc.today = Now()>
<cfset event.setView("home")>
</cffunction>
One more thing I would say is that if anyone is in a position of making a technology choice, Grails should be strongly considered. It, too, is a great framework and has features I just don't see in ColdBox. If I had been talking with my client a month earlier, I would have strongly pushed them in that direction.
Thanks for the kind words and the tip.
Actually, using the RC struct was an option in 2.6.x as well, I just never did it while I was writing these posts.
Yeah, Grails is a pretty awesome framework and is on my list of things to spend more time with.