Sometimes you want to update a form and not refresh the page. This is where AJAX (Asynchronous Javascript) comes handy.
Instead of the controller responding with an html page, we’ll have the controller respond with Javascript and update the page.
Let’s create a list of restaurants by creating a Rails model with restaurant name and category.
rails generate model restaurant name:string category:string
I’m going to add a few restaurants to the database by going into the console.
rails c
Restaurant.create(name:"Burger King", category:"Fast Food")
Restaurant.create(name:"Cheese Cake Factory", category:"Family")
Let’s generate a rails controller.
rails g controller restaurants
We’ll generate the standard routes for our restaurant resource in routes.rb
Rails.application.routes.draw do
resources :restaurants
end
I’m going to be using HAML gem instead of ERB to build out the html. If you need to see what’s going on in erb, feel free to use the haml to erb converter.
Let’s add the index action to the restaurants controller and load all of the restaurants into the @restaurants
instance variable. @restaurants
is now available for use in our views.
class RestaurantsController < ApplicationController
def index
@restaurants = Restaurant.all
end
end
I’ll create an index view views/restaurants/index.html.haml
. I’ll add a simple table. Notice that I’m using Bootstrap 4 for styling but it’s completely optional.
%table.table.table-hover
%thead
%tr
%th Name
%th Category
%tbody
%tr
%td</pre><figure class="wp-block-image size-large">
We have a table but there’s nothing in it. Let’s add the list of restaurants…
%table.table.table-hover
%thead
%tr
%th Name
%th Category
%tbody
- @restaurants.each do |restaurant|
%tr
%td
= restaurant.name
%td
= restaurant.category
</figure>
Let’s add a link to the index page to add a new restaurant.
= link_to "Add Restaurant", new_restaurant_path, class:"btn btn-primary", remote: true
</figure>
Notice that I added remote: true
to the link_to
helper. Statement tells Rails we’re going to be responding with javascript and there’s no reason to redirect to another page when the button is clicked.
Go ahead, try to click the button. Nothing will happen.
Ajax and responding with JS
Let’s create a new
action in the controller and load a new restaurant instance into the @restaurant
instance variable. We’re going to use this in our form.
class RestaurantsController < ApplicationController
def index
@restaurants = Restaurant.all
end
def new
@restaurant = Restaurant.new
end
end
Instead of clicking on the Add Restaurant
button and going to a new page, we want the form to be rendered on the same page. This means that the new
action should NOT respond to html. It should instead respond to js
.
I’m using the responders gem to define how an action in the controller should respond. One line 2 I state that the new
action should respond to js instead of html.
class RestaurantsController < ApplicationController
respond_to :js, only: [:new]
def index
@restaurants = Restaurant.all
end
def new
@restaurant = Restaurant.new
end
end
Let’s return back to the add restaurant button. When you click it, watch your server console. You’ll see that Rails is trying to process the new
action as JS but it can’t find a template to use.
</figure>
We solve this by creating new.js.erb
under views/restaurants
.
Refresh your page and click the add restaurants button again. The new.js.erb
file renders but it’s empty.
</figure>
Notice that this file is first processed by erb (ruby) and then by JavaScript. It’s a JavaScript file. I know that I’m using HAML but why would I want to process this with erb and then js? Because Ruby Mine won’t do syntax highlighting if I process it as new.js.haml
. That’s the only reason.
We want this file to display a form for us to add new restaurants.
Let’s add a form to our index page that we’ll use to add a new restaurant. I’ll use form_with
and call on a new instance of a Restaurant. I added the form right above the link_to
helper. Take note that I added an id new-restaurant-form
so that I had a way of selecting the form somehow with java script.
%h1 Restaurants
%table.table.table-hover
%thead
%tr
%th Name
%th Category
%tbody
- @restaurants.each do |restaurant|
%tr
%td
= restaurant.name
%td
= restaurant.category
%div#new-restaurant-form.my-3
=form_with model: Restaurant.new do |form|
= form.text_field :name, placeholder: 'Name'
= form.text_field :category, placeholder: 'Category'
= form.submit
= link_to "Add Restaurant", new_restaurant_path, class:"btn btn-primary", remote: true
Let’s create create
action that will get triggered once the “create restaurant” button is clicked. Let’s also create strong parameters as a private method which helps us create the new restaurant object. In restaurants_controller.rb
, add:
def create
@restaurant = Restaurant.new(restaurant_params)
@restaurant.save
end
private
def restaurant_params
params.require(:restaurant).permit(:name, :category)
end
Note that in the create
action, we’re grabbing the passed in params from the form we submitted and we’re filling out a new instance of @restaurant
. We’re then saving the restaurant object to the database.
Go to the restaurants page and enter a restaurant and click the “create restaurant” button. Nothing happens. Refresh the page manually and you’ll see your entry in the list.
This is not quite what we want. Let’s add some magic using Javascript (Jquery in this case).
</figure>
First I want the form to appear when we click the add restaurant button. By default, I’m going to apply an HTML class d-none
(display: none) to hide the form by default. When the page is refreshed, the form is not visible.
%div#new-restaurant-form.my-3.d-none
When we render new.js.erb
, I want to show the form. I can do that using Jquery. Inside of new.js.erb
add:
$('#new-restaurant-form').toggleClass('d-none')
When the new.js.erb
action is run, we select the new-restaurant-form
by id using Jquery and toggle the class to remove display: none. Refresh the page and try to press the add restaurant button. You’ll see that the form show up and disappears each time you press the button.
However, we want to see the restaurant added to the table.
Let’s dynamically add a row in our create.js.erb
file. We’re calling the @restaurant
instance variable and grabbing it’s name and category and inserting those values into the table as a row.
$('tbody').after('<tr class="child"><td><%= j(@restaurant.name) %></td><td><%= j(@restaurant.category) %></td></tr>')
Try to fill out the form now. You’ll see that the table updates automatically.