Now that Model-Glue 3 has been announced and available for folks to play with, it's time to start working up some demos so folks can see stuff in action. Joe has included a few demos in the zip, but if I don't play with it myself, I don't learn.
Before I go any further, and yes, I will be repeating this warning a lot, keep in mind the following two points:
- Model-Glue 3 is in Alpha. Everything I talk about here may stop working tomorrow. If it does, I'm going to hunt Joe down and make him rewrite Model-Glue in Pascal.
- Like every new feature, there are "good" ways of using it and "not so good" ways of using it. This is the first time I've done this feature. It may be a stupid example. I may look back in a month and ask - what was I drinking.
So with the above in mind, let's dig in.
First off - let's talk about what Custom Event Types (CETs) are. Those of us who develop in Model-Glue are used to the repeated XML we have to use for things like layout and security checks. Consider:1 <event-handler name="page.about">
2 <broadcasts />
3 <views>
4 <include name="body" template="main.about.cfm" />
5 </views>
6 <results>
7 <result do="view.template" />
8 </results>
9 </event-handler>
10 <event-handler name="page.category">
11 <broadcasts>
12 <message name="GetProjectCategory" />
13 <message name="GetProjectsForCategory" />
14 </broadcasts>
15 <views>
16 <include name="body" template="main.category.cfm" />
17 </views>
18 <results>
19 <result do="view.template" />
20 </results>
21 </event-handler>
These two simple events both have an unnamed event to run a template view. As you can imagine, my application has this repeated quite a bit. CETs allow us a simple way to get around it. Let's start with the XML changes you have to make.
MG3 supports a type argument to the event-handler tag. Here is a simple example:
2 <broadcasts />
3 <views>
4 <include name="body" template="main.about.cfm" />
5 </views>
6 <results>
7 <result do="view.template" />
8 </results>
9 </event-handler>
10 <event-handler name="page.category">
11 <broadcasts>
12 <message name="GetProjectCategory" />
13 <message name="GetProjectsForCategory" />
14 </broadcasts>
15 <views>
16 <include name="body" template="main.category.cfm" />
17 </views>
18 <results>
19 <result do="view.template" />
20 </results>
21 </event-handler>
1 <event-handler name="page.index" type="mg3app1.events.customLayoutEvent">
The value is the full path to the CFC we will create to define our CET. Note that the value is passed to createObject, so use whatever paths you would normally use.
Ok, so the CFC. As you can see I used an events folder above. This was based on Joe's sample apps and as it made sense, I'm going to pretend it was all my idea and take credit for it.
Your CFC will begin by extending the core EventHandler from Model-Glue. Here is an empty CET:
1 <cfcomponent extends="ModelGlue.gesture.eventhandler.EventHandler">
2
3 </cfcomponent>
It's empty - but boy does it run fast. So out of the box there are 2 built-in events that you potentially will want to use:
2
3 </cfcomponent>
- beforeConfiguration: This is run while the event is starting up. This is where you would add broadcasts. So for a security CET, you would broadcast a message to check authorization/authentication.
- afterCongifuration: This is run when the event is ending. This is where you would add results. Note that CETs support named and unnamed results. Now to be clear - you can also broadcast stuff as well. But this would run after other broadcasts so if you need to broadcast something that later messages need, you want to use beforeConfiguration.
1 <event-handler name="page.index">
2 <broadcasts />
3 <results>
4 <result name="print" do="view.printtemplate" />
5 <result do="view.template" />
6 </results>
7 <views>
8 <include name="body" template="dspIndex.cfm" />
9 </views>
10 </event-handler>
11
12 <event-handler name="view.template">
13 <broadcasts />
14 <results />
15 <views>
16 <include name="template" template="dspTemplate.cfm" />
17 </views>
18 </event-handler>
19
20 <event-handler name="view.printtemplate">
21 <broadcasts />
22 <results />
23 <views>
24 <include name="template" template="dspPrintTemplate.cfm" />
25 </views>
26 </event-handler>
This turns out to be very easy in CET. Here is the code I used for afterConfiguration:
2 <broadcasts />
3 <results>
4 <result name="print" do="view.printtemplate" />
5 <result do="view.template" />
6 </results>
7 <views>
8 <include name="body" template="dspIndex.cfm" />
9 </views>
10 </event-handler>
11
12 <event-handler name="view.template">
13 <broadcasts />
14 <results />
15 <views>
16 <include name="template" template="dspTemplate.cfm" />
17 </views>
18 </event-handler>
19
20 <event-handler name="view.printtemplate">
21 <broadcasts />
22 <results />
23 <views>
24 <include name="template" template="dspPrintTemplate.cfm" />
25 </views>
26 </event-handler>
1 <cffunction name="afterConfiguration" access="public" returntype="void" output="false" hint="Called after configuring the event handler. Subclasses can use this to add messages, results, or views after they're added by something like a ModelGlue XML file.">
2 <cfset var result = createObject("component", "ModelGlue.gesture.eventhandler.Result") />
3 <cfset var secondresult = createObject("component", "ModelGlue.gesture.eventhandler.Result") />
4
5 <cfset super.afterConfiguration() />
6
7 <cfset result.event = "template.main" />
8 <cfset addResult(result) />
9
10 <cfset secondresult.name="printformat">
11 <cfset secondresult.event="template.printmain">
12 <cfset addResult(secondresult)>
13
14 </cffunction>
I create two results. The first is unnamed and represents my default template. The second is named printformat. To use this new event type, I first specify it in my XML:
2 <cfset var result = createObject("component", "ModelGlue.gesture.eventhandler.Result") />
3 <cfset var secondresult = createObject("component", "ModelGlue.gesture.eventhandler.Result") />
4
5 <cfset super.afterConfiguration() />
6
7 <cfset result.event = "template.main" />
8 <cfset addResult(result) />
9
10 <cfset secondresult.name="printformat">
11 <cfset secondresult.event="template.printmain">
12 <cfset addResult(secondresult)>
13
14 </cffunction>
1 <event-handler name="page.index" type="mg3app1.events.customLayoutEvent">
2 <broadcasts />
3 <results />
4 <views>
5 <include name="body" template="pages/index.cfm" />
6 </views>
7 </event-handler>
And I'm done. I made it so that in my application, if "print" exists in the Event (url or form obviously), the printformat result is added. I did this via onRequestEnd in my main controller:
2 <broadcasts />
3 <results />
4 <views>
5 <include name="body" template="pages/index.cfm" />
6 </views>
7 </event-handler>
1 <cffunction name="onRequestEnd" access="public" output="false" returnType="void">
2 <cfargument name="event" type="any">
3
4 <cfif arguments.event.valueExists("print")>
5 <cfset arguments.event.addResult("printformat")>
6 </cfif>
7 </cffunction>
So - this would have been the end of the story. Unfortunately, it didn't work. I bugged Joe again, and he came up with the answer. Unfortunately, the reason it doesn't work is rather low level. The simplest explanation is that onRequestEnd (an automatic broadcast in MG) is actually consdiered another event: modelglue.onRequestEnd.
Joe came up with a solution that isn't quite as short as my previous example, but works ok. Now what we do is simply broadcast a message that will do the same check as my onRequestEnd code above. Take a look at the new version on afterConfiguration:
2 <cfargument name="event" type="any">
3
4 <cfif arguments.event.valueExists("print")>
5 <cfset arguments.event.addResult("printformat")>
6 </cfif>
7 </cffunction>
1 <cffunction name="afterConfiguration" access="public" returntype="void" output="false" hint="Called after configuring the event handler. Subclasses can use this to add messages, results, or views after they're added by something like a ModelGlue XML file.">
2 <cfset var result = createObject("component", "ModelGlue.gesture.eventhandler.Result") />
3 <cfset var secondresult = createObject("component", "ModelGlue.gesture.eventhandler.Result") />
4 <cfset var message = createObject("component", "ModelGlue.gesture.eventhandler.Message") />
5
6 <cfset super.afterConfiguration() />
7
8 <cfset message.name = "checkFormat" />
9 <cfset addMessage(message) />
10
11 <cfset result.name="mainformat">
12 <cfset result.event = "template.main" />
13 <cfset addResult(result) />
14
15 <cfset secondresult.name="printformat">
16 <cfset secondresult.event="template.printmain">
17 <cfset addResult(secondresult)>
18
19 </cffunction>
As you can see, I now broadcast checkFormat. I won't show the code for that as it's the exact same as the onRequestEnd above.
Alright - so hopefully that wasn't too confusing! I've attached a zip of my application. Please note that Joe will be updating the MG3 alpha zip fairly shortly so this code may not work right this very second.
2 <cfset var result = createObject("component", "ModelGlue.gesture.eventhandler.Result") />
3 <cfset var secondresult = createObject("component", "ModelGlue.gesture.eventhandler.Result") />
4 <cfset var message = createObject("component", "ModelGlue.gesture.eventhandler.Message") />
5
6 <cfset super.afterConfiguration() />
7
8 <cfset message.name = "checkFormat" />
9 <cfset addMessage(message) />
10
11 <cfset result.name="mainformat">
12 <cfset result.event = "template.main" />
13 <cfset addResult(result) />
14
15 <cfset secondresult.name="printformat">
16 <cfset secondresult.event="template.printmain">
17 <cfset addResult(secondresult)>
18
19 </cffunction>
Comment 1 written by zac spitzer on 9 May 2008, at 11:33 PM
the code is looking quite verbose for a simple example.
using arguments would reduce the line count to 4 lines from the current 12
z
Comment 2 written by Sean Corfield on 9 May 2008, at 11:34 PM
Comment 3 written by Raymond Camden on 10 May 2008, at 8:29 AM
Comment 4 written by Shimju David on 11 May 2008, at 2:01 AM
Comment 5 written by Raymond Camden on 11 May 2008, at 8:04 AM