Mach-ii for newbies - part 2
Mach-ii for newbies ? part2

Mach-ii for newbies - part2

 

Reading and manipulating data

 

Written by Trond Ulseth (www.waterswing.com/blog)

 

 

1. Introduction

 

Hello, and welcome back. This is the follow up to a part 1 (obviously), originally posted at Easycfm.com (http://tutorial345.easycfm.com/). If you did not read that one yet you probably should (or maybe not - what do I know? At any rate, I won't come after you if you don't).

 

A lot of things have happened since I wrote part 1. In the meantime I've been developing on my first real OO (Object Oriented) application. That application was written without using mach-ii or any other framework though.

 

One thing I feel I did not emphasize in part 1 (actually, looking back I see I did not mention it at all) is that the mach-ii framework is "created to help software development and maintenance using an OO style" (quote from mach-ii.info). In other words, to use and leverage the mach-ii framework you should (no - need to) understand OOP.

 

Remember in part 1 I suggested that even though we did not see the payoff of using mach-ii right away "we'll just have to trust that there will be some payoff for the extra work further down the road". After finishing that OO project I can definitely say that there is payback down the road. And looking at the improvements using the mach-ii framework would bring to that project had it been used, I see that the payoff would be doubled (or at least enhanced pretty much).

 

However - for now - OOP will not be the subject of this tutorial series. I've had a lot of positive feedback from the first part, but also some criticism. One of the critics was that I focused very much on the mach-ii.xml configuration file, while going pretty quick through the bean concept. While agreeing that understanding the bean concept is important, a bean is not a mach-ii specific subject, while the mach-ii.xml file is mach-ii specific to the highest degree. So that is the walk I'll walk and talk I'll talk for now.

 

Let me just quickly remind you of the disclaimer from part 1: "I am learning as I'm writing, or writing as I'm learning. I can therefore not give any guaranty that what I am doing is totally best practices, or that I have so much understanding of what I'm doing that I explain it as clear as could be possible. I'll try though to write in an easy to understand and slightly entertaining way."

 

Enough jabbering! Let's dive into the mach-ii waters again.

 

2. A short repetition

 

Let's just quickly go through what we learned/did in the first part.

 

  • In the mach-ii.xml config file we define a default event.
  • The default event calls a view.
  • What view file to display is defined under page-views in the mach-ii.xml file
  • The view file contained a form, which in turn defined a new event when submitted
  • This event populated a bean cfc with the values posted from the form
  • This event also notified a listener, which in turn passed the bean as an argument to a CRUD cfc.

 

Now do you remember the assignment I gave you at the end of part 1?

Without any further ado, here's the solution:

 

<event-handler event="message.create" access="public">

create

                        <event-bean name="message" type="MyGuestbook.model.message.message" /> 

                        <notify listener="messageListener" method="createMessage" />

                        <view-page name="mainTemplate" />

            </event-handler>

 

I'm pretty sure that you had that one figured out yourself.

 

 

 

 

3. Where do we go from here?

 

The obvious first thing to do is to get the submitted messages displayed on our page. So we need to query the database for inserted messages, and find the mach-ii way of getting the output displayed on our display page.

 

In the first part we used a CRUD cfc for putting a message into the database. But I also said that a CRUD should only deal with one record in the database at a time. So for queries retrieving multiple records we need to create another cfc, a so called gateway.

 

We write the following code, and save it as messageGateway.cfc in our model/message/ directory.

 

<cfcomponent displayname="Message Gateway" hint="I am a data gateway to messages">

 

            <cffunction name="init" access="public"             returntype="MyGuestbook.model.message.messageGateway" output="false">

                        <cfargument name="dsn" type="string" required="true" />          

                        <cfset variables.dsn = arguments.dsn />

                        <cfreturn this />

            </cffunction>

           

            <cffunction name="findMessages" access="public" returntype="query" output="false">

                        <cfset var findMessages = 0 />

                        <cfquery name="findMessages" datasource="#variables.dsn#">

                                   SELECT *

                                   FROM guestbook

                                   ORDER BY id

                        </cfquery>

                        <cfreturn findMessages />

            </cffunction>

           

</cfcomponent>

 

So we started out with the familiar part this time. The init method we remember from the CRUD (go back and read part 1 if you're insecure about it - I know I did).  Then the findMessage method is a good ole' SELECT * query.

 

Seeing that the init method here is the same as in the CRUD is a pretty good clue that the gateway also is called from a listener. So now we're going to do the same. In fact we'll do it in the same listener. So let's open up the messageListener.cfc and modify it as so:

 

 

 

<cfcomponent extends="MachII.framework.Listener" displayname="Message Listener" hint="I am the listener for messages">

 

<cffunction name="configure" access="public" returntype="void" output="true" displayname="Listener Constructor"

   hint="I initialize this listener as part of the framework startup.">

<cfscript>                                

var dsn = getAppManager().getPropertyManager().getProperty("dsn");

variables.messageCRUD = CreateObject("component", "MyGuestbook.model.message.messageCRUD").init(dsn, "MyGuestbook");

variables.messageGateway = CreateObject("component", "MyGuestbook.model.message.messageGateway").init(dsn, "MyGuestbook");

 

</cfscript>

</cffunction>

 

<cffunction name="createMessage" access="public" returntype="void" output="false" displayname="Create Message" hint="I cause message to be created from the current event object.">

<cfargument name="event" type="MachII.framework.Event" required="yes" displayname="Event" hint="I am the current event" />

<cfset var message = arguments.event.getArg("message") />

<cfset variables.messageCRUD.create(message) />

</cffunction>

 

<cffunction name="getAllMessages" access="public" returntype="query" output="false" displayname="Get All Messages" hint="I return a query containing all of the messages.">

            <cfreturn variables.messageGateway.findMessages() />

</cffunction>              

           

</cfcomponent>

 

The added code in the listener should be pretty self explanatory.

 

Since this listener already is defined in the mach-ii.xml file we don't have to worry about that, and go directly to calling it with the new method in the showMain event handler

 

<event-handler event="showMain" access="public">

            <notify listener="messageListener" method="getAllMessages" resultKey="request.qry_messages" />

            <view-page name="mainTemplate" />

</event-handler>

 

Now we only got one little thing to do. Open the views/mainTemplate.cfm file and add the following code just above the form for submitting new messages:

 

<cfoutput query="request.qry_messages">

            <hr>

            <strong>From:</strong> <a href="mailto:#email#">#name#</a>

#LSDateFormat(date,"dd mmmm yyyy")#<br>

            <strong>Message:</strong><br>

            #message#<br> 

</cfoutput>

 

That was really all that we needed for displaying the messages on the main page (I'll bet you expected it to be harder).  But now before you start smiling from ear to ear - try to submit another message.

 

I actually saw that one coming even before I tried it. Look at this:

 

<event-handler event="showMain" access="public">

            <notify listener="messageListener" method="getAllMessages" resultKey="request.qry_messages" />

            <view-page name="mainTemplate" />

</event-handler>

 

<event-handler event="message.create" access="public">

            <event-bean name="message" type="MyGuestbook.model.message.message" /> 

            <notify listener="messageListener" method="createMessage" />

            <view-page name="mainTemplate" />

</event-handler>

 

Both of these event handlers display the same view-page, but only the first one call the listener with the method getAllMessages. Fixing the issue at hand would be as easy as to add this same call to the listener to the last event handler. But somehow that feels wrong. I'll investigate the "correct" way of solving this, and get back to you. Should take about a second.

 

Ok - I'm back. Did not take to long did it? Here's the deal. For now we'll add the same call to the second event-handler as well, and then come back to the "correct" way of solving this in part 3, which is planned to deal with filters and plug-ins. So the second event should now look like this:

 

<event-handler event="message.create" access="public">

            <event-bean name="message" type="MyGuestbook.model.message.message" /> 

            <notify listener="messageListener" method="createMessage" />

            <notify listener="messageListener" method="getAllMessages" resultKey="request.qry_messages" />

            <view-page name="mainTemplate" />

</event-handler>

 

4. Deleting data

 

We don't want to have a guestbook without some kind of control. Spammers and other creeps are just too happy to leave their crap around. And we don't want to let them. We could do changes or remove records directly in the db, but that's not very user friendly. And we definitely would not learn much mach-ii from it. So let's start with deleting messages.

 

Seeing how easy it was to display the data, I would guess it's safe to assume that deleting data wont be to hard either. Actually I'm pretty sure that by now you'd be able to figure it out by your self. But we'll quickly go through it anyway.

 

We remember that the CRUD deals with the creation, reading, updating and deletion of one message at a time. So far we've only used the creation part of it. Now we're (obviously) going to use the deletion part. We'll add the following function to our CRUD file:

 

<cffunction name="delete" returntype="void" output="false" hint="CRUD method">

<cfargument name="id" type="numeric" required="yes" displayname="delete" hint="I am the ID of the message

to delete">

            <cfset var messageDelete = 0 />

            <cfquery name="messageDelete" datasource="#variables.dsn#">

                        DELETE FROM guestbook

                        WHERE id = #arguments.id#

            </cfquery>

           

            <cfreturn />

</cffunction>

 

Then we'll add the proper function to the messageListener:

 

<cffunction name="deleteMessage" access="public" returntype="void" output="false" displayname="Delete Message" hint="I cause message to be deleted.">

            <cfargument name="event" type="MachII.framework.Event" required="yes" displayname="Event" hint="I am the current event" />

            <cfset var message = arguments.event.getArg("id") />

            <cfset variables.messageCRUD.delete(id) />

</cffunction>

 

 

Then in our good old trusted mach-ii.xml file we add the following event handler:

 

<event-handler event="message.delete" access="public">

            <notify listener="messageListener" method="deleteMessage" />

            <notify listener="messageListener" method="getAllMessages" resultKey="request.qry_messages" />

            <view-page name="mainTemplate" />

</event-handler>

 

Then all we have to do is to call the message.delete event as well as pass on the id of the message we want to delete by adding a delete link into our mainTemplate.cfm like this:

 

<cfoutput query="request.qry_messages">

            <hr>

            <strong>From:</strong> <a href="">#name#</a> #LSDateFormat(date,"dd mmmm yyyy")#<br>

<strong>Message:</strong><br>

            #message#<br>

            <a href="index.cfm?event=message.delete&id=#id#">Delete</a><br>

</cfoutput>

 

 

Now hit the delete link and see our magic work :)

 

5. Updating data I

 

Now we can't leave two of the CRUD functions stay unused. So when dealing with updating data we'll use both the read and the update functions of the CRUD.

 

What we are going to do is to add a edit link right next to our delete link. The edit link will open a window with a form to edit the message. We'll use the read function to read the message into the form, and the update function to....... you guessed it, update the message.

 

For a little variation the first thing we'll do is to create the new view page for the popup window. Save the following code as updateTemplate.cfm in your views directory.

 

<cfif url.event is "message.edit">

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>

            <head>

            <title>Edit message</title>

            <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">

            </head>

 

            <body>

            <cfoutput query="request.qry_message">

            <form action="index.cfm?event=message.update" method="post" name="updMsgForm">

                          <fieldset>

                                   <legend>Edit message</legend>

                                   Name:<br>

                                   <input name="name" type="text" value="#name#"><br>

                                   Email:<br>

                                   <input name="email" type="text" value="#email#"><br>

                                   Message:<br>

                                   <textarea name="message" cols="40" rows="5" wrap="virtual">#message#</textarea><br>

                                   <input type="submit" name="Submit" value="Update message">

                          </fieldset>

            </form>

            </cfoutput>

            </body>

</html>

<cfelse>

</cfif>

 

 

Don't bother with the cfif/cfelse for now. We'll put them to play later.

 

Now let's get the two missing functions into our CRUD file:

 

<cffunction name="read" returntype="query" output="false" hint="CRUD method">

            <cfargument name="id" type="numeric" required="yes" displayname="read" hint="I am the ID of the message to read">

            <cfset var messageRead = 0 />

            <cfquery name="messageRead" datasource="#variables.dsn#">

                        SELECT *

                        FROM guestbook

                        WHERE id = #arguments.id#

            </cfquery>

           

            <cfreturn messageRead />

</cffunction>

 

<cffunction name="update" returntype="void" output="false" hint="CRUD method">

            <cfargument name="message" type="MyGuestbook.model.message.message" required="yes" displayname="update" hint="I am the message to update" />

            <cfset var messageUpdate = 0 />

            <cfquery name="messageUpdate" datasource="#variables.dsn#">

                        UPDATE guestbook

                        SET name = '#trim(arguments.message.getName())#',

                                   email = '#trim(arguments.message.getEmail())#',

                                   message = '#trim(arguments.message.getMessage())#'

                        WHERE id = #arguments.message.getID()#

            </cfquery>

                       

            <cfreturn />

</cffunction>

 

The first thing we need to do is to get the existing message details into our form. For that to happen we'll make a "Edit" link right next to the "Delete" link in the mainTemplate.cfm file. And since we want to open the edit form in a new window we have to bring in some JavaScript magic as well. So our mainTemplate file should look like this:

 

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>

  <head>

    <title>My Guestbook</title>

    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">

    <script language="JavaScript" type="text/JavaScript">

            <!--

            function openWindow(url,windowName,features) {

              window.open(url,windowName,features);

            }

            //-->

    </script>

  </head>

 

  <body>

    <h2>Welcome to my Guestbook!</h2>

            <cfoutput query="request.qry_messages">

                        <hr>

                        <strong>From:</strong> <a href="mailto:#email#">#name#</a> #LSDateFormat(date,"dd mmmm yyyy")#<br>

                        <strong>Message:</strong><br>

                        #message#<br>

                        <a href="##" onClick="openWindow('index.cfm?event=message.edit&id=#id#','popup','width=400,height=300')">Edit</a> | <a href="index.cfm?event=message.delete&id=#id#">Delete</a><br>

            </cfoutput>

            <form action="index.cfm?event=message.create" method="post" name="msgForm">

              <fieldset>

                        <legend>Add your message</legend>

                        Name:<br>

                        <input name="name" type="text"><br>

                        Email:<br>

                        <input name="email" type="text"><br>

                        Message:<br>

                        <textarea name="message" cols="40" rows="5" wrap="virtual"></textarea><br>

                        <input type="submit" name="Submit" value="Post message">

              </fieldset>

</form>

 

  </body>

</html>

 

 

Now we only need to define the event in the mach-ii.xml file, notify the message listenener asking for a resut-key, and defining the function in the messageListenener.cfc (we are getting good at this).

 

So we add the following event-handler in mach-ii.xml:

 

<event-handler event="message.edit" access="public">

            <notify listener="messageListener" method="getMessage" resultKey="request.qry_message" />

            <view-page name="editTemplate" />

</event-handler>

 

Duh - I almost forgot (not as good as I thought yet) - we need to define the new view as well.

 

<!-- PAGE-VIEWS -->

<page-views>

            <page-view name="mainTemplate" page="/views/mainTemplate.cfm" />

            <page-view name="editTemplate" page="/views/editTemplate.cfm" />

            <page-view name="exception" page="/views/exception.cfm" />

</page-views>

 

We add the following function in the messageListener.cfc:

 

<cffunction name="getMessage" access="public" returntype="query" output="false" displayname="Get a message" hint="I return a query record">

            <cfargument name="event" type="MachII.framework.Event" required="yes" displayname="Event" hint="I am the current event" />

            <cfset var id = arguments.event.getArg("id") />

            <cfreturn variables.messageCRUD.read(id) />

</cffunction>

 

Now clicking the "Edit" link in our main template should open up a window, with our form, pre-filled with the details from the message.

 

6. Updating data II

 

Now we need to configure the right actions to be taken when we submit the form. This process should be very similar to inserting data like we did in part 1 of this tutorial series. We've already added the proper update function to the CRUD file.

 

As we see from the form code - the event we'll call when submitting it will be message.update. So we'll go ahead and define the event-handler in our good ole' mach-ii.xml file like so:

 

<event-handler event="message.update" access="public">

            <event-bean name="message" type="MyGuestbook.model.message.message" />

            <notify listener="messageListener" method="updateMessage" />

            <view-page name="editTemplate" />

</event-handler>

 

Worth to notice here is that we'll reuse the bean from part 1 when we were creating a new message.

 

We won't have to do anything more with the bean, but in the message listener we need to add a new function:

 

<cffunction name="updateMessage" access="public" returntype="void" output="false" displayname="Create Message" hint="I update a message from the current event object.">

            <cfargument name="event" type="MachII.framework.Event" required="yes" displayname="Event" hint="I am the

current event" />

            <cfset var message = arguments.event.getArg("message") />

            <cfset variables.messageCRUD.update(message) />

</cffunction>

 

That's it! We now have a guestbook where we can create, read, update and delete messages (CRUD), as well as read the whole lot of them with the gateway. Smokin'!

 

Just as a little added flavor we're going to put a little JavaScript into play using the after the cfif tag in the editTemplate:

 

<cfelse>

            <script type="text/javascript" language="javascript">

            opener.location.reload();

            window.close();

            </script>

</cfif>

 

This little js reloads the calling page - so that we instantly see the update, and it closes the edit window. Nothing to do with mach-ii really. But I thinks it's sweet, and besides this is my tutorial :)

 

Thank you and good night! This is the end of part 2.

 

 

6. To be continued...

 

If I were you I'd raise up from my chair and getting ready to throw the keyboard at the screen in sheer frustration: "Hey - don't quit on us now. That guestbook is useless. Anyone visiting, can delete and edit messages. That is not a good guestbook Trond."

 

I agree. And that is why I plan for the next part to go into making a administration log in system. We will have to learn how to handle sessions in mach-ii (I've seen the word "session façade" mentioned a lot - at least I think that is the right spelling with the funny ç and all - so I guess maybe that is something we'll stumble across). I also would like to cover filters and plugins. And for an even later tutorial (part 4 or something) I'd like to take a look at the Tartan service layer framework within mach-ii.

What we have learned so far

 

To interact with a data in a db we do the following:

 

  • We define an event (or use the default one)
  • In the mach-ii.xml file we define an event-handler
    • If we want to pass data in to the db (create or update) we define an event bean
    • We notify a listener with a function, with a result key if we are receiving data
    • We set a view-page
  • A bean holds a single instance of a object, it contains of a init() method and setters and getters for each data detail
  • A listener is the "glue" between our view and our model. It contains methods that call our gateway and DAO's
  • The gateway and DAO are the cfc's handling the database interaction (our queries)
  • View-pages are .cfm files providing the interface (what we see) - and are defined in the mach-ii.xml file under page-views (yes the opposite order of the words are correct)

 

In very short this is it! Looking back it was not very hard was it?

 



All ColdFusion Tutorials By Author: Trond Ulseth
  • Mach-ii for newbies - part 2
    Continuing where part 1 left of - this one deals with reading, updating and deleting database records the mach-ii way.
    Author: Trond Ulseth
    Views: 20,128
    Posted Date: Thursday, March 31, 2005
  • Mach-ii for newbies - part1 Getting started
    The first in a series of tutorials explaining how to develop applications with the mach-ii framework. Written by a mach-ii newbie himself, this tutorials take on kind of a collaborative learning aproach.
    Author: Trond Ulseth
    Views: 43,979
    Posted Date: Saturday, January 15, 2005