Advice on handling form submission

Hey all!

I’ve finally gotten a chance to really start getting into ColdBox.

I’m just starting off simple, getting something to work and then progressively refactoring as I build it out.

During the dark ages of my non-ColdBox ways :slight_smile: there’s the technique I use with update forms:

  • Page.cfm would do something like a action=“HandleUpdate.cfm”
  • HandleUpdate.cfm would then process the form, and cflocation back to the Page.cfm

The benefit is that if the user hits reload, doesn’t resend the form vars, it simply reloads the page. Versus Page.cfm doing a action=“Page.cfm” and handling the update directly, if the user hits reload at that point the browser will go “do you want to resend the form data?”

With my little ColdBox app that I’m building, I’ve got a simple List/Add/Edit CRUD going on, and it works fine. But because the browser sees that its all handled by index.cfm, after the user does an update, if they hit a browser reload it’ll resend the form vars (and cause another update to occur).

Is there a way to accomplish the technique used in my old school ways?

Thx!

aceSmall.jpg

CFLEXLogo_44h.jpg

DopeJamLogo_44h.jpg

Tariq,

Here is a bit of pseudo code from the controller…

setView("fooform"); // form fields var obj = getService().getMyFoo();

getPlugin(“beanFactory”).populate(obj);

if ( getService().saveFoo(obj) ) {
getPlugin(“messagebox”).setMessage(“info”,“Foo was successfully added”);
setNextEvent(“handler.add”);
} else {
getPlugin(“messagebox”).setMessage(“error”,“Errors were detected”);
setNextEvent(“handler.add”);
}

HTH

The key piece of Matt’s example is setNextEvent(). Use that method after you have processed your form submission. This will give you the browser behavior you’re looking for as far as not resending the form when a user refreshes the page.

My forms usually consist of 3 handlers. So, if it’s a login form, my events would be something like: user.vuLogin, user.doLogin, user.vuLoginComplete. The last step of user.doLogin would be setNetEvent(‘user.vuLoginComplete’). ColdBox does everything else for you.

-Jonathan

setNextEvent(‘user.vuLoginComplete’) …sorry for the typo

Thanks Matt and Jonathan - that totally worked.

In fact, it ended up simplifying my code!

aceSmall.jpg

CFLEXLogo_44h.jpg

DopeJamLogo_44h.jpg

For error handling, I would suggest re-rendering the “add” action (the form) upon an error, this way, you can render the form with populated values.

For example:

var obj = getService().getMyFoo();

getPlugin(“beanFactory”).
populate(obj);

if ( getService().saveFoo(obj) ) {
getPlugin(“messagebox”).setMessage(“info”,“Foo was successfully added”);
setNextEvent(“handler.add”);
} else {
getPlugin(“messagebox”).setMessage(“error”,“Errors were detected”);
//dont redirect to new view - render the form
setView(‘fooform’);
}

If there is a more accepted approach to accomplishing this - rendering the same form with prepopulated values than I would love to hear it.

This approach is what I use for my Rails apps and it works very cleanly.

/Cody

There might be another option, too. Let's look at a small form, like
a login. Say the fields are 'email' and 'password'.

For clarity, we'll call the events user.showLogin and user.doLogin.

In handler user.showLogin:

<cffunction name="showLogin" etc... >
  <cfargument name="Event" type="coldbox.system.beans.requestContext" /

  <cfset var rc = Event.getCollection() />

  <--- Default values to empty --->
  <cfset event.paramValue('email', '') />
  <cfset event.paramValue('password', '') />

  <cfset event.setView('security/login) />
</cffunction>

In the handler user.doLogin:

<cffunction name="doLogin" etc... >
  <cfargument name="Event" type="coldbox.system.beans.requestContext" /

  <cfset var rc = Event.getCollection() />

  <cfif not yourValidateFunction(rc.email, rc.password)>
    <!--- NOT validated --->
    <cfset setNextEvent(event='user/showLogin',
persist='email,password') />
  </cif>

  <!--- If you're here, do whatever you do after successful login --->
</cffunction>

By doing the paramValue in the showLogin event, you make sure the view
has the values it needs.
By doing the persist='email,password' in the setNextEvent on
validation failure, the showLogin event will have what it needs and
not use the defaults. You can get creative with the persist list,
too, by utilizing rc.fieldnames (assuming you want to couple up with
relying on FORM variables, not my first suggestion. I suggest this
because a bigger form will obviously have more than two fields you
need to work with.

Just an idea, and how I tend to do it most of the time.

- Will B.

(Disclaimer: I have yet to build a full CB application, but I am doing research. I have extensive Rails experience, so when I come into CB I try to carry over my Rails approaches, where appropriate).

I thought about the persist approach but yeah, it seems to break down for any non-trivial number of fields. Since a form and its fields are generally a reflection of an object, I usually do something like this: (pseudo-code):

//action to render the form (CB handler)
var user = new User();

//HTML in the form
input type=“text” name=“name” value="#user.getName()#"

//then the handler for the form submit
var user = new User();
user.populate(); // populate from form scope, rc, whatever
if(user.save()) {
//great … redirect
} else {
//errors
//render the same form as above…
}

Thus, upon re-rendering the form the values are pre-populated because they are encapsulated inside the user object and are then put in the form via the same “getName()” method.

For small forms like a login box, yeah, using an object to encapsulate might be over kill, but for forms with 5-6+ fields I usually find this approach more cleaner - and allows me to not necessarily have to differentiate between the “add” and the “edit” form.

/Cody

I’m using Transfer and typically add a method to my transfer object’s decorator to handle validation.

The method returns a structure with 3 keys:

  • success - boolean
  • errormsg - single message
  • errors - structure (using the field’s @id as the key) of errors
    I then persist the transfer object and errors back to the originating form.

-Dutch

The only real problem with persisting a table-based object is that a
form, unfortunately, doesn't always reflect one-to-one with the
table. For a form that fits that bill, then, yeah, absolutely.
Really, it would require predefining your all your form's values at
some point, so you could param the values in the show event and use
the same list in the persist="#myFieldList#". But that kinda requires
a lot of work. There is the option to do runEvent('user.showLogin"),
which would set a view, but when the runEvent() returns to the
midpoint of your doLogin handler, you'll end up overwriting the view
with whatever you want, or the same thing. Bear in mind, too, that in
a handler, not all the functions have to be handlers. You *can*
create helper functions for shared functionality between multiple
events. The handler might not be the best place for that, but you get
the idea.

- WB

Also consider that you not necessarily need to redirect the user. I do this when there are problems on form submissions and I want them to fix the errors.

Let’s say the form display event is called: addForm()
This event submits to the event: processForm()

then on processForm I could do this:

function processForm(event){

…logic here

if( validation ){

  • set a message that data was bad, mark or set on request collection anything you like
  • Instead of setNextEvent(), do an event call:
    addForm(event);
    return;
    }
    else{
  • process data, do a save or whatever
  • Then set a message box and relocate via setNextEvent() to wherever you want. Data success.
    }
    }

What you do, is that in the submit, you validate check and if error, then you just run the form event and finalize execution for that event. The form get’s presented again and they can see all the errors and then submit back.

ANyways, that is my .02 cents, there are SOOOO many ways.

Luis, would calling the event also skip over a second hit to the
main.OnRequestStart() method?

it does as you are just calling a method internally

"Instead of setNextEvent(), do an event call:
     addForm(event);
     return; "

Im sorry how would you do an event call? Bit confused at that bit..

What I usually do is if I submit a form from my page login....I have a
handler called check login, which does this..

<cfif validation bad>
<render message box plug in.....with message saying there is an error>
<send user back to the login page, persist form variables>
<cfif validation good>
<send user to home page>
</cfif>

Im unsure of how to present an error to the user back on the login
page with using setNextEvent here, as youh have suggested

Thanks