Improving the tutorial: using the same view for multiple actions in CFWheels

August 20, 2009 · Chris Peters

I'll fulfill my promise by giving another example of how I would improve the "Hello Database" tutorial for CFWheels. This time, I'll talk about how we could factor out view logic so that the profile form can be used for both the add and edit actions.

I’ll fulfill my promise by giving another example of how I would improve the Hello Database tutorial for CFWheels.

This time, I’ll talk about how we could factor out view logic so that the profile form can be used for both the add and edit actions. With all of this MVC goodness, we should be able to do some refactoring easily, shouldn’t we?

Using the same view for add and edit

So let’s list each view file to identify where the repetition appears. I’ll also follow my own advice and assume that I’ve factored out the repetitive <div> markup into global defaults.

First, here’s the view at views/users/new.cfm:

<cfoutput>
<h1>Create a New User</h1>
#startFormTag(action="create")#
#textField(objectName="user", property="name", label="Name")#
#textField(objectName="user", property="email", label="Email")#
#passwordField(objectName="user", property="password", label="Password")#
<div>#submitTag()#</div>
#endFormTag()#
</cfoutput>
view raw new.cfm hosted with ❤ by GitHub

And here’s the view at views/users/edit.cfm:

<cfoutput>
<h1>Edit User #user.name#</h1>
<cfif flashKeyExists("success")>
<p class="success">#flash("success")#</p>
</cfif>
#startFormTag(action="update")#
#textField(objectName="user", property="name", label="Name")#
#textField(objectName="user", property="email", label="Email")#
#passwordField(objectName="user", property="password", label="Password")#
<div>
#hiddenField(objectName="user", property="id")#
#submitTag()#
</div>
#endFormTag()#
</cfoutput>
view raw edit.cfm hosted with ❤ by GitHub

The first difference that we can notice is the title contained in the <h1> tags at the top of each file. Assuming that we don’t factor them out into a layout (which may be the best idea), we can set that as a variable in the controller and let it decide what to show in each case. Either way, the value would need to be set in the controller at that point.

The same may go for the success message. That may go in a layout file as well. But as it turns out, we could just have it available in both scenarios. It won’t show unless that value is set in the Flash anyway.

Lastly, it doesn’t matter if the id value is included as blank in the add action, so we can just include it in both scenarios as well.

Those changes said, we can write a single view file. Let’s call it views/users/form.cfm.

<cfparam name="header" type="string">
<cfparam name="user">
<cfparam name="formAction" type="string">
<cfparam name="submitLabel" type="string">
<cfoutput>
<h1>#header#</h1>
<cfif flashKeyExists("success")>
<p class="success">#flash("success")#</p>
</cfif>
#startFormTag(action=formAction)#
#textField(objectName="user", property="name", label="Name")#
#textField(objectName="user", property="email", label="Email")#
#passwordField(objectName="user", property="password", label="Password")#
<div>
<cfif not user.isNew()>
#hiddenField(objectName="user", property="id")#
</cfif>
#submitTag(value=submitLabel)#
</div>
#endFormTag()#
</cfoutput>
view raw form.cfm hosted with ❤ by GitHub

Notice that I also use <cfparam> tags at the top of the view file to check for values that are expected from the controller. It’s a preference of mine to do this because it just makes everything more self-documenting.

This view file also requires a submitLabel value to be used in the submitTag() form helper and a formAction value to be used to define which action to call.

So here’s what the add() and edit() methods in the controller at controllers/Users.cfc would look like to accommodate the view changes:

<cffunction name="add">
<cfset user = model("user").new()>
<cfset header = "Create a New User">
<cfset formAction = "create">
<cfset submitLabel = "Create">
<cfset renderPage(template="form")>
</cffunction>
<cffunction name="edit">
<cfset user = model("user").findByKey(params.key)>
<cfset header = "Edit User #user.name#">
<cfset formAction = "update">
<cfset submitLabel = "Save changes">
<cfset renderPage(template="form")>
</cffunction>
view raw Users.cfc hosted with ❤ by GitHub

Look, the repetition is gone, and now we have one less view file! We just set the values needed by each action and call renderPage() with the name of the view file to run, form.

There are ways to create partials and helpers to factor some of this out application-wide. But I wanted for you to see the process of factoring code up one level. Perhaps we shall explore in a future post.

Update: I’ve written about another approach for refactoring your views using partials. As you will see, there are multiple solutions with different trade-offs.

About Chris Peters

With over 20 years of experience, I help plan, execute, and optimize digital experiences.

Leave a comment