Reputation: 789
I'm currently working on a Rails app that has to communicate with an external web API, update DB records from the result and asynchronously update the view.
I have a list
action where I show data directly from the DB consisting of a list of emails sent in a table with pagination. After the view is rendered I need to make an AJAX request to the application (on a different route) that makes the request to the external web API, updates the DB and then renders the partial with the table
that contains all the emails, the same that was rendered on the original list
action.
The following code handles the AJAX request:
# _mail_list_loader.js.erb
document.addEventListener("turbolinks:load", function() {
$.ajax({
url: '/certified_mail/mail_list',
type: 'get',
data: {
page: <%= @page %>,
rut: <%= @rut || 'null' %>,
from: <%= @from || 'null' %>,
to: <%= @to || 'null' %>,
ids: <%= @outdated_message_ids.to_json.html_safe %>
}
});
});
Which response is a .js.erb
that renders the partial of the actual table
:
# mail_list.js.erb
$('#mail-list').html('<%= j render 'certified_mail/mail_list' %>');
The _mail_list_loader.js.erb
is rendered in my application.html.erb
:
# application.html.erb
<head>
...
<script><%= render 'certified_mail/mail_list_loader.js' %></script>
</head>
My controller looks like this for the action that first loads the table and the action that handles the AJAX request:
class CertifiedMailController < ApplicationController
def list
if params[:page].nil? || params[:page].to_i <= 0
redirect_to action: :list, params: {
page: 1,
from: params[:from].to_s.empty? ? nil : params[:from],
to: params[:to].to_s.empty? ? nil : params[:to]
}
else
# Handle filters and get emails from the DB
end
end
def mail_list
if request.xhr?
# Make API request, update accordingly,
# handle filters and get emails from the DB
respond_to do |format|
format.js
end
else
redirect_to action: :list
end
end
end
When the page first loads, it loads from the controller, and the AJAX request is correctly sent, but if I go to the next page (which sends a request with a different GET parameter page
), the page loads directly from the controller, but two AJAX requests are sent, one for the first page and one for the second page, as shown below, and the HTML for the table
kinda changes randomly, not really showing the right data in the table
:
I used to have the JavaScript that makes the AJAX request defined inside the body
and not the header
but that way the requests would just keep duplicating on every page change, having more and more requests every time. I did some searching and found that the issue of having extra calls could fixed by moving the code to the head
.
I don't really know if my issue is strictly related to how Turbolinks handles the requests, but what I need is for the application to render the full view and then send an AJAX request with the exact same GET
params to a different action that just renders the partial with the table.
Any help would be very much appreciated!
Upvotes: 1
Views: 2712
Reputation: 4240
Because you have dynamic content in _mail_list_loader.js.erb
you'll probably notice that the script tag in the <head>
is being duplicated on subsequent page loads. Although it's written as one block of code, Turbolinks does not "see" it that way, and appends a new script element on each load.
I'd recommend that you try and move as much JavaScript into .js
files and include them in your application manifest. You can get dynamic server-generated variables by rendering them as attributes, and letting the JS pick them up.
For example, you may want to try something like:
# app/views/certified_mail/list.html.erb
<% props = { page: @page, rut: @rut, from: @from, to: @to, ids: @outdated_message_ids } %>
<table data-component="mail-list" data-props="<%= props.to_json %>" id="mail_list_<%= @page %>">
…
</table>
# app/assets/javascripts/load_mail_list.js
;(function () {
function loadMailList (data, callback) {
$.ajax({
url: '/certified_mail/mail_list',
type: 'get',
dataType: 'html',
data: data,
success: callback
})
}
$(document).on('turbolinks:load', function () {
var $element = $('[data-component=mail-list]')
loadMailList($element.data('props'), function (html) {
$element.html(html)
})
})
})()
# app/controllers/certified_mail_controller.rb
def mail_list
if request.xhr?
# Make API request, update accordingly,
# handle filters and get emails from the DB
render 'certified_mail/mail_list'
else
redirect_to action: :list
end
end
The callback should ensure that the response renders the list into the correct element (whereas before, if someone paginated quickly, there might be a risk of page 2's list being rendered on page 3).
Hope that helps.
Upvotes: 2
Reputation: 789
From what I read in this answer the disabling of Turbolinks on certain links was precisely what I needed in order to entirely reload my JavaScript, so I went to the pagination links that sent requests changing parameters (such as page
) and simply added 'data-turbolinks': false
to it.
Now there are no extra AJAX calls because the links now just render the entire HTML, instead of going through Turbolinks.
Upvotes: 0