I spent the last week learning FW/1 (you can see my quick review here) and while - in general - it was pretty easy - one thing was a bit confusing for me. I had a hard time wrapping my head around the logic of:
- User submits a form.
- Controller checks the form.
- If bad, we go back to the form.
- If good, we go to some other view.
That's not complex, but as I said, it was tricky for me to get it working with FW/1. In a Model-Glue application I'd post to an event that runs one controller method. That method would do it's validation and then either throw a bad result or fire off a service method and use a good result. This logic doesn't work the same in FW/1. Thanks go to Sean Corfield for explaining why and hopefully this blog entry will make it easier for others.
First off - what happens if you use a simple controller method? Imagine you had code like so:
<cffunction name="addComment" output="false">
<cfargument name="rc" type="struct" required="true">
<cfparam name="rc.comment_name" default="">
<cfparam name="rc.comment_email" default="">
<cfparam name="rc.comment_comments" default="">
<cfset rc.errors = []>
<cfif not len(trim(rc.comment_name))>
<cfset arrayAppend(rc.errors, "You must include a name.")>
</cfif>
<cfif not len(trim(rc.comment_email)) or not isValid("email", rc.comment_email)>
<cfset arrayAppend(rc.errors, "You must include a valid email.")>
</cfif>
<cfif not len(trim(rc.comment_comments))>
<cfset arrayAppend(rc.errors, "You must include your comments.")>
</cfif>
<cfif arrayLen(rc.errors)>
<cfset variables.fw.redirect("blog.entry", "id,comment_name,comment_email,comment_website,comment_comments,errors")>
<cfelse>
<cfset variables.fw.service("blog.addcomment", "data")>
<cfset variables.fw.redirect("blog.entry", "id")>
</cfif>
</cffunction>
This is very similar to how I'd have written it in Model-Glue. Get the values - check em - and either create a bad or good result. Obviously this didn't work. When I tested with errors it worked fine. When I tested with a good form, the redirect work but not the service call. There are a couple of things wrong here.
First - I had forgotten that FW/1 will run my service call automatically. I didn't need to run the call. Secondly - as is described in the Designing Controllers section of the FW/1 docs, there is an implicit set of controller events run on every request. This is more than just the controller method with the same name as the event ("addComment"). It includes both a pre and post invocation as well. What this means is that I can use the implicit calls, in this case, startAddComment and endAddComment, to handle 'wrapping' my service call. From my previous example application, here is the rewritten logic:
<cffunction name="startAddComment" output="false">
<cfargument name="rc" type="struct" required="true">
<cfparam name="rc.comment_name" default="">
<cfparam name="rc.comment_email" default="">
<cfparam name="rc.comment_comments" default="">
<cfset rc.errors = []>
<cfif not len(trim(rc.comment_name))>
<cfset arrayAppend(rc.errors, "You must include a name.")>
</cfif>
<cfif not len(trim(rc.comment_email)) or not isValid("email", rc.comment_email)>
<cfset arrayAppend(rc.errors, "You must include a valid email.")>
</cfif>
<cfif not len(trim(rc.comment_comments))>
<cfset arrayAppend(rc.errors, "You must include your comments.")>
</cfif>
<cfif arrayLen(rc.errors)>
<cfset variables.fw.redirect("blog.entry", "id,comment_name,comment_email,comment_website,comment_comments,errors")>
</cfif>
</cffunction>
<cffunction name="endAddComment" output="false">
<cfargument name="rc" type="struct" required="true">
<cfset variables.fw.redirect("blog.entry", "id")>
</cffunction>
Notice - I don't even have an addComment controller method. Rather - I run code both in the beginning and end of my event. The service call is implicit. The error handling in onAddComment, specifically the redrect, will handle exiting out of the current process on error. Otherwise, things proceed normally with the service call and endAddComment running automatically.
Makes sense once you see it I guess but it means that your controller code has to be a bit... I don't know. I won't say more complex, since you get to leave a lot out - but coming from Model-Glue, you definitely have to think... different about your controllers.