#294 Playing with PJAX
Pjax is a jQuery plugin, written by Chris Wanstrath, that allows you to easily update a section of a page with an AJAX request instead of having to do a full HTTP request. The demo page1 shows how this works. By default, clicking on any of the links on this page will cause the page to fully reload and we can tell that this has happened as the time on the page changes. When we enable pjax by clicking the checkbox clicking the links no longer a full page reload and the time no longer updates, although the main section of the page still changes.
Integrating Pjax into a Rails Application
There are several ways we can integrate pjax into a Rails application and we’ll try two of them in this episode. The application we’ll be working with is shown below. It shows a list of products and clicking on a product reveals a sidebar showing some information about that product.
Currently when we click of the link the entire page reloads. To show how much of the page has changed the layout file and the page’s template both generate random numbers when they’re loaded. When both numbers change this shows that the entire page has been reloaded and this is what we currently see. We’ll use pjax now to so that only the areas of the page that need to change are updated.There’s a gem called pjax_rails by David Heinemeier Hansson that makes it easy to add pjax to a Rails 3.1 application and though it doesn’t quite fit the needs of our application we’ll try it anyway to show how it works. To install it we just add the pjax_rails gem to our
source 'http://rubygems.org' gem 'rails', '3.1.1' gem 'sqlite3' # Gems used only for assets and not required # in production environments by default. group :assets do gem 'sass-rails', '~> 3.1.4' gem 'coffee-rails', '~> 3.1.1' gem 'uglifier', '>= 1.0.3' end gem 'jquery-rails' gem 'pjax_rails'
Once Bundler has finished we need to modify our
Finally we need to wrap the
yield call in the layout file in a div with a
When we restart the server and the reload our page now every link on the page will be pjax-enabled. When we click a link now the page doesn’t reload fully and we can see this as only the template’s random number changes. This is almost what we want but if we look closely we’ll see that the page’s title doesn’t change when we click on one of the links.
We can fix this by adding a
title tag in the template. This is a little hacky and makes the page’s HTML invalid, but it does work.
<% content_for :title, @product ? @product.name : "Products" %> <title><%= yield (:title) %></title> <!-- Rest of page omitted -->
When we reload the page now the title changes when we click one of the links and we also have a fully-working back button and history. From the user’s perspective everything works as it did before but it feels a little faster. This is good but the
pjax_rails gem is a little aggressive. Every link on the page is pjax-enabled by default but this isn’t really what we want in our application. Likewise the whole template is always updated when we click a link and there’s no way of restricting which parts of the page are updated. There is a way of disabling pjax for certain links but having to keep track of these can be awkward. If you have a simple application whose layout file always stays the same, though, then pjax-rails may well suit you, though.
It should be possible to use Rack middleware to make a more flexible solution and this is how Gert Goet’s rack-pjax gem works so we’ll replace pjax_rails with rack-pjax in our application. First we’ll replace the pjax_rails gem with rack-pjax in the
Gemfile and run
# gem 'pjax_rails' gem 'rack-pjax
Next we need to add the included Rack middleware to the application’s config file.
module Store class Application < Rails::Application config.middleware.use Rack::Pjax # Other config commands omitted end end
Rack-pjax doesn’t come with a jQuery plugin embedded so we’ll need to install this separately. We’ll create a
jquery.pjax.js file to it using
//= require jquery //= require jquery_ujs //= require jquery.pjax //= require_tree .
Unlike pjax_rails, rack-pjax doesn’t pjax-enable all links by default so we need to specify the ones to enable. These are all in pages in the
ProductsController so we’ll do this in the products CoffeeScript file.
jQuery -> $('.product a').pjax('[data-pjax-container]')
In this code we first make sure that the DOM has loaded then fetch the links that we want to enable. In our case this is any link inside a parent element with a
product class. We then call
pjax on them and specify what we want to updated when the link is clicked.
With rack-pjax we no longer need to override the
title element and so we can remove this from the
index template. When we reload the page now it works as it did before; the template is updated when we click a link but not the layout and only the template’s random number is updated. The title is updated now, too, even though it’s not specified in the template because this is detected from the layout.
What’s really useful is that we can now move our
data-pjax-container element anywhere we want. It’s no longer has to be placed around the
yield call in the layout file. We’ll move it into the
index template and wrap it around the product details section, i.e. the section that displays the details for a product.
<% content_for :title, @product ? @product.name : "Products" %> <div class="template_time">Template random: <strong><%= rand(900)+100 %></strong></div> <h1>Products</h1> <% for product in @products %> <div class="product"> <h2> <%= link_to product.name, :product_id => product %> <span class="price"><%= number_to_currency(product.price) %></span> </h2> </div> <% end %> <p><%= link_to "New Product", new_product_path %></p> <div data-pjax-container> <% if @product %> <div id="product_details"> <h3><%= @product.name %></h3> <dl> <dt>Price:</dt> <dd><%= number_to_currency(@product.price) %></dd> <dt>Released:</dt> <dd><%= @product.released_at.strftime("%B %e, %Y") %></dd> <dt>Category:</dt> <dd><%= @product.category %></dd> </dl> <p class="actions"> <%= link_to "Edit", edit_product_path(@product) %> | <%= link_to "Destroy", @product, method: :delete, confirm: "Are you sure?" %> </p> </div> <% end %> </div>
Now when we click a link only the side panel changes and neither of the random numbers changes.
If you’re curious about how this gem works then you should take a look at its source code. It’s really simple, being just one Ruby file of about fifty lines of code. Bear in mind though that the entire layout and template are still rendered by the server before rack-pjax decides which parts to send back to the client. If rendering the template is a big performance problem then you might be better off looking at an alternative solution, but for most application this should work perfectly.
That’s in for our episode on pjax. It’s a simple solution and well worth taking a look at if you think it might fit your application’s needs.