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.
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;
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.
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! 🎉