How to Create a Form with Multiple Submit Buttons to Different URL Endpoints

How to Create a Form with Multiple Submit Buttons to Different URL Endpoints

2 min read

I came upon an interesting problem at work yesterday. A 'search filters' form needed to have two endpoints to which it could submit: one to apply the filters, and another to save the filters as default.

The first implementation was to create a Stimulus controller that intercepted the "save filters" button click, fetched the URL endpoint and the HTTP method via Stimulus target elements, submitted the form to the given URL, and then reset the form for the other button (which applied filters) which applies the filters.

At first, it seemed to work. At least on the desktop browser. However, during testing, we found problems on iPad Safari. Whenever a user tried to save the filters as default, instead of saving them silently, Safari would show a prompt asking the user to download the filters. Something like this.

A little research revealed that the custom JavaScript was indeed the issue. Safari was freaking out due to custom JavaScript intercepting and submitting the form and wanted to get confirmation from the user before it proceeded. Which, of course, we didn't want.

Then I remembered a similar problem I had run into last year when building my markdown blog. The editor contained plain markdown, and in addition to submitting the post, I wanted to render the server-generated HTML preview, because, we're all fans of HTML over the wire (Hotwire) here, and I didn't want to add another JavaScript markdown library for my blog. In short, I wanted to preview and create a post using the same form.

Both these problems boil down to: How can we have a form that submits to multiple endpoints, without writing any custom JavaScript?

Turns out, HTML already has a standardized solution for this problem: the formaction attribute on a button.

formaction

The URL that processes the information submitted by the button. Overrides the action attribute of the button's form owner. Does nothing if there is no form owner.

Let's say you have the following form that creates the post:

<form action="/posts" method="post">
  <input type="text" name="title" id="post_title">
  <textarea name="body" id="post_body"></textarea>

  <input type="submit" name="commit" value="Create Post">
</form>

And now you want to add another button that generates the preview, you could do this:

<form action="/posts" method="post">
  <input type="text" name="title" id="post_title">
  <textarea name="body" id="post_body"></textarea>

  <button type='submit' formaction='/preview'>Preview</button>
  <input type="submit" name="commit" value="Create Post">
</form>

Our little form can now submit to two different endpoints, without writing a single line of JavaScript.

Pretty cool, right?

Then I ran into another problem.

Override the Parent Form's Method

The original form was making a GET request for some reason, and the new button wanted to make a POST request. Again, I didn't want to write any new JavaScript. A little search revealed the formmethod attribute, which does just this:

formmethod

If the button is a submit button, this attribute specifies the HTTP method used to submit the form.

If specified, this attribute overrides the method attribute of the button's form owner.

After using these two attributes, the form started working as expected. The prompt on the Safari browser disappeared, too!

Deleting all the extra JavaScript code never felt so good :)

P.S. I fixed the bug late on Friday, and the QA still hasn't got a chance to test it yet. So I hope I haven't jinx'ed it by writing this post.

Fingers crossed ;)