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.
<form> you ever create has a
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
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 the
methodattribute on the parent form.
formAction- Overrides the
actionattribute on the parent form.
formTarget- Overrides the
targetattribute 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.
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.