Model-Glue and post-processing views for e-mail send

3 views
Skip to first unread message

Jamie Krug

unread,
Feb 4, 2009, 2:11:48 PM2/4/09
to model-glue
Hi, all,

My mind is swimming with various thoughts around MVC, OO design
practices and Model-Glue... I've searched around quite a bit before
posting here. I'm primarily looking for Model-Glue usage advice here,
as I'm still a bit new to MG and also know that Gesture has some
significant changes, but please speak up if my design sounds way off
to you. (BTW, I'm using Gesture, bleeding edge release from SVN.) Any
suggestions would be very much appreciated. So, here's my situation...

I have a registration of sorts and I need to send a confirmation e-
mail upon successful registration. It will be a multi-part message
(using cfmailpart tags for text and html versions) with some dynamic
content. I wrote a little EmailUtility.cfc that makes it easy to pass
in arguments for a multi-part message, along with any other attributes
to be passed through to cfmail. I can't help but think that my mail
part arguments are essentially rendered views -- in fact, that's
exactly what they are. As a matter of fact, to test I simply commented
out my MG event's <results> section so my e-mail body view would
render to my browser screen.

That said, I don't want to have my view responsible for sending the e-
mail, because the view should just be responsible for that one piece
of the puzzle (the html or text body for the e-mail -- not other
cfmail attributes that come from domain objects).

Okay, so I was all excited and thought I'd just have a few events
chained via results and have a notification controller just grab the
two views for the e-mail body parts and pass them along as arguments
to my e-mail service. But I always seemed to get an empty string when
calling arguments.event.getView('htmlBody') in my controller method.

Ray Camden seemed to have a similar question a while back, but no
response here:
http://www.nabble.com/odd-issue-with-event.getView-td9743691.html

Searching some more, I've learned (I think) that views are not
rendered until all results have been handled, which is why my rendered
view is not available to the controller. Now, I also realize that this
is breaking MVC a bit by having the controller layer dealing with the
view layer, but it seemed like a reasonable use case.

So, I searched yet some more and found a really slick solution by Sean
Corfield, to nearly my exact problem:
http://corfield.org/blog/index.cfm/do/blog.entry/entry/ModelGlue_and_PostProcessing_Views

Finally, on to my ultimate question... Is Sean's solution the one I
should bear in mind as I solve my current problem, or have there been
significant changes to Model-Glue (since Sean's 2005 post) that would
provide another path? I'm also still trying to fully wrap my head
around Sean's solution, but I think I follow how he's injecting a
callback for a view. That said, I think I'd need a pseudo-layout view
for my situation, which would be able to access both my htmlBody and
textBody views, right? I wonder if that nice new MG3 event types
feature might be useful here?

FWIW, here's a ModelGlue.xml snippet to demonstrate the idea I had
(which, remember, does not work, other than sending a blank e-mail
confirmation!):

<event-handler access="public" name="register.save">
<broadcasts>
<message name="needRegistrationSaved" />
</broadcasts>
<views>
<include name="pageTopMessage" template="includes/
pageTopMessage.cfm" />
</views>
<results>
<result name="saveRegistration.success"
do="register.prepareConfirmation" />
<result name="saveRegistration.fail" do="register.main" />
</results>
</event-handler>

<event-handler access="private" name="register.prepareConfirmation">
<views>
<include name="htmlBody" template="pages/
registerConfirmationHtml.cfm" />
<include name="textBody" template="pages/
registerConfirmationText.cfm" />
</views>
<results>
<result do="register.sendConfirmation" />
</results>
</event-handler>

<event-handler access="private" name="register.sendConfirmation">
<broadcasts>
<message name="needRegistrationConfirmationSent" />
</broadcasts>
<results>
<result do="register.login" />
</results>
</event-handler>

Thanks a ton, in advance, to anyone who's made it through my lengthy
post and might actually be kind enough to reply :)

Best,
Jamie

Jamie Krug

unread,
Feb 4, 2009, 4:20:40 PM2/4/09
to model-glue
Quick update (to my own post:)... Searching a bit further, I see that
Joe Rinehart blogged about a similar topic:
http://firemoss.net/post.cfm/ModelGlue--Rendering-Excel-Email-and-alternate-formats

Notice the sole comment as Sean making note of the callback idea. I
was able to quickly implement this solution, so that's what I'm going
with for now, unless anyone has other ideas!

I've taken the route of having my controller CFC store a reference to
itself in the event as the callback object, as opposed to another CFC,
because my sendConfirmation() method needs to pull not only both the
rendered views for the text and html mail body parts from the event,
but also two other business objects that already live in the event
object. So, I have my layout view pass the event as an argument to the
callback controller object. This also allows my controller's
sendConfirmation() method to behave just like any other controller
method (it doesn't need to know it's part of a callback or anything).

Here's an example snippet from my layout view, which makes all the
callback magic happen:

<cfscript>
if ( event.exists('registrationConfirmationCallback') ) {
// grab reference to callback object from event:
callbackObj = event.getValue('registrationConfirmationCallback');
// add expected rendered views to event:
event.setValue('registrationConfirmationHtml', event.getView
('registrationConfirmationHtml') );
event.setValue('registrationConfirmationText', event.getView
('registrationConfirmationText') );
// make callback:
callbackObj.sendConfirmation(event);
}
</cfscript>

And this is basically what the callback controller method looks like:

<cffunction name="sendConfirmation" returntype="void" access="public"
output="false">
<cfargument name="event" type="any" required="true" />

<cfscript>
var args = {
user = arguments.event.getValue('user'),
registration = arguments.event.getValue('registration'),
htmlBody = arguments.event.getValue
('registrationConfirmationHtml'),
textBody = arguments.event.getValue
('registrationConfirmationText')
};

getRegistrationService().sendConfirmation(argumentCollection=args);
</cfscript>
</cffunction>

Best,
Jamie
> Corfield, to nearly my exact problem:http://corfield.org/blog/index.cfm/do/blog.entry/entry/ModelGlue_and_...

Nando

unread,
Feb 4, 2009, 5:54:48 PM2/4/09
to model...@googlegroups.com
Ummm ... I simply render the email in a cfc method, using cfsavecontent to save the rendered portion to a variable that is returned to the method that sends the email out. It's very easy to do. My thinking is that only content to be viewed via the browser should be processed via MG's view mechanism. Using it to render email content seems unnecessarily contrived to me.

If you're concerned about MVC separation, then create a separate cfc and call it something like EmailRenderer. You can even place that in a separate directory structure if you want, so it's not under the "model" directory. I don't bother because I usually work solo. There are certain complex problems I've run across rendering a view in MG for the browser that I haven't been able to solve without using render methods that effectively encapsulate variables and the rendering of view chunks. It can be an elegant solution that to my mind shouldn't be set aside for a somewhat fixed interpretation of how MVC is properly implmented.

Nando
--

Nando M. Breiter
The CarbonZero Project
CP 234
6934 Bioggio
Switzerland
+41 76 303 4477
na...@carbonzero.ch

Jamie Krug

unread,
Feb 4, 2009, 9:35:44 PM2/4/09
to model-glue
Nando,

Thanks for the reply. Your suggestion certainly seems to make sense,
and I did consider that, but I guess I was just hung up on the fact
that the rendering of that e-mail body is exactly like the rendering
of any other view. In both cases, you grab a few variables from the
framework's event object and output accordingly within your markup.
The only difference being that one rendered view is output in a layout
view to the browser and another in an e-mail. I was just trying to go
with my gut on this. I don't feel I have enough serious OO design
experience to argue one way or the other, nor do I feel strongly about
either. That said, the renderer CFC plan certainly sounds more simple!
I have seen a number of other developers recommend a similar solution,
too.

Now that I'm thinking about, comparing the view callback solution to
the renderer CFC solution shows little real difference with regard to
MVC. For the callback, I have a controller that simply injects an
instance of itself into the event, then the MG view rendering takes
care of the required rendered views, and finally there's the "layout"
view that checks for the callback and calls upon it to trigger the e-
mail send. With the renderer CFC solution, there's no use of the MG
view rendering mechanism, but there's still a controller that does
something to make the rendered view(s) available to the event. What
I'm getting at, I think, is that in both cases we have the controller
layer sitting between the view rendering and the model.

It really boils down to my final controller's sendConfirmation()
method. In both solutions, I need the grab the same objects to pass in
to the service layer as arguments. The only difference is that the
callback solution gets the rendered e-mail bodies from event
values,arguments.event.getValue(), while the renderer CFC solution
just has the controller method call a method on a renderer CFC, which
can be wired in by ColdSpring of course. So it's a ColdSpring wire-up
versus the framework's rendering engine -- the renderer CFC doesn't
know anything about the model or controller layer, it really does
behave as part of the view layer.

So, to the best of my knowledge, both seem like very fair adherence to
the MVC design. I really feel like I'm just rambling on about a subtle
difference that may just be largely personal preference, but thanks
for the feedback and thanks for listening!

Best,
Jamie

On Feb 4, 5:54 pm, Nando <d.na...@gmail.com> wrote:
> Ummm ... I simply render the email in a cfc method, using cfsavecontent to
> save the rendered portion to a variable that is returned to the method that
> sends the email out. It's very easy to do. My thinking is that only content
> to be viewed via the browser should be processed via MG's view mechanism.
> Using it to render email content seems unnecessarily contrived to me.
>
> If you're concerned about MVC separation, then create a separate cfc and
> call it something like EmailRenderer. You can even place that in a separate
> directory structure if you want, so it's not under the "model" directory. I
> don't bother because I usually work solo. There are certain complex problems
> I've run across rendering a view in MG for the browser that I haven't been
> able to solve without using render methods that effectively encapsulate
> variables and the rendering of view chunks. It can be an elegant solution
> that to my mind shouldn't be set aside for a somewhat fixed interpretation
> of how MVC is properly implmented.
>
> Nando
>
>
>
> On Wed, Feb 4, 2009 at 10:20 PM, Jamie Krug <jamiek...@gmail.com> wrote:
>
> > Quick update (to my own post:)... Searching a bit further, I see that
> > Joe Rinehart blogged about a similar topic:
>
> >http://firemoss.net/post.cfm/ModelGlue--Rendering-Excel-Email-and-alt...

Chris Blackwell

unread,
Feb 9, 2009, 12:48:34 PM2/9/09
to model...@googlegroups.com
Why not just force MG to render the view when you want it.

ModelGlue.xml

<event-handler name="page.emailTest">
<broadcasts>
<message name="doEmailTest">
<argument name="template" value="email/emailTest.cfm" />
</message>
</broadcasts>
</event-handler>

Controller.cfc

<cffunction name="doEmailTest">
<cfargument name="event" />
<cfset var email_content = "" />
  <cfset var view = createObject("component", "ModelGlue.gesture.eventhandler.View") />
<cfset view.template = event.getArgument("template") />
<cfset view.name = "email_content" />
<cfset event.setValue("value_i_need_in_my_view", "Hi!") />
<cfset event.renderView(view) />
<cfset email_content = event.getView('email_content') />
<!---
  cfmail or whatever here
--->
</cffunction>

This only uses public methods of the event object, you don't have to hardcode your "view" templates in the controller because of the argument tag, the view template is reusable and the rendered view is available later in the request... 

Chris

Jared Rypka-Hauer

unread,
Feb 9, 2009, 3:39:18 PM2/9/09
to model...@googlegroups.com
I think I'd do it differently, to be honest, because something doesn't strike me as kosher about creating instances of MG's internal components when there are more standard, built-in ways of doing this.

FYI - Unity's EventContext.cfc doesn't have a renderView() function, although Gesture's does so you're going to have to do one of two things to get this to work depending on your version, but the solution is basically the same. I bring up Unity because I'm still stuck on Unity for my main app, tho' gesture is looking pretty freakin sweet.

I recommend using a registered Controller onRequestEnd() end method to check the ViewCollection for an email* view and send it. Right now I've only got one email view being generated, but it wouldn't be hard at all to adapt this to looping over the contents of the ViewCollection and sending any view where left(viewName,5) IS "email" thru cfmail. As has been indicated previously, onQueueComplete() runs after the event queue has run but before the views are rendered... but onRequestEnd() runs after all events have been run and views rendered... so any <include /> tag with a name="" attribute is available to onRequestEnd() at getEventRequest().getViewCollection().getView("name") in Unity and getViewCollection().getView("name") in Gesture.

Before I get into the actual XML and Controller code, though, I want to point out one thing: What I'm posting here is a sample, or a simple example, or something... the variations on this theme are nearly infinite because the solution is based !!mostly!! on configuration, not on hardcoding (much) into the controller. The parts that are hardcoded in this example can easily be extracted into wildcards, collections or arguments. It's easy enough to do as the XML below shows... one path that generates both HTML and SMPT view output, using separate content for each... creating execution paths is as simple as using <result do="" /> in clever ways to either delegate to "headless" views without refreshing a page or to generate HTML output and SMTP output at the same time. So please keep in mind that this is as simple an example as I could present to get the idea across without encumbering us all with some gigantic email that's not digestible.

It's close enough to that as it is. ;)

OK, so now to the nitty-gritty:

The differences between Unity and Gesture come pretty much at the end of this process... but the first parts are pretty much the same. Here's your XML (very subtle variations (mostly template names and paths and the controller type="" attribute) needed to adapt to Gesture properly, but this config file will work just fine with either version of the framework. Notice that there are 2 ways to view the default. Hitting page.index displays the page and hitting page.indexWithMail sends an email (be sure change the controller code to correct email addresses and mail settings before you test this! hehe).

Here's the sample XML:

<modelglue>
<controllers>
<controller name="MyController" type="unityTestApp.controller.Controller">
<message-listener message="OnRequestStart" function="OnRequestStart" />
<message-listener message="OnQueueComplete" function="OnQueueComplete" />
<message-listener message="OnRequestEnd" function="OnRequestEnd" />
</controller>
</controllers>
  
<event-handlers>
<!-- URL-ACCESSIBLE EVENTS -->
<event-handler name="page.indexWithMail">
<!-- processes the output into an email using onRequestEnd() to redirect the output -->
<broadcasts /> 
<results>
<result do="page.email" />
</results>
<views>
<include name="body" template="dspIndex.cfm" />
</views>
</event-handler>

<event-handler name="page.index">
<!-- processes the output into an email using onRequestEnd() to redirect the output -->
<broadcasts /> 
<results>
<result do="view.template" />
</results>
<views>
<include name="body" template="dspIndex.cfm" />
</views>
</event-handler>

<event-handler name="page.email">
<!-- processes the output into an email using onRequestEnd() to redirect the output -->
<broadcasts /> 
<results>
<result do="email.template" />
</results>
<views>
<include name="emailBody" template="dspIndex.cfm" />
</views>
</event-handler>

<!-- TEMPLATES AND EXCEPTIONS -->

<event-handler name="email.template" access="private">
<broadcasts />
<results>
<result do="view.template" />
</results>
<views>
<include name="email" template="emlTemplate.cfm" />
</views>
</event-handler>
<event-handler name="view.template" access="private">
<broadcasts />
<results />
<views>
<include name="template" template="dspTemplate.cfm" />
</views>
</event-handler>

<event-handler name="exception">
<broadcasts />
<views>
<include name="body" template="dspException.cfm" />
</views>
</event-handler>
</event-handlers>
</modelglue>


So here, with page.indexWithMail I'm illustrating the following sequence:
1) Run main event and create a view called body
2) Control is routed to page.email, which creates a view called emailBody
3) Control is routed to email.template, which will wrap emailBody with whatever template code is included and creates a view called email
4) Control is routed to view.template which wraps the view called "body" (from step 1) in whatever wrapper code is included and creates a view called template
5) Run onQueueComplete()
5) Render all Views
6) Run onRequestEnd() and, in doing, send the view called "email" from step 2
7) Since "template" is the last view in the ViewCollection to have been rendered, it's the default HTML output from the framework, so it's dumped into the output buffer and returned to the browser.


A few quick notes on this:

a) email.template includes standard syntax like page.template... ViewState.getView("body") is replaced by ViewState.getView("email") is the only difference, and that's just to keep email content and content variables separate from the regular body output

b) an argument could replace the view's content variable name if you wanted to keep the content variables names and contents the same throughout the process

c) step 6 could loop over the ViewCollection's contents using ViewCollection.getAll()

d) (COOLEST SOLUTION! hehe) If you wanted to be really robust about it, this is a class-a situation for implementing classes that extend MG components and then get superimposed onto the framework instance using the instructions in /ModelGlue/gesture/configuration/ModelGlueConfiguration.xml or /ModelGlue/unity/config/Configuration.xml... superclassy and super-reusable (not to mention adaptable to action packs). This is where you could implement your own ViewCollection or ViewRenderer even, that could handle some of this automatically.

Unity Controller Code:
<cffunction name="onRequestEnd" access="public" returnType="void" output="false">
  <cfargument name="event" type="any">
<cfset var Request = arguments.event.getEventRequest()>
<cfset var ViewCollection = Request.getViewCollection()>
<cfif ViewCollection.exists("email")>
<cfmail from="ja...@example.com"
subject="MG Test App Email"
type="html">#ViewCollection.getView("email")#</cfmail>
</cfif>
</cffunction>


Gesture Controller Code:
<cffunction name="onRequestEnd" access="public" returnType="void" output="false">
  <cfargument name="event" type="any">
<cfset var ViewCollection = Event.getViewCollection()>
<cfif ViewCollection.exists("email")>
<cfmail from="ja...@example.com"
subject="MG Test App Email"
type="html">#ViewCollection.getView("email")#</cfmail>
</cfif>
</cffunction>

OK, so I have the makings for some serious blog posts here... I'll get around to it this week, but I wanted to share my perspective on this because ModelGlue has a lot richer and more powerful functionality than it seems to get credit for sometimes. :)


Man I really like note D above... I may have to do that one day...

J

Chris Blackwell

unread,
Feb 9, 2009, 4:27:20 PM2/9/09
to model...@googlegroups.com
Hi Jared,

That's a clever approach and I do agree somewhat about using MG's internal components, but I feel its an acceptable compromise. 

Out of interest how would you handle sending multiple emails, eg. looping over a query, using your approach?

Chris

Jared Rypka-Hauer

unread,
Feb 10, 2009, 2:09:36 PM2/10/09
to model-glue
There's any number of ways to handle it... I guess I wasn't so much
focused on the specific implementation as I was on the underlying
architecture. Without something abstract and flexible (read: lots of
work), any method for sending email is going to be pretty specific to
the implementation. So you've really got a couple choices: include
configuration thru arguments or event values or view values or
something or build what details you need to into your application. I
would tend to do things like include <argument name="EventQueryKey"
value="QueryOfEmailAddresses" /> in my top-level email-sending event,
and then have my controller with onRequestEnd do something like this:

XML:
<event-handler name="page.email">
<!-- processes the output into an email using onRequestEnd()
to redirect the output -->
<broadcasts>
<message name="setupForSendingMail">
<argument name="EventQueryKey"
value="SomeQueryFullOfEmailAddresses" />
</message>
</broadcasts>
<results>
<result do="email.template" />
</results>
<views>
<include name="emailBody" template="dspIndex.cfm" />
</views>
</event-handler>

...somewhere in onRequestEnd()...
<cfset var query = 0>
<cfif event.argumentExists("EventQueryKey")>
<cfset query = event.getValue(event.getArgument("EventQueryKey"))
>
</cfif>

<cfmail query="query" ...>
...
</cfmail>

That way you keep the details out of your implementation and in the
configuration where you have the flexibility to effectively configure
the controller layer for whatever model layer you may have, or to talk
to other service packs or use data from parts of the app written by
other devs or written before this particular section... you're not
stuck with ALL the details being hardcoded into your DB

And I'm putzing around with a way to override the built-in framework
functionality to enhance it with some abilities to make things like
this work well in a different way. More on that later. :)

Jason Fisher

unread,
Feb 10, 2009, 5:39:26 PM2/10/09
to model...@googlegroups.com
I think this has come up in some form somewhere along the chain, but in order to avoid duplicating my views for things like email, I tend to create CFCs that are Viewers.  So these aren't related to my actual business or DAO objects, although they work with similar data.  So, there may be a TemplateService.cfc for getting datasets back by calling a TemplateDAO.cfc or whatever, but actual view layouts can be rendered in the TemplateViewer.cfc as event variables for use anywhere else, including in an actual View.  Individual event-handlers, in turn, can call TemplateViewer methods (from the TemplateViewer controller defined in the controllers block) to output for page layout or for email layout or for any other type of layout I decide I need, so there's never a need a to back-reference or jump through hoops to get there.  An EmailNotifier.cfc can simply refer to the arguments.event.getValue("emailOutput") or whatever it needs to feed the CFMAIL call.
 
Just some thoughts.

Jamie Krug

unread,
Mar 4, 2009, 3:04:41 PM3/4/09
to model-glue
Chris/Jared/Jason: Thanks a bunch for the additional thoughts on this
thread. I just noticed that I missed these when first posted. You guys
are no help in my decision-making, because they all sound like good
ideas, but you've provided some more fantastic food for thought ;-)

@Jared: I would be curious to hear where you end up with regards to
the Model-Glue ActionPack ideas. FWIW, I started my real Model-Glue
development with Gesture Alpha, so very little experience with Unity.

Best,
Jamie
Reply all
Reply to author
Forward
0 new messages