ColdBox's Event Handlers Guide

Introduction

This is a guide to event handlers for the ColdBox Coldfusion Framework. It will give you a quick overview of event handler syntax, regulations, locations, method invocations, and declarations. It will also show you some event handler code samples.

What are Event Handlers?

ColdBox Event handlers are cfc's (Coldfusion Components) that are written to implement ColdBox events. These events carry the task of controlling your application flow, calling business logic, preparing a display to a user and much more. Every method that has an access of public/remote is automatically exposed as a runnable event in the ColdBox framework and ColdBox will auto-register them for you. These events can then be defined for some explicit or implicit invocations via the coldbox.xml and from within the components themselves. Another important aspect of event-driven architectures is that the event action can be passed via the URL or the FORM. The framework captures this action variable and uses it to execute the correct event handler method. From within the event handler's method, you will program the flow of the application, redirect data to the correct business logical units (model calls), logging, error trapping, validation, etc. If you have a Fusebox background you can relate to a handler cfc to being a circuit file and the methods in the handler to be like the fuseactions in the circuit file. However, with one MAJOR difference, you are coding in coldfusion and not in XML. There is no need for parsing or an XML dialect. Another major advantage is the benefit of using Coldbox Plugins from withing your handlers, which provide you with several programming aspects that are already pre-built for you.

If you are not familiar with coldfusion components, you will have to get to speed in their usage in order to use the ColdBox framework. Below are some resources on Coldfusion Components:

Important Note: Event Handler's are not to be used to write business logic. They are used as controllers of your application, they make calls and redirect data. THAT IS IT!!! You can test code on them, but you cannot leave it there!!! ADHERE TO BEST PRACTICES!! If not this would not be an MVC framework.

How are events called?

As discussed in the introduction, events are determined via a special variable that can be sent in via the FORM or URL. The default name for this variable is event. Of course, you can change this by updating the EventName setting in your configuration file. If no event name is detected as an incoming variable, the framework will look in the configuration settings for the DefaultEvent setting and use that instead. Ok, so now that we know how we can determine what event to execute, how do we write the events since they are used by convention?

All event handlers must be placed in the conventions directory of handlers in the application root. (See Directory Structure). At application startup, the framework registers all the valid event handler cfc's. So in order to call them you will use the following dot notation format:

{handler}.{method} = ColdBox Event
{package}.{handler}.{method} = ColdBox Event

This looks very similar to a java/cfc method call, example: String.getLength(), but without the parenthesis. Once the event variable is set and detected by the framework, the framework will tokenize the event string to retrieve the cfc and method call and validate it against the internal registry of registered events. It then continues to instantiate the event handler cfc or retrieve it from cache, and the finally executes the event handler's method.

Examples:

//No Packages
event=main.index
//With Package
event=blog.main.entry

So just remember that when declaring event variables, they have to follow the dot notation.

Event Handlers Location

All event handlers should be placed in the handlers directory of your application. This is the location where ColdBox will look for event handlers, parse them and register them. However, you can also change this convention via the Dashboard or settings.xml file. All of the framework's conventions can be customized to your liking. Please see Naming Conventions Guide

|ApplicationRoot
|----+ handlers (Event  Handlers Directory

Event Handlers External Location

With ColdBox 2.5.0 you can now declare a HandlersExternalLocation setting in your configuration file. This will be a dot notation path or instantiation path where more external event handlers can be found (You can use coldfusion mappings). Once declared, the framework will search that location for event handler cfc's and register them as external events. How will I call them then? The same way as normal events, using the syntax shown above. There is no distinction on your part, only for the framework. How cool is that?

Note: If an external event handler has the same name as an internal conventions event, the internal conventions evnet will take precedence.

Rules and Anatomy of an Event Handler

  • First of all, these cfc's must extend the coldbox eventhandler base cfc: coldbox.system.eventhandler
  • They must have an init method with the following standard IF and only IF you will be putting any initialization code for the entire event handler. If not, the init method is not mandatory.
  • They must exist in the correct handlers directory under your application. See Directory Structure or wherever you have pointed your custom conventions.
  • They must NOT contain any business logic, that is why the model or business layer is for, unless you don't want to.
  • They must determine what view will be rendered or what event to redirect execution to via a redirect or just implicitly giving execution to another event.
  • If the handlers are called via the ColdBox proxy from Flex/Air/Remote applications, your event handlers can actually return data by having a returntype and returning a value.
  • They must have the public/remote exposed methods that will respond as ColdBox events.
Note: If a view is not set by an event handler, the rendering engine will throw an exception, unless a '''defaultView''' has been set in the coldbox.xml or the event.noRender() method has been used or the handlers is being called from the ColdBox Proxy.

What if I don't want to render anything?

Well, if you don't want to, then you don't have to. All you need to do is use the event object to call on the noRender() method.

<cfset event.noRender()>

This method tells the framework that this request will not produce any output, so just finalize the request. Most likely you will end up with a white page or if called from ajax, nothing.

Sample Handler Component Declaration

Below is a sample handler component declaration which can exhibit some caching parameters discussed below:

<cfcomponent name="ehSecurity" hint="This is my security handler" extends="coldbox.system.eventhandler" cache="true" cachetimeout="20" cacheLastAccessTimeout="5">

  <!--- All my methods here --->

</cfcomponent>

The Caching Parameters

Since ColdBox is built with a solid cache foundation, your handlers can also be cached if needed. You will do this by adding meta data attributes to the cfcomponent tag. By default handlers WILL BE cached, unless you specifically use the cache meta data attributes to tell the framework NOT to cache it. Caching of handlers simulates persistence, so remember this if you are planning handlers that can maintain their own persistence. This is a true flexible and awesome feature. Persistence controlled by the framework for you.

ATTRIBUTE TYPE DESCRIPTION
cache boolean A true or false will let the framework know whether to cache this handler object or not.
cachetimeout numeric The timeout of the object in minutes. This is an optional attribute and if it is not used, the framework defaults to the default object timeout in the cache settings. You can place a 0 in order to tell the framework to cache the handler for the entire application timeout controlled by coldfusion.
cacheLastAccesstimeout numeric The last access timeout of the object in minutes. This is an optional attribute and if it is not used, the framework defaults to the default last access object timeout in the cache settings. This tells the framework that if the object has not been accessed in X amount of minutes, then purge it.

Reserved Words and Methods

Since you will be inheriting functionality from the base event handler, you will have certain restrictions on creating properties and methods

Reserved Words

controller The ColdBox Controller
instance Internal private scope
instance.__hash An internal hash id for the event handler.

Reserved Facade/Concrete Methods

abort cfabort facade
announceInterception A way to announce custom interceptions
dump cfdump facade
getColdBoxOCM Get a reference to the ColdBox Object Cache Manager
getcontroller Get a reference to the ColdBox controller
getDatasource Get a named datasource bean
getDebugMode Get the current debug mode for the user
getfwlocale If using i18N, then retrieves the current locale
getHash Get the hash
getMailSettings Get a mail settings bean
getMyPlugin Get a custom plugin
getPlugin Get a core or custom plugin
getResource If using i18N, then retrieves a resource from a resource bundle
getSetting Get a setting
getSettingsBean Geta a config settings bean
getSettingStructure Get the ColdBox or Application Settings Structure
include cfinclude facade
includeUDF A mixin injection method, so you can inject your handler with your own methods at runtime.
purgeView The ability to purge a named view from the cache
relocate Facade to cflocation
renderExternalView Render an external view
renderView Render a view
runEvent Execute a ColdBox event
setDebugMode Set the debug mode for the user
setNextEvent Relocate to another event
setNextRoute Relocate to another route
setSetting Set a setting
settingExists Check if a setting exists
throw cfthrow facade

Sample Init Method

<cffunction name="init" access="public" returntype="ehGeneral" output="false">
  <cfargument name="controller" type="any" required="yes">
  <cfscript>
  //Calling the the super init method is MANDATORY
  super.init(arguments.controller);
  
  //Any Code you like below
  return this;
  </cfscript>
</cffunction>

OR
<cffunction name="init" access="public" returntype="ehGeneral" output="false">
  <cfargument name="controller" type="any" required="yes">
  <cfset super.init(arguments.controller)>
  <cfreturn this>
</cffunction>

Anatomy of an Event Handler Method

As we discussed before a handler cfc contains several public/remote methods that can be executed by the framework. You can name these methods in any way you like since version 2.0.0. With these methods you can call business logic, validate input, call plugins, etc. Well, so how do I do this? Each method will receive the request context object named Event as a parameter, which contains the request collection that we went over in the ColdBox overview guide. This object contains the incoming request's data such as FORM/URL variables, the event requested, the view to render, the layout to render and any variables that your methods place in it. You can look at the CFC API to see all the methods that you have available in this object.

NOTE: The name of the argument received is Event of type coldbox.system.beans.requestContext

<cffunction name="{Method Name}" access="public" returntype="void" output="false">
  <cfargument name="Event" type="coldbox.system.beans.requestContext">
</cffunction>

Public methods in the event handler that will be executed by the framework can be of three types ( This is my convention, you can create your own just by not writing what I do :) ):

  • on{name} : I Usually use this syntax for facades to the framework's onRequestStart, onApplicationStart, onException, etc or for custom raised events like: onBookingDone, onOverride, etc.
  • dsp{name} : This syntax is usually used to prepare a view for display. Let's say that your are preparing a view that needs two queries in order to be displayed. You will place those query model calls here and then set the view to be rendered. Example: dspLogin, dspUserListings.
  • do{name} : This syntax is most likely your model/business logic invocation calls, depending on your application. The main purpose is for them to carry out a certain processing operation. Example: doLogin, doCreateAccount. You can then decide to set a view to render or relocate to another event by using the setNextEvent(event) method.

Setting up an event for caching

The way to set up an event for caching is on its cffunction declaration with the following extra arguments:

ATTRIBUTE TYPE DESCRIPTION
cache boolean A true or false will let the framework know whether to cache this event or not. The default is FALSE.
cachetimeout numeric The timeout of the event's output in minutes. This is an optional attribute and if it is not used, the framework defaults to the default object timeout in the cache settings. You can place a 0 in order to tell the framework to cache the event's output for the entire application timeout controlled by coldfusion, NOT GOOD. Always set a decent timeout for content.
cacheLastAccesstimeout numeric The last access timeout of the event's output in minutes. This is an optional attribute and if it is not used, the framework defaults to the default last access object timeout in the cache settings. This tells the framework that if the object has not been accessed in X amount of minutes, then purge it.

Now, for more information on how to use this and how to purge events, please read the Caching Guide.

Note DO NOT cache events as unlimited timeouts. Also, all events can have an unlimited amount of permutations, so make sure they expire and you purge them constantly. Every event + URL/FORM variable combination will produce a new cacheable entry.

Method Samples

Can you show me an example of a method in real life? Sure, here are some examples from the ColdBoxReader application:

<cffunction name="onException" access="public" returntype="void" output="false">
        <cfargument name="Event" type="coldbox.system.beans.requestContext">
        <!--- My own Exception Handler --->
        <!--- Log error --->
        <cfset var exceptionBean = Event.getValue("ExceptionBean")>
        <!--- Do per Type Validations, example here --->
        <cfif exceptionBean.getType eq "Framework.SettingNotFoundException">
                <cfset getPlugin("messagebox").setMessage("warning", "Settings not found by my funky code.")>
                <!--- Relocate to default event, by not passing an event to the setnextevent method --->
                <cfset setNextEvent()>
        <cfelse>
                <!--- Else, just log the errors and finalize execution, the framework then presents an error page--->
                <cfset getPlugin("logger").logErrorWithBean(exceptionBean)>
        </cfif>
</cffunction>


<cffunction name="doCreateAccount" access="public" returntype="void" output="false">
        <cfargument name="Event" type="coldbox.system.beans.requestContext">
        <cfscript>
        var password2 = Event.getValue("password2","");
        var userService = getPlugin("ioc").getBean("userService");
        var userBean = userService.createUserBean();
        //Get a reference to the request collection for shorter typing, I am lazy.
        var rc = Event.getCollection();
        //Populate Bean From Request Collection via the beanfactory plugin
        getPlugin("beanFactory").populateBean(userBean);
                        
        if ( userBean.getUserName() eq "" or userBean.getPassword() eq "" or userBean.getemail() eq ""){
                getPlugin("messagebox").setMessage("warning", "Please enter all the account information in order to create an account.");
                setNextEvent("ehUser.dspSignUp");
        }
        if ( compare(UserBean.getpassword(),password2) neq 0 ){
                getPlugin("messagebox").setMessage("warning", "The passwords do not match.");
                setNextEvent("ehUser.dspSignup");
        }
        try {
                userService.saveUser(userBean);
                userBean.setVerified(true);
                //set session object
                getPlugin("sessionstorage").set("oUserBean",userBean);
        
                //relocate
                setNextEvent("ehGeneral.dspReader");
        } catch (any e) {
                getPlugin("messagebox").setMessage("error", e.message & "<br>" & e.detail);
                //Call internal event by passing the request context
                dspSignUp(Event);
                //or you can use the runevent method
                //runEvent("ehUser.dspSignUp")
        }
        </cfscript>
</cffunction>

Event Handler Implicit Events: preHandler, postHandler

There are also two implicit events that can be declared in your event handler that the framework will use in order to execute them anytime an event is fired from the current handler. This is great for intercepting calls and for pre/post processing before any type of event. If you declared them, the framework will execute them. (See Request Life Cycle)

preHandler Executes before the requested event (In the same handler cfc)
postHandler Executes after the requested event (In the same handler cfc)

Example:

<cffunction name="preHandler" output="false" returntype="void" access="public">
  <cfargument name="Event" type="coldbox.system.beans.requestContext">
  <cfscript>
  //Execute any pre-event code here. Like AOP logging, etc.
  </cfscript>
</cffunction>

<cffunction name="postHandler" output="false" returntype="void" access="public">
  <cfargument name="Event" type="coldbox.system.beans.requestContext">
  <cfscript>
  //Execute any post-event code here. Like layout considerations, etc.
  </cfscript>
</cffunction>

How to set and get values (Event Handlers, Views, Layouts)

In order for any event handler to work, it needs values. Most likely url parameters, form submissions or application/session/client variables. The framework provides you with a request collection structure for all your variable needs, modeled after the requestContext object. This object gets passed in to every event handler method with the argument name of event and type coldbox.system.beans.requestContext. The object also gets prepared for usage in your views and layouts as event. Below is an example of a event handler method using the request collection.

<cffunction name="dspHome" access="public" returntype="void" output="false">
  <cfargument name="Event" type="coldbox.system.beans.requestContext">
  <!--- 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>

The framework automatically captures the FORM and URL scopes, in specific precedence, into the request collection for your usage. It also provides you with utility methods to interact with the collection. For more in depth methods look the the API

  1. getValue ( name, defaultValue )
    1. name: The name of the variable to return from the collection
    2. defaultValue: The default variable to return if the variable is not found in the collection. Since there are no default values that can be set for complex variables, you can send the following action keywords to return an empty complex variable according to the keyword. Please note that you need to pass in the keyword in brackets. Else the regular expression match will fail and the simple value will be returned.
      1. [array]
      2. [struct]
      3. [query]
  2. setValue ( name, value )
    1. name: The name of the variable to set in the reqCollection.
    2. value: The value of the variable (simple or complex)
  3. getCollection() : Returns the entire request collection data structure. Very useful to send in to the business layers or create a reference scope.
  4. paramValue(name, value) : This method checks if the name exists in the collection, if not, then it creates it with the value.
  5. removeValue(name) : Remove a value from the collection
  6. valueExists(name) : Checks if the value exists in the collection.
<cfscript>
//Creating a reference scope
var rc = event.getCollection();

//set a value
event.setValue("name", "Luis");

//param a value
event.paramValue("user_id","");

//remove a value
event.removeValue("name");

//check if value exists
if( event.valueExists("name") ){

}
</cfscript>

So if you need to retrieve a value from a form POST or URL parameters, then you will use the event.getValue() method. If you need to store values into the collection in order for the views and layouts to access them, use the event.setValue() method.

How to relocate to another event

The framework provides you with two methods that you can use to relocate to other events, 1 for normal urls and another for pretty SES urls:

  • setNextEvent([string event], [string queryString], [boolean addToken], [string persist])
  • setNextRoute(string route, [string persist])

setNextEvent

The setNextEvent method can be used for normal urls and it can take up to 4 arguments:

Argument Required Type Description
event false string The event to relocate to, if empty it defaults to the default event. ex: main.home
queryString false string The query string to append to the relocation
addToken false boolean Whether to add the cf tokends or not. Default is false
persist false string(list) A comma-delimmited list of request collection key names to persist in the relocation

All the arguments above are pretty straightforward except the last one: persist. This argument can be a comma-delimmited list of request collection key names that you want to persist in the internal flash memory of the framework on the relocation. So when the user get's relocated, those variables (can be simple, complex, or even objects) will be flash stored and re-inflated back to the request collection on the relocation. Very useful to silently keep state on temporary objects.

setNextRoute

The setNextRoute method can be used for normal urls and it can take up to 2 arguments:

Argument Required Type Description
route true string The route to relocate to. ex: main/home
persist false string(list) A comma-delimmited list of request collection key names to persist in the relocation

All the arguments above are pretty straightforward except the last one: persist. This argument can be a comma-delimmited list of request collection key names that you want to persist in the internal flash memory of the framework on the relocation. So when the user get's relocated, those variables (can be simple, complex, or even objects) will be flash stored and re-inflated back to the request collection on the relocation. Very useful to silently keep state on temporary objects.

In order to understand routes in ColdBox, please refer to the SES and Pretty URL guide. It will explain how you can use these incredible routing system.

Best Practices

Organization

I always try to have some kind of mapping between the different logical sections or modules of my application and their event handlers. For example, if my application has a section for user management, with master and detail views, I'd create a single event handler cfc to hold all the methods related to that module. Now, large sections of your application that are more complex and have lots of actions and views, may require you to split the event handlers even more (like packages/directories), but the idea is the same.

The handler cfc's are also objects and therefore they should have their specific identity and function. So you need to map them in the best logical way according to their functions. If you need more flexibility you can even create directories (packages) and place them in logical buckets. This is mostly used in large applications as mentioned above. So think of them as entities and how they would do tasks for you via the events. Once you do this, you can come up with a very good cohesive event model for your applications. The application template creates two event handlers for you: ehMain and ehGeneral. The ehMain contains mostly implicit methods for use in conjunction with the config.xml. The ehGeneral is a general event handler and so forth.

In conclusion, organize your handlers as you would a domain model, put in practice your Ontology skills and define what these handlers will do for your, what is their identity.

Executing other events (Event Chaining)

The best practice on event execution would be via the runEvent() method. That is what is there for and it should be used. You can bypass it when calling methods in the same event handler. However, the best practice would be to use the provided method.

Init Method

Every handler does NOT need an init method, but if used, it most comply to the standard shown in this guide. In that init method you can do whatever you want. If you want to set private properties to a handler, due composition of services, etc. You can.

Naming Conventions

The naming conventions are optional but are best practices and are described in detail in this document.

Advanced OO Features: UDF Injections

ColdBox provides you with a way to actually inject your event handlers with custom UDF's so you can change the behavior or expand on the behavior an event handler already has. This is called mixin methods and can be done via the includeUDF() method provided to every event handler or via the UDFLibrary setting in your coldbox.xml. The method is a provided way for you to dynamically load UDF's into your event handlers at runtime. You can do this in a specific method or at the initialization of the handler via the init() method. The sample below is using the init method:

<cffunction name="init" access="public" returntype="ehGeneral" output="false">
  <cfargument name="controller" type="any" required="yes">
  <cfscript>
  super.init(arguments.controller);
  //Any Code you like below
  
  //Load my special UDF's for my tools handler.
  includeUDF('includes/toolsUDF.cfm');
  
  //return instance.
  return this;
  </cfscript>
</cffunction>   
        

The includeUDF method call will find the template and inject it to the handler.

WARNING: If you try to inject a method that already exists in the handler, the call will fail and !ColdFusion will throw an exception.

There you go, that is dynamic loading of methods in your handlers. If you choose the route of the config file, you will declare the location of the UDF template to load into the handlers, layouts and views. That is the main difference between calling the includeUDF method or using the config setting.


Copyright 2006 ColdBox Framework by Luis Majano