Adding Todo Lists - ColdBox Series Part 8a

So we've set up our Coldbox Application, we've built our model, now we need to build some front-end pieces and handlers to work with our model through the service layer.

Today we will begin by creating a form and some handlers. In the next few posts we will look at creating the service layer, form validation, and persistence.

Creating our "Add List" form

The first thing we are going to do is give our application the ability to add new To-Do Lists. The first thing we need is a form for our users to add forms.

As you may recall, our lists have 3 fields. List name, list priority, and list id. We will manage the listID later.

So let's build a simple "Add List" form. In our /views directory, create a file called listForm.cfm and enter this code into it.


<!--- Putting it in a padded div so that it doesn't hug the sides of the browser --->
<div style="padding: 20px;">
    <cfoutput>
        <!--- Start with a normal form, except for that xehDoit. What's that? --->
        <form name="listForm" action="?event=#event.getValue('xehDoit')#" method="post">
            <!--- Put down a form field for the name, note the value, we'll look at that in a bit --->
            <label for="name">
                List Name : <input type="text" name="name" maxlength="50" size="40" value="#event.getValue('name','')#" />
            </label><br />
        
            <!--- same as above --->
            <label for="priorirty">
                Priority :
                <select name="priority">
                    <option value="" disabled="true" selected="true"><--- Choose ----></option>
                    <option value="1" <cfif event.getValue('priority', '') eq 1>selected="true"</cfif>>High</option>
                    <option value="2" <cfif event.getValue('priority', '') eq 2>selected="true"</cfif>>Medium</option>
                    <option value="3" <cfif event.getValue('priority', '') eq 3>selected="true"</cfif>>Low</option>
                </select>
            </label><br />
        
            <!--- Every form needs a submit button, right? --->
            <input type="submit" value="#event.getValue('btn')#" />
        </form>
    </cfoutput>
    </div>

I am not going to explain everything that is going on here. It is a simple web form. One text field, one select box and one submit button.

There might be a few things here that seem odd. Let's look at them.

  1. In the form tag's action attribute we have ?event=#event.getValue('xehDoit')#. This is an exit handler. Instead of hard coding the event that this form will post to, we enter a dynamic variable that we will set into the event object in our event handler that displays this view. More on this shortly. But this allows us to dynamically change where this form posts to without needing to change this form. It will, someday, be used to help make automatically mapping an application easier.
  2. In the "value" attribute of the "name" input field, and in the cfif statements of the select box, you see a few event.getValue() calls. These method calls are used to retrieve data from the event object that was passed into the view. We'll look at how those values get into the event object shortly.
  3. In a couple of the event.getValue() calls you will see that a second argument is being passed in. This is a default value. If the value is missing, as it will be the first time we call the form, then the default value is used.
  4. There is another event.getValue() in the value attribute for the submit button. We'll look at that shortly too. It is for the button label text.

We have our view, how are we going to get it displayed on our page?

Announcing an Event in ColdBox

To announce an event in ColdBox, we need to pass the event name into the controller. This can be done on the URL string, or in a posted form. We'll do it on the URL string. So let's type in:


    http://todolists:81/?event=list.addList

Then hit enter. NOTE: Remember that I set up a local domain in my host file for http://todoslists/ and I am running on port 81. You may need to modify the URL string to match your settings. If everything is default, you will probably have something like:


    http://localhost:8500/todolists/?event=list.addList

You should get an error that reads something like:


    The event handler: list.addList is not valid registered event.

We got this error because we have not set up the event handler for "list", nor the event handler method "addList()". So let's do that.

In your /handlers directory, create a file called list.cfc. This CFC is a normal CFC except that it needs to extend coldbox.system.eventhandler.


    <cfcomponent display="List Handlers" output="false" extends="coldbox.system.eventhandler">

    </cfcomponent>

You have now created a ColdBox event handler CFC. Well done.

Now we need to add a method that will handler the event "addList". We do this by creating a method by the same name. We need that event handler method to insert values into the event object for the exit handler(xehDoit) and the text for the submit button(btn), and we need it to show the view listForm.cfm.


    <cffunction name="addList" access="public" returntype="void" output="false">
        <cfargument name="Event" required="true" type="coldbox.system.beans.requestcontext" />
        
        <cfset event.setValue('xehDoit', 'list.doAddList') />
        <cfset event.setValue('btn', 'Add')>
        <cfset event.setView('listForm') />
    </cffunction>

Here we have created a event handling method. The ColdBox controller automatically passes in the event object (coldbox.system.beans.requestcontext), so all we need to do is set up our handler method to receive this. This is required for EVERY event handler. It contains essential methods for continuing the lifecycle of the request.

Next the method inserts the value 'list.doAddList' into a property inside of the event object called "xehDoit". You may remember that this is the value we are inserting into the "action" attribute of our form element. So this is the name of the event handler that will handle our form post.

Then it inserts the value "Add" into the property "btn". This will be used as the text of the button.

Finally, the event is told to display the "listForm" view. Note the lack of .cfm on the view name.

Now save and refresh the page where we entered the URL:


    http://todolists:81/?event=list.addList

We should see our form, we should see the button properly labeled "Add" and, if we view source, we should see action="?event=list.doAddList".

So let's click "Add" and see what happens.


An invalid event has been detected: [handlers.list] The action requested: [doAddList] does not exists in the specified handler.

Again, we have called a handler that does not exist. This time the error is different. This is because the list.cfc file exists now, there is just no doAddList() method inside.

Let's add it. Under our addList() method in /handlers/list.cfc add:


    <cffunction name="doAddList" access="public" returntype="void" output="false">
        <cfargument name="Event" required="true" type="coldbox.system.beans.requestcontext" />
        <cfdump var="#event.getCollection()#">
        <cfabort>
    </cffunction>

Just like our previous event handler method, this one takes the Event object as an argument. One thing to point out here is that with most (if not all) MVC frameworks, you almost never access the FORM or URL scopes directly. These scopes are combined by the controller and inserted into the Event object. To demonstrate this, we are going to CFDUMP the method event.getCollection(). This is a method from the ColdBox requestContext object that will get the fields that were passed in as URL or FORM scoped variables.

Now if we submit our form we should see something like:

Notice that the "event" variable from the URL scope and the "name" and "priority" variables from the form scope have all been combined and placed into the request collection inside of the event object.

Until next time

Well, that's enough for one day. We have created a form and two handlers. One handler is displaying the form, and one handler is receiving the user input from the form. Next time, we will work on how to get that second handler to talk to the service layer (that we will create) to validate and persist the data from the form.

I have posted the code for this project. You can download it with the "Download" link below.

Comments
Luis Majano's Gravatar Here are some recommendations for you.

Using event.getValue() gets really tedious. Only use it if you need to have defaults for variables, if not, there is already an "rc" scope built in to layouts and views. You can just access it: #rc.xehMyLink# #rc.myname# no need to do tedious event.getValue()

On you handlers, I also recommend creating the virtual scope:
<cfset var rc = event.getCollection()>
and just use the rc structure as a scope. Much easier and less typing :)

For links, I recommend using the SES interceptor instead of old fashioned ?event=this. You don't even need a rewrite engine for this to work. Using ColdBox's Courses gives you incredible URL flexibility, obfuscation and ease of use.

index.cfm/list/doAddList
or alias it
index.cfm/addlist

Try it out.

Normal ?event= links should not be used, but SES is favored. Maybe in the future, you will only be able to do SES Links
# Posted By Luis Majano | 11/4/08 5:05 PM
Jason Dean's Gravatar @Luis,

Thanks for taking the time.

Great advice about the rc scope. I recall, now that you mention it, that you discussed the rc scope at cf.Objective(). I will definitely make that change going forward.

As for the SES URLs, I actually had that planned for a future post. I wanted to show how it was done without, then demonstrate how to change it. That post will probably be coming up in the next week or so.

Thanks again.
# Posted By Jason Dean | 11/4/08 6:18 PM
Paul Baylis's Gravatar When I run this: http://localhost:8500/china-buy/?event=list.addLis... (set up exactly as per your example),
I get the following error:

EVENT argument passed to the addList function is not of type coldbox.system.beans.requestcontext.

Any ideas why?
# Posted By Paul Baylis | 3/11/10 7:28 PM
Jason Dean's Gravatar @paul,

do you have a mapping for /coldbox or do you have the coldbox directory at your webroot?

If the coldbox directory is in your /china-buy folder, then the actual path to the component would be china-buy.coldbox.system.beans.requestcontext, which, of course, you don't want.

Make sure you have a mapping called /coldbox that points to wherever your coldbox directory is, or ensure that the coldbox dir is in the webroot.

Hope this help.
# Posted By Jason Dean | 3/11/10 8:07 PM
Luis Majano's Gravatar This is a change from coldbox 2 to coldbox 3.0, pathing. Just make all the types for event = any
# Posted By Luis Majano | 3/11/10 9:09 PM
Chip's Gravatar Just a quick reminder - for anyone doing this on a case sensitive system (i.e. Linux) extends="coldbox.system.eventhandler" becomes extends="coldbox.system.EventHandler"
# Posted By Chip | 5/8/11 5:42 PM
BlogCFC was created by Raymond Camden. This blog is running version 5.9.1. Contact Blog Owner