Speed up rendering Rails pages with this gem.
render_async
renders partials to your views asynchronously. This is done
through adding JavaScript code that does AJAX request to your controller which
then renders your partial into a Rails view.
Workflow:
- user visits a Rails page
- AJAX request on the controller action
- controller renders a partial
- partials renders in the place where you put
render_async
helper
JavaScript is injected into <%= content_for :render_async %>
so you choose
where to put it.
Add this line to your application's Gemfile:
gem 'render_async'
And then execute:
$ bundle install
-
Include
render_async
view helper somewhere in your views (e.g.app/views/comments/show.html.erb
):<%= render_async comment_stats_path %>
-
Then create a route that will
config/routes.rb
:get :comment_stats, controller: :comments
-
Fill in the logic in your controller (e.g.
app/controllers/comments_controller.rb
):def comment_stats @stats = Comment.get_stats render partial: "comment_stats" end
-
Create a partial that will render (e.g.
app/views/comments/_comment_stats.html.erb
):<div class="col-md-6"> <%= @stats %> </div>
-
Add
content_for
in your base view file in the body part (e.g.app/views/layouts/application.html.erb
):<%= content_for :render_async %>
Advanced usage includes information on different options, such as:
- Passing in a container ID
- Passing in a container class name
- Passing in HTML options
- Passing in an HTML element name
- Passing in a placeholder
- Passing in an event name
- Handling errors
- Caching
- Doing non-GET requests
- Using with Turbolinks
- Using with respond_to and JS format
- Nested Async Renders
- Configuration
render_async
renders an element that gets replaced with the content
of your request response. In order to have more control over the element
that renders first (before the request), you can set the ID of that element.
To set ID of the container element, you can do the following:
<%= render_async users_path, container_id: 'users-container' %>
Rendered code in the view:
<div id="users-container">
</div>
...
render_async
renders an element that gets replaced with the content of your
request response. If you want to style that element, you can set the class name
on it.
<%= render_async users_path, container_class: 'users-container-class' %>
Rendered code in the view:
<div id="render_async_18b8a6cd161499117471" class="users-container-class">
</div>
...
render_async
can accept html_options
argument.
html_options
is an optional hash that gets passed to a Rails'
javascript_tag
, to drop HTML tags into the script
element.
Example of utilizing html_options
with a nonce
:
<%= render_async users_path, nonce: 'lWaaV6eYicpt+oyOfcShYINsz0b70iR+Q1mohZqNaag=' %>
Rendered code in the view:
<div id="render_async_18b8a6cd161499117471">
</div>
<script nonce="lWaaV6eYicpt+oyOfcShYINsz0b70iR+Q1mohZqNaag=">
//<![CDATA[
...
//]]>
</script>
render_async
can take in an HTML element name, allowing you to control
what type of container gets rendered. This can be useful when you're using
render_async
inside a table
and you need it to render a tr
element before your request gets loaded, so
your content doesn't get pushed out of the table.
Example of using HTML element name:
<%= render_async users_path, html_element_name: 'tr' %>
Rendered code in the view:
<tr id="render_async_04229e7abe1507987376">
</tr>
...
render_async
can be called with a block that will act as a placeholder before
your AJAX call finishes.
Example of passing in a block:
<%= render_async users_path do %>
<h1>Users are loading...</h1>
<% end %>
Rendered code in the view:
<div id="render_async_14d7ac165d1505993721">
<h1>Users are loading...</h1>
</div>
<script>
//<![CDATA[
...
//]]>
</script>
After AJAX is finished, placeholder will be replaced with the request's response.
render_async
can receive :event_name
option which will emit JavaScript
event after it's done with fetching and rendering request content to HTML.
This can be useful to have if you want to add some JavaScript functionality
after your partial is loaded through render_async
.
Example of passing it to render_async
:
<%= render_async users_path, event_name: "users-loaded" %>
Rendered code in view:
<div id="render_async_04229e7abe1507987376">
</div>
<script>
//<![CDATA[
...
document.dispatchEvent(new Event("users-loaded"));
...
//]]>
</script>
Then, in your JS, you could do something like this:
document.addEventListener("users-loaded", function() {
console.log("Users have loaded!");
});
NOTE: Dispatching events is also supported for older browsers that don't support Event constructor.
render_async
let's you handle errors by allowing you to pass in error_message
and error_event_name
.
-
error_message
passing an
error_message
will render a message if the AJAX requests fails for some reason<%= render_async users_path, error_message: '<p>Sorry, users loading went wrong :(</p>' %>
-
error_event_name
calling
render_async
witherror_event_name
will dispatch event in the case of an error with your AJAX call.<%= render_asyc users_path, error_event_name: 'users-error-event' %>
You can then catch the event in your code with:
document.addEventListener('users-error-event', function() { // I'm on it })
render_async
can utilize view fragment caching to avoid extra AJAX calls.
In your views (e.g. app/views/comments/show.html.erb
):
# note 'render_async_cache' instead of standard 'render_async'
<%= render_async_cache comment_stats_path %>
Then, in the partial (e.g. app/views/comments/_comment_stats.html.erb
):
<% cache render_async_cache_key(request.path), skip_digest: true do %>
<div class="col-md-6">
<%= @stats %>
</div>
<% end %>
- The first time the page renders, it will make the AJAX call.
- Any other times (until the cache expires), it will render from cache instantly, without making the AJAX call.
- You can expire cache simply by passing
:expires_in
in your view where you cache the partial
By default, render_async
creates AJAX GET requests for the path you provide.
If you want to change this behaviour, you can pass in a method
argument to
render_async
view helper.
<%= render_async users_path, method: 'POST' %>
You can also set body
and headers
of the request if you need them.
<%= render_async users_path,
method: 'POST',
data: { fresh: 'AF' },
headers: { 'Content-Type': 'text' } %>
On Turbolinks applications, you may experience caching issues when navigating
away from, and then back to, a page with a render_async
call on it. This will
likely show up as an empty div.
If you're using Turbolinks 5 or higher, you can resolve this by setting Turbolinks
configurtion of render_async
to true:
RenderAsync.configure do |config|
config.turbolinks = true # Enable this option if you are using Turbolinks 5+
end
This way, you're not breaking Turbolinks flow of loading or reloading a page. It makes it more efficient that the next option that is suggested below.
Another option:
If you want, you can tell Turbolinks to reload your render_async
call as follows:
<%= render_async events_path, 'data-turbolinks-track': 'reload' %>
This will reload the whole page with Turbolinks.
Make sure to put <%= content_for :render_async %>
in your base view file in
the <head>
and not the <body>
.
If you need to restrict the action to only respond to AJAX requests, you'll
likely wrap it inside respond_to
/format.js
blocks like this:
def comment_stats
respond_to do |format|
format.js do
@stats = Comment.get_stats
render partial: "comment_stats"
end
end
end
When you do this, Rails will sometime set the response's Content-Type
header
to text/javascript
. This causes the partial not to be rendered in the HTML.
This usually happens when there's browser caching.
You can get around it by specifying the content type to text/html
in the
render call:
render partial: "comment_stats", content_type: 'text/html'
It is possible to nest async templates within other async templates. When doing
so, another content_for
is required to ensure the JavaScript needed to load
nested templates is included.
For example:
<%# app/views/comments/show.html.erb %>
<%= render_async comment_stats_path %>
<%# app/views/comments/_comment_stats.html.erb %>
<div class="col-md-6">
<%= @stats %>
</div>
<div class="col-md-6">
<%= render_async comment_advanced_stats_path %>
</div>
<%= content_for :render_async %>
render_async
renders Vanilla JS (regular JavaScript, non-jQuery code)
by default in order to fetch the request from the server.
If you want render_async
to use jQuery code, you need to configure it to do
so.
You can configure it by doing the following anywhere before you call
render_async
:
RenderAsync.configure do |config|
config.jquery = true # This will render jQuery code, and skip Vanilla JS code
config.turbolinks = false # Enable this option if you are using Turbolinks 5+
end
Also, you can do it like this:
# This will render jQuery code, and skip Vanilla JS code
RenderAsync.configuration.jquery = true
After checking out the repo, run bin/setup
to install dependencies. Then, run
rake spec
to run the tests. You can also run bin/console
for an interactive
prompt that will allow you to experiment.
Bug reports and pull requests are welcome on GitHub at https://github.com/renderedtext/render_async.
The gem is available as open source under the terms of the MIT License.
Thanks goes to these wonderful people (emoji key):
This project follows the all-contributors specification. Contributions of any kind welcome!