Welcome to the eight installation to my tutorial on building a Model-Glue application. In the last entry, I talked about the Gallery object. Because the entry ran a bit long, I didn't get to cover everything we needed, so before moving on to photo uploading (which is the whole point of the application), I want to wrap up some final Gallery actions, specifically deleting and updating the gallery data.

Let's handle deleting since it's actually the easiest of the actions. If you remember, the Galleries link showed you both a list of your galleries as well as a form to add a new gallery. Let's modify the display to allow for deleting and editing. I've taken the code in dspGalleries.cfm, and added a few new links. Here is the relevant section:

<p> <table> <cfoutput query="galleries"> <tr <cfif currentRow mod 2>bgcolor="ghostwhite"</cfif>> <td width="100%"><a href="#viewState.getValue("myself")#viewgallery&id=#id#">#name#</a></td> <td> <nobr> <cfif isPublic>Public<cfelseif isPassword>Password Req.<cfelse>Private</cfif> </nobr> </td> <td><a href="#viewState.getValue("myself")#editgallery&id=#id#">[Edit]</a></td> <td><a href="#viewState.getValue("myself")#deletegallery&id=#id#">[Delete]</a></td> </tr> </cfoutput> </table> </p>

As you can see, I've converted the simple list to a table. I've added links for the edit and delete events. I also added a simple message that shows the security settings for each gallery. As I said, I wanted to cover deleting first, so let's look at the deletegallery event I'm adding to the XML file.

<event-handler name="DeleteGallery"> <broadcasts> <message name="getAuthenticated" /> <message name="deleteGallery" /> </broadcasts> <results> <result name="notAuthenticated" do="Logon" redirect="yes" /> <result do="Galleries" /> </results> </event-handler>

Nothing terrible special here. It is basically a clone of the AddGallery method. Do note I'm using redirect now. I talked a bit about why in an entry earlier this week. By now you know the next step is to add the call in the controller block:

<message-listener message="deleteGallery" function="deleteGallery" />

And then add the relevant code in my controller:

<cffunction name="deleteGallery" access="public" returntype="void" output="false" hint="I delete a gallery."> <cfargument name="event" type="ModelGlue.Core.Event" required="true"> <cfset var galleryToKill = arguments.event.getValue("id")> <cfset var myUsername = session.userbean.getUsername()> <cfset var gallery = variables.galleryDAO.read(galleryToKill)>

<cfif gallery.getUsername() eq myUsername> <cfset variables.galleryDAO.delete(galleryToKill)> </cfif> </cffunction>

So there is something a bit interesting going on here. I'm passing the ID of the gallery to delete, but I don't want to just delete the gallery without checking first to make sure it's one of your galleries. This is done by simply comparing your username to the username associated with gallery object. If they match, I go ahead with the delete.

Now for editing. As you saw in the last entry, the gallery display page has a form to add a new gallery. I want to tweak this so it also works for editing an existing entry. The first thing I think I should do is see if I can pass the gallery to the view. So let's start as we always do - with the XML file:

<event-handler name="EditGallery"> <broadcasts> <message name="getAuthenticated" /> <message name="editGallery" /> </broadcasts> <results> <result name="notAuthenticated" do="Logon" redirect="yes" /> <result do="Galleries" /> </results> </event-handler>

Once again, this is nothing more than a copy of the add and delete gallery events. I used this listener in the controller:

<message-listener message="editGallery" function="editGallery" />

Now let's write the controller code to get the gallery and put it in the view state:

<cffunction name="editGallery" access="public" returntype="void" output="false" hint="I edit a gallery."> <cfargument name="event" type="ModelGlue.Core.Event" required="true"> <cfset var galleryToEdit = arguments.event.getValue("id")> <cfset var myUsername = session.userbean.getUsername()> <cfset var gallery = variables.galleryDAO.read(galleryToEdit)>

<cfif gallery.getUsername() eq myUsername> <cfset arguments.event.setValue("gallery", gallery)> <cfif not arguments.event.valueExists("name")> <cfset arguments.event.setValue("name", gallery.getName())> </cfif> <cfif not arguments.event.valueExists("access")> <cfif gallery.getIsPublic()> <cfset arguments.event.setValue("access", "public")> <cfelseif gallery.getisPassword()> <cfset arguments.event.setValue("access", "password")> <cfset arguments.event.setValue("password", gallery.getPassword())> <cfelse> <cfset arguments.event.setValue("access", "private")> </cfif> </cfif> </cfif> </cffunction>

This is like the delete event, only this time we set values from the gallery into the view state. I did something a bit different with my view state. Instead of just setting the values, I'm checking to see if the value exists already using valueExists(). I did this since I didn't want to overwrite any possible changes that may occur during editing. Also note that I have to translate the security settings into a generic access setting for the form. I also pass the entire gallery bean into the view state. Now I need to modify the gallery view to handle this possible change. I've modified dspGalleries.cfm with these changes. (Again, I'm not going to show the entire file, but rather what has been updated.) First I see if a gallery exists in the view state:

<!--- editing? ---> <cfset editingGallery = viewState.getValue("gallery")>

Remember that viewState.getValue() will return a simple empty string is nothing is there. I then modified the text above the form to check for this:

<cfif isSimpleValue(editingGallery)>

<cfoutput> <p> Please use the form below to create a new gallery. </p> </cfoutput>

<cfelse>

<cfoutput> <p> Please use the form below to edit the gallery: #editingGallery.getName()# </p> </cfoutput>

</cfif>

So far so good. I then want the form action to change based on whether or not this is a new gallery:

<cfif not isSimpleValue(editingGallery)> <form action="#viewstate.getValue("myself")#updategallery" method="post"> <input type="hidden" name="id" value="#editingGallery.getID()#"> <cfelse> <form action="#viewstate.getValue("myself")#addgallery" method="post"> </cfif>

As you can see, in the case where I'm editing, I pass along the ID of the gallery. (For those who have access to the last version of the code, there was a bug in galleryDAO.cfc where the ID was not returned. That is fixed now.) Finally, I want to tweak the submit button a bit depending on the state we are in:

<cfif isSimpleValue(editingGallery)> <input type="submit" name="submit" value="Create Gallery"> <cfelse> <input type="submit" name="submit" value="Update Gallery"> </cfif>

Ok, that's it for the view. If you try this now, you can see the form populating with data from the gallery. As you may have noticed, there is no way to go from editing back to a blank form unless you click on Galleries in the left hand nav. One thing I could possible add later is a Cancel button.

Alright - so now we need to build updateGallery event into our XML file:

<event-handler name="UpdateGallery"> <broadcasts> <message name="getAuthenticated" /> <message name="editGallery" /> <message name="updateGallery" /> </broadcasts> <results> <result name="notAuthenticated" do="Logon" redirect="yes" /> <result do="Galleries" /> </results> </event-handler>

Like the editGallery event, this event broadcasts out a editGallery message. This will be useful in case the user makes an error. It then broadcasts an updateGallery message. I added this to my listeners:

<message-listener message="updateGallery" function="updateGallery" />

And next I need to add the code in the controller:

<cffunction name="updateGallery" access="public" returntype="void" output="false" hint="I update a gallery."> <cfargument name="event" type="ModelGlue.Core.Event" required="true"> <cfset var bean = arguments.event.makeEventBean("model.galleryBean") /> <cfset var errors = "">

<!--- before doing anything, let's check the security ---> <cfset var myUsername = session.userbean.getUsername()> <cfset var gallery = variables.galleryDAO.read(bean.getID())>

<cfif gallery.getUsername() neq myUsername> <cfset arguments.event.forward("Galleries","")> </cfif>

<!--- Translate Access setting ---> <cfset bean.setIsPublic(false)> <cfset bean.setIsPassword(false)> <cfset bean.setIsPrivate(false)> <cfset bean.setUsername(session.userbean.getUsername())>

<cfswitch expression="#event.getValue("access")#">

<cfcase value="public"> <cfset bean.setIsPublic(true)> <cfset bean.setPassword("")> </cfcase>

<cfcase value="private"> <cfset bean.setIsPrivate(true)> <cfset bean.setPassword("")> </cfcase>

<cfcase value="password"> <cfset bean.setIsPassword(true)> </cfcase>

</cfswitch>

<cfset errors = bean.validate()>

<cfif not arrayLen(errors)> <cfset galleryDAO.update(bean)> <!--- clear values ---> <cfset arguments.event.setValue("name", "")> <cfset arguments.event.setValue("access", "")> <cfset arguments.event.setValue("password", "")> <cfelse> <cfset arguments.event.setValue("gallery", gallery)> <cfset arguments.event.setValue("errors", errors)> </cfif> </cffunction>

The first thing you will notice is that this is quite similar to the addGallery code. I'll talk about that a bit more later. Once again I use the very handy makeEventBean function. However, before doing anything with the data, I perform my security check again to ensure that the record I'm editing actually belongs to the current user. Notice how I handle this:

<cfif gallery.getUsername() neq myUsername> <cfset arguments.event.forward("Galleries","")> </cfif>

The forward method is a part of the Event object in Model-Glue. This instantly sends the user to a new event, passing along all state values. You can think of it as a better cflocation call. So the rest of the method is pretty much the same code I covered in the previous entry. You see how I handle translating the access setting to the proper security value. The only real difference is how I store the value:

<cfset galleryDAO.update(bean)>

So I mentioned that the add and update events are pretty similar. That's bad. Bad as in a "disturbance in the Force" bad. I know that I should take these two methods and combine them. The portion that worries me the most if the "Access to Security flag" section. I can see that changing in the future, and if it does, I'm going to need to remember to update it multiple locations. This is where the beauty of MVC (Model, View, Controller) really shines (abstraction in general actually). When I do get around to updating my controller, there is zero work I'll have to do in either my Model or View. I know I'm probably repeating myself, but I can't drive home enough the benefits of code abstraction.

Summary

  • This entry was mostly a followup to the last entry. We finished the work on our "Gallery" model by adding methods to both delete and update gallery objects.
  • I also introduced you to the forward() method of the Event object. As you can see, it was a useful way to quickly abort the update method and send the user away. This is NOT the only way to do it. I could have simply used an event result. I could have simply checked and if the security test failed, don't do anything. As with everything ColdFusion related, there is more than one way to skin the cat.

That's it for today. The next entry will finally get us uploading images. (I swear, really!)

Download attached file.