Reminder about forms and ColdFusion 8 Ajax Containers

While this is covered in the docs, it can be a bit surprising. When working with ColdFusion 8 Ajax-based containers (cfdiv, cflayoutarea, cfpod, and cfwindow), any form inside the area will be posted to the area itself. What that means is - the entire page won't reload, just the container. However - this is only true for cfform, not form. Consider this code example:

<cfoutput>loaded #timeFormat(now(), "long")#</cfoutput>
<p>
<hr>
<p>
<cflayout type="tab">

   <cflayoutarea title="Tab 1">
   <form>
   <input type="text" name="name">
   <input type="submit" name="submit">
   </form>
   <cfdump var="#form#">
   </cflayoutarea>
   
   <cflayoutarea title="Tab 2">
   <cfform>
   <cfinput type="text" name="name">
   <cfinput type="submit" name="submit">
   </cfform>
   <cfdump var="#form#">
   </cflayoutarea>
   
</cflayout>

I have a simple page with a time stamp on top. Then I have 2 tabs. The first tab has a FORM based form, and the second tab has a CFFORM based form. If you post the first tab, the entire page reloads. If you post the second form, the content of the second form is replaced.

And here is where things get freaky. Notice I didn't do an action for the cfform. That means the action is the same as the current page. The current page defines tabs. So guess what I get inside? Yep, tabs within tabs. Consider this screen shot:

Most likely this isn't what you want. You want to be sure you specify an action and that the action isn't the same as the page that hosts the form itself. So here is an example from an upcoming update to ColdFusionBloggers:

<cflayout type="tab" style="margin-left: 10px;">

   <cflayoutarea title="Login" source="login.cfm" />


   <cflayoutarea title="Register" source="register.cfm" />

</cflayout>

The contents of register.cfm contain just the form itself. (This page is in development right now, so pardon the half-completed error checking.)

<cfset errors = "">

<cfif structKeyExists(form, "selected") and form.selected is "register">
   <cfif not len(trim(form.username))>
      <cfset errors = errors & "You must enter a username.<br />">
   <cfelseif reFind("[^a-z0-9]", form.username)>
      <cfset errors = errors & "Usernames can contain only numbers and letters.<br />">
   </cfif>   
</cfif>

<cfoutput>
<p>
Note that all form fields are required.
</p>

<cfform action="register.cfm" method="post">      
<p>            
<label>Username</label>
<input name="username" value="" type="text" size="30" />
<label>Password</label>
<input name="password" value="" type="password" size="30" />
<label>Name</label>
<input name="name" value="" type="text" size="30" />
<label>Email Address</label>
<input name="email" value="" type="text" size="30" />
<br /><br />
<input class="button" type="submit" value="Login"/>      
<input type="hidden" name="selected" value="register">   
</p>      
</cfform>      
</cfoutput>

So all in all a pretty cool feature. I'll be able to reload inside my tab without reloading the entire page - but it is definitely something you have to watch out.

Comments

Cool Ray, quick question, a little off topic but I want to move to a CF 8 server you have any suggestions?
# Posted By John Ramon | 8/5/07 6:46 PM
Why not use the cfinput tags?
# Posted By Raul Riera | 8/5/07 7:29 PM
No reason. I don't normally use cfform as I don't like to rely on JS checking when I have to do server side anyway.
# Posted By Raymond Camden | 8/5/07 7:56 PM
John, I used to use hosting.com, and was very happy with them. I have a new host now, but I don't name him as he isn't quite ready yet for new business.
# Posted By Raymond Camden | 8/6/07 6:41 AM
I never liked the cfinput JS validations either. And now with CF8, you can write your server-side validation code in a CFC and leverage that same code client-side using cfajaxproxy, so you only have to maintain one set of validation scripts.
# Posted By Brian Swartzfager | 8/6/07 6:49 AM
cool, when you don't specify it does cfform use the ColdFusion.Ajax.submitForm on its own?

I ask because another good 'oh yeah' is ColdFusion.Ajax.submitForm cannot handle file fields. I'm currently re-working some prototyping because of that :)
# Posted By DK | 8/6/07 8:57 AM
I haven't tried. You try and post back. :)
# Posted By Raymond Camden | 8/6/07 11:06 AM
Hey Ray,

I'm trying to use a checkbox that submits using onclick inside the code you provide.

<cfinput type="checkbox" name="test" onClick="form.submit();" value="1">test

Unfortunately, the page reloads. How do you get it to load inside the tab?
# Posted By JC Anderson | 8/13/07 10:50 AM
JC: An explanation as to why that doesn't work:

http://cfsilence.com/blog/client/index.cfm/2007/8/...

Is it absolutely necessary to submit the form that way? It could be hacked but IMO it would get ugly.
# Posted By todd sharp | 8/13/07 12:36 PM
Todd - Nope, not absolutely necessary, but something I stumbled across. Thanks for the detailed explanation.

I also have a CFC with a function that contains dynamically generated JScript. If I use it in an AJAX container, it fails miserably, too.

Gotta keep in mind that those containers are limited and keep it simple.
# Posted By JC Anderson | 8/13/07 2:39 PM
I'm having a little trouble figuring out navigation after submitting a form. usually, the page I start with stores a variable in the session scope (#Session.Source#) so that when the processing page is done it knows where to return me. I have a "content" cfdiv that has a form where I choose a customer, when I submit that the processing page checks to see if that customer has more than one business unit. If they do, I use a cflocation to go to go to the business unit form. So far so good, still in the same cfdiv content container. But when I submit that form to its processing page and it uses the same cflocation URL="#Session.Source#") to return me whence I came the entire page is replaced instead of the cfdiv content. so my question is, how do I need to manipulate the #Session.Source# variable to always update the cfdiv? I tried using <cfset Session.Source = "javascript:ColdFusion.navigate('/Dashboard.cfm','content');">
but cflocation doesn't seem to like that. maybe the syntax is wrong or maybe there's another way besides cflocation to send me on my way after the processing is done.
# Posted By Michael White | 8/24/07 9:45 AM
I read your comment twice and I don't get it. You said it works - then you said it didn't. Can you explain this again? I can't figure out the difference between your first case (which works) and your second case (which does not work).

And yes - you can't put anything but a valid URL (absolute or relative) in a cflocation.

Also - don't forget you can just post the form using ColdFusion.Ajax.SubmitForm(). No need to go anywhere really.
# Posted By Raymond Camden | 8/24/07 10:05 AM
the difference is whether there is a second level. if a customer has more than one business unit, I cflocation to the business unit form which only replaces the cfdiv contents (which is as expected), but when i return from the second form, the cflocation url="#Session.Source#" replaces the whole page. If the customer has only one business unit, i never go to the business unit form and the cflocation url="#Session.Source" returns me where I was and only replaces the cfdiv content (not the whole page)
# Posted By Michael White | 8/24/07 11:10 AM
SO let me see if I get this right. In your div you load foo.cfm lets say. If foo.cfm sees you have one BU, it uses cflocation to send you to edit.cfm, which works fine.

If you have N BUs, you cflocate to a page where you ask a person to pick the BU. When they leave THAT page, you break out of the cfdiv.

SO my question is - on the page that displays the BUs, how do you let them pick a BU? Is it an HTML link? If so - you need to use AjaxLink to keep stuff inside the div. If it is a form, you need to use cfform to keep them in the div.
# Posted By Raymond Camden | 8/24/07 11:16 AM
I start at the dashboard. the layout has a cfdiv for cfmenu, cfdiv for header, cfdiv for content. I choose change customer from the cfmenu and the ColdFusion.navigate updates the content cfdiv with the customer form with a cfselect to choose customer, that submits to processing page which saves the selection and counts bu for that customer; if >1 cflocation to bu form with cfselect. this submits to processing page, then cflocation to #Session.Source# to go back to original dashboard (hopefully all within the content cfdiv
# Posted By Michael White | 8/24/07 11:40 AM
Ok but in your "bu form with cfselect", are you sure it is "cfselect" and therefore a cfform?
# Posted By Raymond Camden | 8/24/07 11:46 AM
that is all I ever use: cfform, cfinput, cfselect, etc.
on the cfmenu I also have a "Change Business Unit" menuitem that takes me directly to the BU form. I can change BU and come back to the dashboard all within the cfdiv. same BU form, just skip the customer form.
# Posted By Michael White | 8/24/07 11:50 AM
Truly odd. So your first form works, your second form does not. All I can suggest is poring over that form and being super-anal to make sure you aren't missing something obvious like a target= on the form.

Also consider maybe temporarily removing the form and replacing it with a ajaxlink to see if a GET op works ok. (ie, hard coded the BU)

Lastly, you can use COldFusion.Ajax.submitForm instead as I mentioned earlier.
# Posted By Raymond Camden | 8/24/07 1:00 PM
supposedly you can use ColdFusion.navigate to submit a form and direct the results to a container but I can't find any examples and I can't get that to work either.
# Posted By Michael White | 8/25/07 3:06 PM
Here is another gotcha... suppose you have a form with three submit buttons on a cfform in a cfdiv named deleteMe,cancelMe,submitMe. if you make a change on the form and and click the "submitMe" button, which button gets saved to the form scope? well if you have a plain cfform and it's not part of a cfdiv layout, you get submitMe, but no matter which button I pressed on my cfdiv form I got deleteMe. the delete button was first on the form and it was inside a cfif tag to test if it was a new record or an edit. if it was a new record, the deleteMe button didn't exist so when I dumped the form I got cancelMe.
# Posted By Michael White | 8/25/07 4:06 PM
I found a little thing buried in the dev guide that said when you are using a javascript function in a dynamic page like a cfdiv that you ColdFusion.navigate, you have to construct your javascript function declaration a little backwards. All the examples I was looking at had the common syntax: function functionName(arguments){code} but in my case I needed to do something like functionName = function(arguments){code}. now I'm able to get the ColdFusion.Ajax.submitForm and ColdFusion.naviagate to work in javascript functions.
# Posted By Michael White | 8/25/07 9:32 PM
I wouldn't say "in a dynamic page" - but more, if you are writing JS code in a file that will be included via Ajax by another CF control, then you need ot use that format. I've blogged about this already I think. :)
# Posted By Raymond Camden | 8/26/07 10:45 AM
Has anyone discovered a way to DISABLE the feature that forces a CFFORM to submit an Ajax request when used inside a CFDIV or CFLAYOUTAREA? I've run into multiple situations now where I do not want the submit/results to stay within the DIV or tab, especially with a CFDIV. Other than changing the CFFORM to a FORM, and writing all my own validation, I can't find a way to stop it!
# Posted By Mike Sprague | 10/9/07 9:29 AM
Not that I know of - but you can use onSubmit and do the POST in the outer window. That should work.
# Posted By Raymond Camden | 10/9/07 9:57 AM
This is a total hack job but, to get the select submit the form within the cflayout I used this on the select

onchange="document.yourFormID.yourSubmitID.click();"

and I made my submit button hidden

input type="submit" name="yourSubmitID" id="yourSubmitID" value="Submit" style="visibility:hidden"

Is there a better way to submit a form on event without actually clicking a submit button?
# Posted By joe | 5/5/08 12:51 PM
Are you able to set/reset your focus with this form inside a cflayout?
# Posted By TD | 6/12/08 11:12 AM