When I use the browser back button some scripts seem to be re-evaluating each time the button is pressed. For instance if I click a link that takes me from an index view to a show view, if there is a plugin such as jquery dataTables initialized on the index view, when I use the browser back button to go back to the index from the show view scripts will be re-evaluated and will duplicate causing unexpected behavior (e.g. in the case of dataTables the table will be initialized multiple times with dataTables elements appearing for each restoration visit -- note this is not just an issue with dataTables, it's occurring with other plugins). These scripts are in the head and inside of an event listener:
document.addEventListener("turbolinks:load", function() {
For example, this occurs with dataTables on the first back button click. This is repeated with each click such that you end up with as many duplicated elements as number of back button clicks.


Hi, did you read this sections?
https://github.com/turbolinks/turbolinks#making-transformations-idempotent
https://github.com/turbolinks/turbolinks#preparing-the-page-to-be-cached
Hi @woto , thanks very much for pointing this out. Embarrassingly, I had read over this before, but it was prior to having these issues and for some reason I didn't think about it when I started having problems. In any case, this is exactly right. I've now made my plugin initialization functions idempotent and the issue is resolved. Thanks again!
I just want to add for those having similar issues that making an initialization function idempotent is not necessarily the solution in some circumstances. Having done so with dataTables I am able to avoid duplicate elements. However, the cached versions of the elements on the page related to the plugin no longer function on a browser back click as it seems the plugin is not initialized in a cached page.
I'm guessing for plugins behaving in this manner tearing them down prior to caching is necessary. However, I assume this will require a reinitialization, meaning I'll probably do away with the measures I took to make them idempotent (I assigned a data-attribute which I then used in a condition to determine whether a running the initialization function was appropriate) so that they are reinitialized on a back button click.
Also, if a plugin doesn't actually have it's tear own down method, that would leave us needing to figure out a way to do it on our own.
I'm sure there's good reasons for this change in Turbolinks, but it does create substantial required changes in our code base. Please let me know if I've misunderstood these concepts and what is likely needed to make things behave as intended.
@jaredgalanis I'm running into the same issue with turbolinks and jquery datatables. What was your ultimate solution?
@jaredgalanis I've found exactly what you are describing to be the case. If I add a data-attribute (or even just a class) to the table to not initialize it again when the user hits the back button, the controls won't be duplicated - but the datatable no longer works.
I'm about to try to destroy it before caching...
Yep, that was the only way I could get jQuery datatables to work correctly with Turbolinks:
var dataTable = $('.data-table').DataTable();
Then:
document.addEventListener("turbolinks:before-cache", function() {
dataTable.destroy();
});
That was the only way I could get everything to work with the back button. Hope that helps you @jaredgalanis
yes, exactly @gregblass! you need to tear down the plugin at the before-cache stage. it's a little more management than i was used to performing, but the results are more predictable.
Blah, this really doesn't work for me because the user will use the back button often...and then the datatable will be re-initialized to show all records, and the filter will be lost.
Ideally we shouldn't have to destroy and re-initialize the datatable on every navigation, but for some reason the datatable functionality breaks when you click to another page, and then hit back.
I wonder why that happens.
So yeah, major compatibility issue here with anyone trying to use jquery datatables with Turbolinks. I haven't quite found a good solution yet that will save the user's search/filter. I know it will only be a matter of time before the client asks for it to be saved when navigating back to the table.
this is just a rough thought, but could you test for whether the filter is empty on destroy? if not, save the query to a session param (or somewhere else, like say a property on the window object). then upon initialization you could test for whether the session param is empty. if not, use that to set the value of the filter with jquery. you could then clear that session param after each initialization. I'm sure this isn't all best practice, and it feels like more effort than it should be, but perhaps it would work.
Thats a good idea for a workaround. I'll give it a go.
I wish I knew why datatables wasn't working after being loaded from the cache.
yeah i wish i did as well. the change to turbolinks 5 must have involved some broader structural change that causes this behavior, it wasn't this way before.
not sure how far along you are in the dev of your project, but you may consider the use of frontend mvc framework like ember (if you have the time to invest in learning it). i've started moving parts of our project over and i have not experienced these types of issues. of course, there are trade offs, but i'm pretty happy with how things are going.
feel free to PM me if you want to chat further about that.
Another way to destroy the dataTable before re-creating it on a turbolinks load is to use the DataTables built in destroy option. For example (coffescript):
````
initJqueryDatatables = (table_selector, options) ->
$(table_selector).dataTable(options)
return
init_datatables_run_ready = ->
initJqueryDatatables '#users',
destroy: true
pageLength: 50
$(document).ready init_datatables_run_ready
$(document).on('turbolinks:load', init_datatables_run_ready);
````
It should also be noted that if you're using AJAX based datatables, you should just disable the cache on the page - otherwise you'll send two requests to the server. That in itself is good enough and should eliminate the need to destroy the tables.
I personally am doing that in conjunction with destroying the table on a request-end to clear out the datatables context on each page (as would happen on a full/non-turbolinks page load), and its been working great so far.
stateSave should work fine too, just give your table an ID.
Ended up writing my first blog post about this topic. Hope it helps someone out!
https://medium.com/philly-dev-shop/turbolinks-5-and-datatables-a882c29d6eff#.iksqjvhvb
Thanks a lot @gregblass
meta name="turbolinks-cache-control" content="no-cache"
Adding above code in header worked for me.It is mentioned in below link
https://github.com/turbolinks/turbolinks#preparing-the-page-to-be-cached
Read this for more detail.
The solution is simple, you have to add this line of code to your table tag
data-turbolinks='false'
So your table will be at the end like this
<table class="datatable" data-turbolinks='false'>
....
</table>
```
With this the user to give back or forward will not duplicate its datatable and may use filters
Happy code
@halleyrv <table class="datatable" data-turbolinks='false'> is not working for me. Are you able to provide a reference to the documentation on this so I can look into it further?
I can only find a reference to data-turbolinks='false' with regards to disabling a link:

https://github.com/turbolinks/turbolinks#disabling-turbolinks-on-specific-links
Another way to destroy the dataTable before re-creating it on a turbolinks load is to use the DataTables built in destroy option. For example (coffescript):
initJqueryDatatables = (table_selector, options) -> $(table_selector).dataTable(options) return init_datatables_run_ready = -> initJqueryDatatables '#users', destroy: true pageLength: 50 $(document).ready init_datatables_run_ready $(document).on('turbolinks:load', init_datatables_run_ready);
this one is working properly for my case,
I actually need to modify my post, as the way I did it actually could cause some javascript errors, I believe when using the back button. But here's the gist of the design pattern I use in production across multiple projects:
First, I initialize a global dataTable variable so we can check to see if it is null or not before attempting to destroy it before turbolinks caching occurs. I'm using rails, so I do this in a .js file in the asset pipeline or webpacker.
var dataTable = null;
Then, per page / table, declare data tables with a unique ID so that save state works properly, and assign them to the global dataTable variable.
<table id='js-messages-table'>
...
</table>
<script>
dataTable = $('#js-messages-table').DataTable({
})
</script>
Then in another JS file in the asset pipeline/webpacker, add a turbolinks:before-cache listener to destroy the table - but only if it exists. By setting the variable it to null after destroy, we won't try to destroy it if a table has not been initialized.
document.addEventListener("turbolinks:before-cache", function() {
if (dataTable != null) {
dataTable.destroy();
dataTable = null;
}
});
The key to this modification from my post is just to use a global dataTable variable. Without that there were some cases where it was possible for javascript errors to occur (I forget the actual error - it may have been due to calling destroy on undefined...and it may have involved the back button? Apologies for the vagueness here. I'll try to come back to this one day with a better explanation and also modify my post.)
Then, as I mention in my post, I'm still using this design pattern from the folks at basecamp: https://github.com/turbolinks/turbolinks/issues/87#issuecomment-241425366, and I explicitly disable turbolinks caching on pages that use AJAX-driven data tables.
This design pattern has worked great for me, so I hope this helps people!
My 2 cents.
For pages that require dataTable, load this script in the <head> below:
require("datatables.net")
require('datatables.net-bs4')
require("datatables.net-bs4/css/dataTables.bootstrap4.min.css")
var dataTable = null;
document.addEventListener("turbolinks:load", () => {
if (dataTable == null && $('.dataTable').length !== 0) {
dataTable = $('.dataTable').DataTable({
pageLength: 50
});
}
});
document.addEventListener("turbolinks:before-cache", () => {
if (dataTable != null) {
dataTable.destroy();
dataTable = null;
}
});
I do this by having
# application.html.slim
head
= yield :javascript_in_head
body
= yield
# specific/page.html.slim
body
- content_for :javascript_in_head do
= javascript_pack_tag 'my-datatables-scripts', 'data-turbolinks-track': 'reload'
Explanation
When loading the page directly (meaning land on the page as the first page) turbolinks:load will initialize DataTable on the elements with the class dataTable. This part is straightforward.
When navigating away, turbolinks:before-cache will be called and the dataTable that are present are destroyed. This allows DataTable to be work on a pristine instance of .dataTable elements when the user navigates back to this page. However, we will be able to see the table being destroyed for a fleeting moment before the page navigates away.
When navigating forward from another page (naming it 'pageA'), the turbolinks:load will pick up the .dataTable elements in the page. This will only work if the script is loaded in the <head> as mentioned. This gives the script access to the new elements in the
.dataTable elements.
My explanation might not be correct. I should come back when I have the time to experiment it again. Hope it at least helps someone.
Most helpful comment
Yep, that was the only way I could get jQuery datatables to work correctly with Turbolinks:
var dataTable = $('.data-table').DataTable();Then:
That was the only way I could get everything to work with the back button. Hope that helps you @jaredgalanis