February 28th, 2021

Setup Rail's form_with for Javascript/jQuery validation and AJAX submission


In this tutorial, I’ll be explaining how to use form_with with Javascript/jQuery in a Ruby on Rails project. This is useful for performing client-side tasks prior to submitting to your server. In this tutorial, I’ll be creating a contact form that sends an email when someone submits, but you can adapt it for any type of form you may want.

You’ll need to be on Rails 5.1 or newer at a minimum, as that’s when form_with was included.

form_tag, form_for, and form_with?

form_with aims to unify form_tag and form_for. It's recommended as the new method for creating forms in Rails 5.1 and newer. More information can be found here and here.


Setup for Form

First, create a route in routes.rb for the form to send the submission info to:

config/routes.rb
Rails.application.routes.draw do
    # ...
    post '/contact' => 'home#contact'
    # ...
end

Next, setup your view with your form_with tag.

app/views/form.html.erb
<%= form_with url: contact_path, id: 'contact_form', scope: :contact, method: :post, local: false do |f| %>
    <%= f.label :name, "Name" %>
    <%= f.text_field :name, placeholder: "Name" %>
    <%= f.submit "Submit" %>
    <div id="form_msg"></div>
<% end %>

After that, in the controller you specified in your route, create the contact method:

app/controllers/home_controller.rb
class HomeController < ApplicationController
    # ...
    def contact
        respond_to do |format|
            begin
                # Send an email on submission
                UserMailer.with(form: params[:contact]).contact_email.deliver_now

                # Return Javascript back to our contact page for user feedback
                format.js {
                    # We'll be creating these ERB files next
                    render  :template => "home/contact.success.js.erb",
                    :layout => false
                }
            rescue
                # If sending the email fails, let the user know by returning specific code
                format.js {
                    render  :template => "home/contact.error.js.erb",
                    :layout => false
                }
            end
        end
    end
end

And finally, create the files with the Javascript we’ll be returning to the client’s form. This will be used to show success messages or errors.

app/views/home/contact.success.js.erb
// This will be returned if there weren't any issues
document.getElementById("form_msg").textContent = "Your message has been sent!";

And conversely:

app/views/home/contact.error.js.erb
// This will be returned if there was an error
document.getElementById("form_msg").textContent = "There has been an error. Please try again.";

If you’re using Webpack and want to use jQuery with the files above, add this to your application.js file:

app/javascript/packs/application.js
// Expose jQuery globally if using Webpack
import $ from 'jquery';
global.$ = jQuery;

What's going on?

Let's pause for a moment and explain what's happening above.

By default, form_with submits the form via AJAX (meaning remotely). So when you submit the form, it sends the content to the route you specified, which leads to the controller method. From the controller, you can then process the info as you'd like.

The real magic here is that you can respond with Javascript that will be executed on the client-side. This is great for triggering specific unique events or feedback.

If you'd like to know more, there are some great resources out there that explain things more.


Submit the Form via Javascript

I’m including this portion as I recently ran into this issue and spent a lot of time trying to find the seemingly simple answer: how would we submit this form via Javascript?

The obvious answer seems to be something along the lines of $('#form').submit() or $("#form").trigger('submit.rails');. However, neither of these options work if you want to perform some client-side processing prior to submitting, say like validate the form. Those options simply cause the form to submit traditionally (not remotely), and therefore doesn’t use the Javascript we set above and defeats the purpose.

It took some time, but this is what worked for me.

First, Rails comes bundled with rails-ujs, which is a nice javascript library to help with various aspects of Rails. If using Webpack, simply enable it by adding the following line to environment.js:

config/webpack/environment.js
const { environment } = require('@rails/webpacker')
const webpack = require('webpack')

environment.plugins.append('Provide', new webpack.ProvidePlugin({
  $: 'jquery',
  jQuery: 'jquery',

  // This is the important line
  Rails: ['@rails/ujs']
}))

module.exports = environment

And now, to submit the form remotely via Javascript, you can simply do:

form.js
function submitForm(form, e) {
    e.preventDefault();

    // Process form prior to submitting (like validation)

    // This is the magic line
    Rails.handleRemote.call(form, e);
}

And you’re done! You’re new form now submits remotely and can be submitted via Javascript! 🎉