So you want to create a ModelGlue:Unity application? ( Part 9 )
In our last segment we built the functionality to store our contacts to the database. Now we need a way to edit the information because after all, friends can become enemies, and enemies can become co-workers. We have a good structure in place and these changes will be simple.
Simple, that is, once we take care of a bit of housekeeping....
I just realized our event-handler for our contact form is under Contact.View. This seems a little silly, doesn't it? Even if you don't think so, it annoys me. So lets sort that really quick. Changing the form event to Contact.Form makes much more sense. We begin the change in the ModelGlue.xml file. Locate the Contact.View event-handler and rename it to Contact.Form. Then, in the same xml tagset, change the value of whichMenuIsCurrent to match.
<broadcasts>
<message name="needContactForm" />
<message name="needContactTypes" />
</broadcasts>
<views>
<include name="body" template="frmContact.cfm">
<value name="whichMenuIsCurrent" value="contact.form" />
</include>
</views>
<results>
<result do="view.template" />
</results>
</event-handler>
Note: if you are pasting from the above code sample, remember to change the & lt; to a proper < tag.
Lastly, open up the dspTemplate.cfm file located in *ContactManagerMG.views and alter the link to point from Contact.View to Contact.Form and the link text from Contact to Contact Form.
Before:
After:
If your application is set for production mode, you will need to reload the framework to pick up the changes in the XML file.
There, much better. Now back to our regularly scheduled programming. ( pun fully intended ).
Editing and removing contacts are actions that relate to a single contact. We have a list of contacts in our application and it would make sense to put the links for our new functions there.
Open dspContactList.cfm located inside the *ContactManagerMG.views directory. Our two links, Edit and Remove, go inside a new table column. Note we use the 'myself' value for the base of our links because 'myself'' pulls in the scriptname (index.cfm) and the eventValue parameter (event) automatically. Thus, if you wanted your application to live on contacts.cfm and to change the url parameter 'event' to 'ObeyMyCommand' making for a handy http://www.contact-o-matic.com/contacts.cfm?ObeyMyCommand=Contact.List, all of your links ( that use 'myself' ) would instantly reflect the changes and negate several hours of Find-And-Replace work. You can use the time saved to learn jQuery or something.
Here is a look at the dspContactList.cfm page.
<cfset ContactList = viewstate.getValue("ContactList") />
<cfoutput>
<cfif ContactList.recordcount IS 0 >
-No Saved Contacts-<br />
<cfelse>
<table>
<tr>
<th>Name</th>
<th>Type</th>
<th> </th>
</tr>
<cfloop query="ContactList">
<tr>
<td>#ContactName#</td>
<td>#ContactType#</td>
<td><a href="#myself#Contact.form&ContactID=#ContactID#">Edit</a> | <a
href="#myself#Contact.Remove&ContactID=#ContactID#">Remove</a></td>
</tr>
</cfloop>
</table>
</cfif>
</cfoutput>
Now to complete the Contact.Edit function. Up to this stage in our application, the contact form has been used to create contacts only. The new Contact.Form link passes a ContactID for a specific contact. We need to alter our controller function to pull the ContactID from the event scope, fetch the associated record from the database and stuff it inside our form bean.
From the top, we need a reference to the ContactFormBean and to the ContactService. We then set the ContactID value from the event (url) into the ContactFormBean and pass the whole shebang into a service function called loadContact. Finally, we place it back in the event where it will eventually find itself in a form.
<cfargument name="event" type="any">
<cfset var ContactFormBean = getModelGlue().getBean("ContactFormBean") />
<cfset var ContactService = getModelGlue().getBean( "ContactService") />
<cfset ContactFormBean.setContactID( arguments.event.getValue("ContactID") ) />
<cfset ContactService.loadContact( ContactFormBean ) />
<cfset arguments.event.makeEventBean( ContactFormBean ) />
<cfset arguments.event.setValue( "ContactFormBean", ContactFormBean ) />
</cffunction>
Now, open the ContactService located inside *ContactManagerMG.model and add the new loadContact function. This function simply pulls in the ContactDAO, calls the read function and passes in the ContactFormBean.
<cfargument name="Contact" type="any" required="true"/>
<cfset getContactDAO().read( arguments.Contact ) />
</cffunction>
We shouldn't have to alter the ContactDAO as the code has already been written.
If your app needs to be reloaded, then do so now. Try out the new editing capabilities now.
Wasn't that simple? Now we have a go at the remove function.
We added in the link on the dspContactList.cfm page to reference the Contact.Remove application event. Open up the ModelGlue.xml once again and add a new event-handler with the name of Contact.Remove. This event-handler will simply broadcast a message called needContactRemove and then use a result tag to direct the application flow back to Contact.List.
<broadcasts>
<message name="needRemoveContact" />
</broadcasts>
<results>
<result do="Contact.List" redirect="true" />
</results>
</event-handler>
Then, at the top of your ModelGlue.xml, register the new message and have it point to a function called removeContact.
Once complete, open up the Controller.cfc located at *ContactManagerMG.controller and add the removeContact function. This function will pull a new ContactFormBean from ColdSpring, set the ContactID from the passed ContactID in the event scope, and pass it to the ContactService.deleteContact() function.
<cfargument name="event" type="any" />
<cfset var ContactFormBean = getModelGlue().getBean( "ContactFormBean" ) />
<cfset var ContactService = getModelGlue().getBean( "ContactService") />
<cfset ContactFormBean.setContactID( arguments.event.getValue("ContactID") ) />
<cfset ContactService.removeContact( ContactFormBean ) />
</cffunction>
Finally, in ContactService, add the function for removeContact. This function takes the Contact component as an argument, gets a reference to the ContactDAO and calls the delete function with the Contact component as the sole argument.
<cfargument name="Contact" type="any" required="true"/>
<cfset getContactDAO().delete( arguments.Contact ) />
</cffunction>
If your app needs to be reloaded, then do so now.. Click the remove link next to any contact in the list.
Bye Bye Contact!
Wow, that was pretty simple. We are reusing a lot of code and this means less typing. More importantly than economy of typing, code that has previously been written and tested can be used again without modification.
The completed code up to and including this section is attached below. Look for the 'download' link.







Locate the Contact.View event-handler and rename it to Contact.View.
I think you mean:
Locate the Contact.View event-handler and rename it to Contact.Form.
dw
Thanks
Inside the ContactDao, in the read function there is a query that pulls the contact record out of the database. Immediately following the query are three lines that set the value from the query into the ContactFormBean. See below:
<cfset arguments.Contact.setContactID( ReadQuery.ContactID ) />
<cfset arguments.Contact.setContactName( ReadQuery.ContactName ) />
<cfset arguments.Contact.setContactTypeID( ReadQuery.ContactTypeID ) /
When the function ends, the bean has been populated and processing continues .
Was that helpful?
DW
and dump contactFormBean.getMemento() I get contactID but have empty strings for contactName and ContactTypeID. For some reason I want to add a new listener contact.edit that would make sure that contactFormBean is being loaded from DB. Would this be a screwed up approach? I thank you for doing this tutorial, it has been very helpful.
Look in the controller.cfc at the getContactForm.
<cfset var ContactFormBean = getModelGlue().getBean("ContactFormBean") />
<cfset var ContactService = getModelGlue().getBean( "ContactService") />
<cfset ContactFormBean.setContactID( arguments.event.getValue("ContactID") ) />
<cfset ContactService.loadContact( ContactFormBean ) />
<cfset arguments.event.makeEventBean( ContactFormBean ) />
<cfset arguments.event.setValue( "ContactFormBean", ContactFormBean ) />
You can see we pull in the ContactFormBean and the ContactService. Then we set the value of ContactID from the event into the ContactFormBean. Following, we pass the ContactFormBean to the ContactService.loadContact() function and get back a populated bean.
If you are getting the contactID in your memento dump, then that means the ContactID is coming though the event just fine. You may want to have a look at the loadContact method in your service.
The source code is attached to this blogpost. You can get it by clicking the download link.
Hope this helps,
Dan Wilson
Mazda wheels - http://www.automotivemazparts.com/mazda-wheels/
<cffunction name="loadContact" access="public" returntype="void" output="false">
<cfargument name="contact" type="any" required="true"/>
<cfset getContactDAO().read( arguments.contact ) />
</cffunction>
Does it offer some advantage over a code simply returning object both from the contactDAO.read() and contactService.loadContact() method?
For example if I implemented some views in Flex, methods in the service component returning values could be easily consumed remotely:
<cffunction name="loadContact" access="remote" returntype="any" output="false">
<cfargument name="contact" type="any" required="true"/>
<cfreturn getContactDAO().read( arguments.contact ) />
</cffunction>
Just I'm thinking about integrating MG with Flex and it looks that the service component could also serve remote requests. Or am I missing the point here?
You are on the right track. The ContactOMatic is an academically simple application with emphasis on using ModelGlue and ColdSpring effectively.
If I was building the model/service layer for a real world application, there are a number of things I might do differently. For example, if I was going to expose part of my model remotely, I would probably not use the UserService as you see it in code. I would have a proper remote service. (Check into the ColdSpring remote service factory. )
Of course, a remote service can call other services/model objects as is appropriate for the transaction. The methods in those objects could operate on the reference, or return it, as long as the value was in scope for the remote function to serialize the data and return it over the wire.
Thanks for bringing this point up. I bet it will help someone else.
DW