Ajax Form Submission Revisited And Advice Needed

Posted By : todd sharp Posted At : August 13, 2007 12:21 PM Posted In: ColdFusion

14

Ray blogged last week about Async form submission with ColdFusion 8 Ajax containers. This is a really cool feature but a reader commented about wanting the ability to submit a form using JavaScript like so:

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

The problem with trying to do this is that you're circumventing the process a bit. Much of the magic behind this Async submission is quite complicated. That's the beauty of ColdFusion - it takes a mess of a process and makes it seemless to us. It just works. So this magic happens when a form is submit - but it all starts in the onSubmit handler of the form itself which looks something like this:

return ColdFusion.Ajax.checkForm(this, _CF_checktestForm, 'login')

When trying to use JavaScript to submit the form, unfortunately you will not have the benefit of the onSubmit handler being fired. Therefore the submission will not be async and you'll lose out on the cfform validation (but who really uses that anyways).

So what happens when a form is submitted inside a dynamic container? I took a peak at some of the magic with Firebug and what follows is my understanding of the process - I may not be 100% correct but I feel like it's pretty close.

So first off the internal (read: undocumented) ColdFusion.Ajax.checkForm is called. The form itself is passed (this) followed by the cfform JS validation function name (_CF_checktestForm) and finally the Ajax container in which the form is held (in my case a tab named 'login'). Long story short, the form is validated and submit (via ColdFusion.Ajax.submitForm I'm sure) and the original container's innerHTML replaced with the response body of the async form submission. Could this be replicated manually? Sure. Would it be nasty looking? In my opinion, yes. Since your Ajax container and your form are two different templates you'd have to use a method for Ajax submission (an ajaxproxy or submitForm) and then replace the main template container contents with the response body.

So hopefully that explanation makes sense. That being said does anyone have any other ideas for async form submission using JavaScript with CF 8 Ajax containers?

Comments (14)

Brian Kotek's Gravatar Can't you just call ColdFusion.Ajax.submitForm() instead?

todd sharp's Gravatar That would work - but - if you have output to display that is returned by the form submission you'd have to handle that somehow. Take the following quick example:

layout.cfm
<cflayout type="tab">
<cflayoutarea name="login" source="login.cfm" />
</cflayout>

login.cfm
<cfif form.submit...>
thanks for submitting.....
</cfif>
<cfform name="testForm">
<cfinput ...>
<cfinput onclick="ColdFusion.Ajax.submitForm('testForm', 'login.cfm', myCB, myErrorHandler); />
</cfform>

So login.cfm is the source of the tab in layout.cfm. If I use ajax.submitForm it simply takes the form and posts it (in this case to itself) but doesn't actually do anything with the response body (which would be the 'thanks for submitting...'). In myCB I *could * take that response body and stuff it in the innerHTML of login (the id of the tab in layout.cfm) but that feels really hacky to me. Plus what happens when someone renames the ID of the tab 'login' to 'superLogin'? Now I've hardcoded the ID and things are broken.

Does that make sense? In other words I could manually replicate what the default behaviour is but it isn't fool proof.

Brian Kotek's Gravatar I'm not sure why you think taking the result and pushing it into the div is "hacky" since this is exactly what happens if you submit the cfform in the standard way. If this is the only difference in behavior and you absolutely need to submit the form yourself rather than letting CF handle it, I don't think you have any other option.

Also, if you do anything with the AJAX JavaScript functions you're going to have to reference the element through it's ID, so if the goal is to try to avoid any direct references to any DOM elements by their ID, you're going to be either very limited in what you can do, or you're going to have to set up a set of global aliases for the IDs that you can use in both your JavaScript calls and in the actual HTML where you create and identify the element in question.

Basically, I see no problem handling the callback yourself (almost anything complex that uses the new AJAX functions will require a custom function anyway), nor any problem in referencing the ID (again, you're going to have to do this in just about any custom JavaScript function you write that handles AJAX processing).

todd sharp's Gravatar The 'hacky' part to me - bad term - I should say the part I'm uncomfortable with in the above scenario is referencing an HTML element in a seperate template (since layout.cfm and login.cfm are two seperate templates all together). To be honest I personally don't have an issue with the async submission - I just wanted to point out the issue in response to the question that was posed on Ray's blog (why doesn't form.submit() work).

Personally I think I'll be using an ajaxproxy in most situations. I feel perfectly comfortable with callbacks and pushing the generated markup into a container.

I am with you here even though it sounds like I'm talking my way around it ;)

Brian Kotek's Gravatar I usually put all of my AJAX event handlers into one JavaScript template (or if there are a lot of them, several logical groupings of handler templates). Because of the nature of the DOM, these usually have to be included in the main layout. So there's really no getting away from the fact that the JavaScript is going to have to reference IDs in your other templates. The best option is to just group them in one logical place so changes are handled in one place.

If you think of the DOM IDs as the "API to your display elements", there's no real problem with referencing them from outside of the template. And once the template is finished it is unlikely that the IDs are going to change a lot. And again, if they do, the key is to have one place to maintain the JavaScript.

It's the same idea as a CFC: your CFC has methods and arguments, and if you change those, external code is going to have to change too. There's really no way around it, but there are ways to minimize the impact of the changes.

Don Q's Gravatar I realized something while going over the livedocs I missed while I had the same problem, the solution is to use
ColdFusion.navigate(); in this way:

ColdFusion.navigate('urlToFile.cfm','updateElement','callback','errorhandler','POST','formID');

Michael Sandoz's Gravatar If I understand what everyone is talking about here. Here's some code that does what you want.

<cfform name="test">
<cfinput type="Submit" name="submit" class="formbutton" value="UPDATE" />

GOOD FOR SELECT MENUS
onchange="document.test.submit.click();"

GOOD FOR OTHER STUFF
onclick="document.test.submit.click();"

Basically, this simulates clicking on the update button.

Felipe Serrano's Gravatar Hi there...
Sorry to bother you, but Im fighting this thing for a couple of days now. http://www.multilistmanager.com/views/formtest.cfm...

Any idea why Im receiving this: "Error while updating Error code: 500 Message: OK"

The form is submited to a cfc function with just a simple cfmail using the the form values to send an email.

Thanks in advance

Felipe Serrano

todd sharp's Gravatar Is your CFC method set for remote access?

Felipe Serrano's Gravatar Yes, in one function is my form and in another is my cfmail process, both are in remote. What I found out is that variables in form are not passing to the other function; becasue if I delete the #form.varibles# in cfmail all goes ok, but this is useless.

Thanks Todd

todd sharp's Gravatar You shouldn't be accessing the form scope from a CFC. Items that get passed to a CFC will be passed in the 'arguments' scope.

Felipe Serrano's Gravatar You are right!

But how can you pass or set form.scope variables to be in arguments.scope from cfform?

Thanks Todd

todd sharp's Gravatar Your form scope will probably passed in whole to the CFC as a struct. Try this, in your cfc add:

<cfdump var="#arguments#" format="html" output="#expandPath("dump.html")#" />

Then submit the form and go take a look at what is dumped in the dump.html.

I'd be willing to bet you'll see all your form values in the arguments struct. If thats not the case you may want to submit to a CFM page that then calls your CFC, passing the form variables. That probably is a better idea anyways since you can then do some validation before calling the CFC.

Hammo777's Gravatar To solve your original problem:

document.formname.onsubmit();

just call the onsubmit method yourself.