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.

view plain print about
1<event-handler name="contact.form">
2    <broadcasts>
3     <message name="needContactForm" />
4    <message name="needContactTypes" />
5    </broadcasts>
6    <views>
7        &lt;include name="body" template="frmContact.cfm">

8        <value name="whichMenuIsCurrent" value="contact.form" />
9        &lt;/include>
10    </views>
11    <results>
12        <result do="view.template" />
13    </results>
14</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:

view plain print about
1<li><a href="#myself#contact.view" <cfif whichMenuIsCurrent IS "contact.view">id="current"</cfif>>Contact</a></li>

After:

view plain print about
1<li><a href="#myself#contact.form" <cfif whichMenuIsCurrent IS "contact.form">id="current"</cfif>>Contact Form</a></li>

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.

view plain print about
1<cfset myself = viewstate.getValue("myself") />
2<cfset ContactList = viewstate.getValue("ContactList") />
3
4<cfoutput>
5    <cfif ContactList.recordcount IS 0 >
6        -No Saved Contacts-<br />
7    <cfelse>
8        <table>
9            <tr>
10                <th>Name</th>
11                <th>Type</th>
12                <th>&nbsp; </th>
13            </tr>    
14        <cfloop query="ContactList">
15            <tr>
16                <td>#ContactName#</td>
17                <td>#ContactType#</td>
18                <td><a href="#myself#Contact.form&ContactID=#ContactID#">Edit</a> | <a
19
20href="#myself#Contact.Remove&ContactID=#ContactID#">
Remove</a></td>
21            </tr>
22        </cfloop>
23        </table>    
24    </cfif>
25</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.

view plain print about
1<cffunction name="getContactForm" access="public" returnType="void" output="false">
2        <cfargument name="event" type="any">
3            <cfset var ContactFormBean = getModelGlue().getBean("ContactFormBean") />
4         <cfset var ContactService = getModelGlue().getBean( "ContactService") />
5            <cfset ContactFormBean.setContactID( arguments.event.getValue("ContactID") ) />            
6            <cfset ContactService.loadContact( ContactFormBean ) />
7            <cfset arguments.event.makeEventBean( ContactFormBean ) />
8            <cfset arguments.event.setValue( "ContactFormBean", ContactFormBean ) />
9    </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.

view plain print about
1<cffunction name="loadContact" access="public" returntype="void" output="false">
2        <cfargument name="Contact" type="any" required="true"/>
3        
4            <cfset getContactDAO().read( arguments.Contact ) />
5
6 </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.

view plain print about
1<event-handler name="Contact.Remove">
2    <broadcasts>
3        <message name="needRemoveContact" />
4    </broadcasts>
5    <results>
6        <result do="Contact.List" redirect="true" />
7    </results>
8</event-handler>

Then, at the top of your ModelGlue.xml, register the new message and have it point to a function called removeContact.

view plain print about
1<message-listener message="needRemoveContact" function="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.

view plain print about
1<cffunction name="removeContact" access="public" returntype="void" output="false">
2    <cfargument name="event" type="any" />
3        <cfset var ContactFormBean = getModelGlue().getBean( "ContactFormBean" ) />
4        <cfset var ContactService = getModelGlue().getBean( "ContactService") />
5        <cfset ContactFormBean.setContactID( arguments.event.getValue("ContactID") ) />
6        <cfset ContactService.removeContact( ContactFormBean ) />
7</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.

view plain print about
1<cffunction name="removeContact" access="public" returntype="void" output="false">
2    <cfargument name="Contact" type="any" required="true"/>
3    <cfset getContactDAO().delete( arguments.Contact ) />
4
5</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.

Download Download

There are no comments for this entry.

Add Comment Subscribe to Comments

1/12/08 5:39 PM # Posted By strix

Hi, thanks for the great tutorials. I am new to all this so I might be asking a silly question, but I was wondering why are you using functions that operate solely on their arguments and don't return any values? For example:
<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?


1/12/08 8:25 PM # Posted By Dan Wilson

strix,

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


8/20/08 4:10 PM # Posted By Fernando Lopez

About to finish in the series and learning a lot along the way.

I have a question.
On the function "getContactForm" presented on this page I can see that we are loading values from the DB using this line <cfset ContactService.loadContact( ContactFormBean ) /> right after that we are making a call to event.makeEventBean(ContactFormBean). From what I understand the makeEventBean() will grab variables from the "event" and run the setters for those variables in the ContactFormBean. I see it as a quick way to "load" what's on the FORM scope (through the MG event) and fill up the bean.
So if my assumptions are correct and that's what makeEventBean actually does, then why are we calling it right after loading data from the DB?

I would think that the function would work the same if we made some changes
---Original----
<cffunction name="getContactForm" access="public" returnType="void" output="false">
<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>

-----New-----
<cffunction name="getContactForm" access="public" returnType="void" output="false">
<cfargument name="event" type="any">
<cfset var ContactFormBean = getModelGlue().getBean("ContactFormBean") />
<cfset var ContactService = getModelGlue().getBean( "ContactService") />
<cfset arguments.event.makeEventBean( ContactFormBean ) />
<cfset ContactService.loadContact( ContactFormBean ) />
<cfset arguments.event.setValue( "ContactFormBean", ContactFormBean ) />
</cffunction>

Basically getting rid of the line that gets the ContactID value from the event and replace it with the arguments.event.makeEventBean. This will happen before the ContactService.loadContact() call and will effectively load the ContactID in the contactFormBean.

Are these assumptions correct?

Fernando


8/20/08 4:21 PM # Posted By Dan Wilson

Hi Fernando,

Thanks for dropping in again. I see where you are going with this and you are keen to ask the question. However, there is a difference between the Original and the New version in your comment.

The purpose of the Original code is to load the object from the database, then add in any passed form variables. So if I have an existing contact, and I wish to edit one of the fields, what would happen is, the contactbean.contactID is set, then the object is loaded from the database. THEN any matching form elements are set into the object. At this point, the contactbean now matches the form.

However, in your 'new' example, something is lost.
In your code, you get a contactbean, load the form variables, THEN load the record from the database. As you can probably see, any changes to the object that come from the form, are overwritten when the object is loaded from the database. So if this process happens after the user tried to change the name of a contact, that new name will be overwritten when you load the object from the DB.

Do you see the subtle, but important difference?

DW


8/20/08 4:33 PM # Posted By Fernando Lopez

Something else I found.

On the post you mention "ContactService.deleteContact()" but in the code the ContactService has a removeContact(). Nothing major but something that may confuse people.


8/20/08 4:57 PM # Posted By Fernando Lopez

AHA!! subtle but very important distinction. It seemed OK to me but I realized my mistake after reading what you said. It all became very clear when I changed my validation rules a bit and had to return to the same form. Since on my "new" code the last step was to load from the DB I was effectively clearing any values entered on the form and returning to the form showing nothing.

I changed things the way they were and now after a 'failure' on the validation I can see the original values.

Thanks much for taking the time Dan. Learning a lot from this series.


Add Comment Subscribe to Comments