In the last entry, I finally showed you how to hook up model information (in this case the model handled our User data) to the Model-Glue controller. This let you logon to the system. Now I need to modify things a bit to let you actually register for the site. Most sites will have features like "Request a Password", but in keeping with things simple, I'm only building a logon form and a register form. If you forget your password, your out of luck. (Although I am planning some entries that take place place after the application is done, so this may be one of the improvements I add.)
The first thing to do is to add a "Register" link to the logon form. This can be found in dspLogon.cfm. I'm not going to show the entire template, but just the actual Register link.
I talked about this style of code earlier, and in fact, you can see it in use in the same template in the logon form. The "myself" value from the view state is simply a built-in value that points back to the application itself. All I need to do is append the event name at the end. Let's now add the register event to our ModelGlue.xml file:
<event-handler name="Register"> <broadcasts /> <views> <include name="body" template="dspRegister.cfm" /> </views> <results /> </event-handler>
Nothing too fancy here, in fact, I just cut and pasted the Logon event and renamed it along with the view. Just like the Logon event, and unlike the Home event, we don't need to check for your authentication status, obviously, as you won't be logged in at this point. Now let's build the register form:
<!--- grab potential errors ---> <cfset errors = viewState.getValue("errors")>
<cfparam name="form.name" default=""> <cfparam name="form.username" default="">
<head> <title>PhotoGallery Register</title> </head>
<cfoutput> <p> <h2>Register</h2>
<cfif isArray(errors) and arrayLen(errors)> <cfoutput> <ul> <b> Please correct the following errors:<br> <cfloop index="x" from="1" to="#arrayLen(errors)#"> <li>#errors[x]#</li> </cfloop> </b> </ul> </cfoutput> </cfif>
<form action="#viewstate.getValue("myself")#registerattempt" method="post"> <table> <tr> <td>name:</td> <td><input type="text" name="name" value="#form.name#"></td> </tr> <tr> <td>username:</td> <td><input type="text" name="username" value="#form.username#"></td> </tr> <tr> <td>password:</td> <td><input type="password" name="password"></td> </tr> <tr> <td>confirm password:</td> <td><input type="password" name="password2"></td> </tr> <tr> <td> </td> <td><input type="submit" name="logon" value="Register"></td> </tr> </table> </form> </p> </cfoutput>
There is a lot going on here, but lets focus on the Model-Glue portions. The very first thing I do is examine the view state for errors. I haven't built this logic yet, but I know that when I do the form checking, I'm going to return the errors in a variable called errors. I do this for all my forms so it makes it easy to remember. The errors will be an array of messages, so you can see that I loop over them later on to display the issues. Outside of that, everything else here is just a simple form. The only thing special is the use of "myself" again for the link and the event name, registerattempt.
So now I need to return back to my ModelGlue.xml file. Let's take a second here and think about it. I think you see a pattern here. As I build out this application, I'm going back and forth between my model, my controller, and my view. What I find powerful about this setup is the level of separation between each piece, and how it makes things simpler for me. There have been a lot of discussion lately about frameworks and whether or not they are good or not. Personally I think this is something every developer, or company, needs to decide for themselves. For me, I really like the fact that with Model-Glue, everything has a place and there is a place for everything. I don't want to spend time worrying about where to put files. I don't want to spend time worrying about how my application will operate. I want to spend time worrying the bits of code that are not trivial. The business logic. The things that really require my time - not simple stuff like where each file should go. Anyway, sorry for the mini sermon there. Let's add our registerattempt event.
<event-handler name="RegisterAttempt"> <broadcasts> <message name="register" /> </broadcasts> <results> <result name="goodregister" do="Home" /> <result name="badregister" do="Register" /> </results> </event-handler>
This acts much like our LogonAttempt event. The only difference here is the broadcast and result handlers. Also, some sites will probably want to send you to a page when you register. Again, to keep things simple, we will simply send the user to the home page. Now we need to add the register message to the controller section:
<message-listener message="register" function="register" />
Then we follow this up by actually adding the register logic to our controller component:
<cffunction name="register" access="public" returntype="void" output="false" hint="I register a user."> <cfargument name="event" type="ModelGlue.Core.Event" required="true">
<cfset var bean = arguments.event.makeEventBean("model.UserBean") /> <cfset var errors = ""> <cfset errors = bean.validate()> <cfif arguments.event.getValue("password") neq arguments.event.getValue("password2")> <cfset arrayAppend(errors, "Your confirmation password did not match.")> </cfif> <cfif not arrayLen(errors)> <cftry> <cfset userDAO.create(bean)> <!--- send email ---> <cfmail to="#getModelGlue().getConfigSetting("adminemail")#" from="#getModelGlue().getConfigSetting("adminemail")#" subject="PhotoGallery Registration">
New User @ PhotoGallery:
Username: #bean.getusername()# Password: #bean.getpassword()# Name: #bean.getname()# </cfmail>
<!--- log them in ---> <cfset session.loggedIn = true> <cfset session.userBean = bean> <cfset arguments.event.addResult("goodregister")> <cfcatch> <cfset errors = "This username already exists."> <cfset arguments.event.setValue("errors", errors) /> <cfset arguments.event.addResult("badregister")> </cfcatch> </cftry> <cfelse> <cfset arguments.event.setValue("errors", errors) /> <cfset arguments.event.addResult("badregister")> </cfif>
Ok, a lot going on here, so let's cover this line by line. First off, we create an instance of the User bean. One cool thing about Model-Glue is that it has some code to handle automatically wiring event data to a bean. What does that mean in English? My register form had form fields with the same name as the "set" methods in my user bean. So for example, there was a name field and there is a setName method. By using the makeEventBean function, Model-Glue just passes in all event data to methods in the bean that maps up. That's a lot of work saved in one little function.
Next I call my validate method. This simply checks to see if name, username, and password exist. This logic was written in my bean and can be updated later if my User model changes. One thing the bean doesn't do is check to see if my password and password confirmation match, so I do that by hand. Last but not least, I attempt to create the new user account. The userDAO CFC will throw an error if a user with the same name exists, hence the use of try/catch.
Notice the email. As an ego-booster, I want to know whenever someone registers for the application. So I've added a new setting, adminemail, to my ModelGlue.xml file, and I send an email to that account when someone registers. Next I log the user on in the same way my authenticate method does.
Lastly, look at how I handle the various event results. I either return a goodregister or badregister result. The ModelGlue.xml file will use that result to figure out how to route the user.
That's it for this session! As always, you can play with the live application here: http://pg1.camdenfamily.com. You can also download the application by clicking the "Download" link at the end of this article.
- In this entry we focused on the registration logic. We modified the logon form to provide a link to a new register event.
- This register event was a simple form, but it handled errors nicely.
- Where did the errors come from? Our controller was updated to handle the registration event, and use the event model to save error information so that our view, the register page, could also pick up on it.
- We also added a simple configuration setting, adminemail, to the ModelGlue.xml file. This wasn't displayed, but can be seen in the zip attached to this entry.