Mach-ii for newbies - part2
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.
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">
<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:
In very
short this is it! Looking back it was not very hard was it? |