Overriding Form Submission Properties Using Button Attributes In Native HTML
For the last few weeks, I've been [very slowly] looking into the Hotwire framework from Basecamp. One of the guiding principles of Hotwire seems to be, "HTML has a bunch of great stuff, let's use it!" Case in point, I was reading through a Thoughtbot article on rendering live previews by Sean Doyle when I saw something that I had never seen before: submit buttons with form "action" and "method" attributes. Apparently, this has been supported by browsers going back to IE 10; but, since I've never seen it before, I wanted to try it out for myself in ColdFusion.
Every <form>
you ever create has a method
, an action
, and a target
attribute (among many others). Even if you don't define these attributes explicitly, they are there implicitly with default values. These attributes determine how and where a form submission is sent to the server.
Typically, when you embed a button of type="submit"
within a form, clicking on said button will simply trigger the form submission using the method
, action
, and target
attributes on the <form>
tag. However - and this is the thing I just learned - you can have your submit buttons override the form attributes.
The submit button element can include the following override attributes (not an exhaustive list):
formMethod
- Overrides themethod
attribute on the parent form.formAction
- Overrides theaction
attribute on the parent form.formTarget
- Overrides thetarget
attribute on the parent form.
To see this in action, let's create a super silly ColdFusion form page that submits back to itself. The form has nothing but a name
field; and, upon submission, all we're going to do is display a message tailored for the given name value:
<cfscript>
param name="form.name" type="string" default="";
</cfscript>
<cfoutput>
<!--- Render the message! This is a silly example, sorry. --->
<cfif form.name.len()>
<p>
Hello #encodeForHtml( form.name )#, I hope you are well.
</p>
</cfif>
<!--- NOTE: This form just posts BACK TO ITSELF. --->
<form method="post">
<p>
Name:<br />
<input type="text" name="name" size="30" />
<button type="submit">
Submit Form
</button>
</p>
</form>
</cfoutput>
Like I said, this is a super silly example; but, I'm trying to keep this super simple. This <form>
has no action
attribute. As such, it will, by default, post back to itself (think cgi.script_name
). And, when the form.name
value is populated, we just output a "Hello" message.
If we render this form and submit "Sarah", we get the following output:
As you can see, the page is refreshed with the form submission and the appropriate "Hello" message is displayed.
Now, let's add another submit button. But, this time, we're going to have it override the submission behavior, sending the user to a preview page within a different browser tab:
<cfscript>
param name="form.name" type="string" default="";
</cfscript>
<cfoutput>
<!--- Render the message! This is a silly example, sorry. --->
<cfif form.name.len()>
<p>
Hello #encodeForHtml( form.name )#, I hope you are well.
</p>
</cfif>
<!--- NOTE: This form just posts BACK TO ITSELF. --->
<form method="post">
<p>
Name:<br />
<input type="text" name="name" size="30" />
<button type="submit">
Submit Form
</button>
</p>
<p>
<!---
CAUTION: I'm putting this submit button AFTER the primary submit button so
that hitting "Enter" while focused on the Input will trigger the primary
submit button. The browser will take the first Submit found in the form
when submitting via "Enter"; and we don't want to accidentally make this
"Preview" button the primary button.
--->
<button type="submit" formaction="./preview.cfm" formtarget="_blank">
Preview
</button>
</p>
</form>
</cfoutput>
As you can see, our new submit includes two additional attributes:
formAction="./preview.cfm"
- We're going to override the destination of the form submission, sending the data to the Preview page, not the form processing page.formTarget="blank"
- We're going to post this data to a new browser tab so that we leave the current form in place.
This new preview.cfm
page does nothing but render the message:
<cfscript>
param name="form.name" type="string" default="";
</cfscript>
<cfoutput>
<p>
Hello #encodeForHtml( form.name )#, I hope you are well.
</p>
</cfoutput>
And now, let's try using the form with both submit buttons:
As you can see, when I click on the new Preview submit button, the form is submitted to the preview.cfm
template using a new browser tab. This leaves the original form unsubmitted and in place. We can close the new browser tab and the submit the original form using our original submit button and the "Hello" message is displayed as normal.
This is pretty cool! I can't believe I didn't know that this was a feature of native HTML. I can definitely see this being a useful feature, especially when progressively enhancing a user interface (UI) with Hotwire - but, I'm not there yet.
The Order of Submit Buttons Matters
When I first implemented this form, I put the Preview button right next to the Name input. This worked fine if you clicked on the various submission buttons with your mouse. But, the problem is that if you focused the Name input and hit "Enter" on the keyboard, the browser used the Preview submission button, not the original submission button.
When you submit a form via the "Enter" key, the browser uses the first submission button in the DOM (Document Object Model) branch contained within the Form. As such, it would use the Preview button as the submission button. This is why in a previous article, on using multiple submission buttons to build up complex form data, I included an "off screen" (ie, visually hidden) submission button as the very first rendered element in my form.
To keep things simple, I just moved my Preview submission button to the bottom so that I didn't have to worry about order.
Want to use code from this post? Check out the license.
Reader Comments
Mind Blown! I've been writing HTML for 30+ years and (thought) I understood the submit button. Why would I ever have to look up those specs?! Haha...makes me wonder how much I'm missing because I never question my assumptions. I assumed I understood the submit button...nope! Thanks Ben! Feeling humble.
@Chris,
Dude, I often feel the same way! This is especially true for methods on the DOM nodes - I feel like there's so many I never heard about. Every now and then, I'll scroll through You Might Not Need jQuery, which is a sobering reminder that there are methods I never user and never remember exist.
Add me to the list of developers who had no idea this level of flexibility existed in vanilla HTML. My first thought was that I might be able to use
formmethod
to tell a form to use fancier HTTP methods like PUT, PATCH, DELETE, etc... but unfortunately, the MSDN specs say we are still limited to POST and GET. That was wishful thinking on my part. :)@David,
It's funny you mention that. I came across this stuff because I'm looking into Hotwire, and in Hotwire you can change the methods on links to be
DELETE
... but even the documentation is like, You probably shouldn't be doing this!!!! 🤣@All,
So, on a very related note, I just learned that any submit button could be associated with any form, regardless of where they existing in the DOM. This is done via the
form
attribute:www.bennadel.com/blog/4447-associating-submit-buttons-with-any-form-using-button-attributes-in-native-html.htm
No more reaching for JavaScript on this kind of stuff 😮
Post A Comment — ❤️ I'd Love To Hear From You! ❤️
Post a Comment →