I have a fb-like-box and a twitter-timeline, the official facebook page and twitter widgets. However, on actual navigation (the links in the sidebar do an ajax load), for example on clicking the logo, these two widgets vanish into thin air.
On refreshing the page, the widgets once again appear as expected.
<div id="social_bar">
<div id="facebook_like_box">
<div class="fb-like-box" data-href="https://www.facebook.com/Gx.Edg" data-width="234" data-height="500" data-colorscheme="light" data-show-faces="true" data-header="true" data-stream="false" data-show-border="true"></div>
</div>
<a class="twitter-timeline" href="https://twitter.com/gxEDGE" data-widget-id="308092518074040321"></a>
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+"://platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>
</div>
The root cause is how Turbolinks interacts with header scripts. #peteykun offers the correct solution in his answer, though it is out of date and lacks an elaborate explanation. At the time of this writing, Rails 5 uses Turbolinks 5.
When you visit a page on your Rails site for the first time (or refresh the page), the page is fetched normally without interference from Turbolinks. The expected browser events fire, the JavaScript executes, and everything renders properly. The trouble starts when you navigate from that page to another page on the same domain (i.e. clicking a link). When that happens, Turbolinks intercepts the new page request and makes a slightly different request that is faster. Glossing over the details, the end result is that expected browser events do not fire as they normally would. No events, no JavaScript. No JavaScript, no rendering. Per the Turbolinks GitHub:
You may be used to installing JavaScript behavior in response to the
window.onload, DOMContentLoaded, or jQuery ready events. With
Turbolinks, these events will fire only in response to the initial
page load—not after any subsequent page changes.
The best and simplest solution is to listen for Turbolink's events instead of the standard browser events.
Fortunately, there's a website authored by user #reed of GitHub which is dedicated to rewriting a wide variety of commonly-used JavaScripts to be compatible with Turbolinks. Unfortunately, the solutions currently on the site were written for an earlier version of Turbolinks and will not work out-of-the-box. Assuming they haven't updated the site yet, you'll need to make some changes.
First, change all event bindings of the form page:* to turbolinks:*. The event namespaces were changed in Turbolinks 5. This is the only change required to get #reed's Twitter coffeescript (Solution #2) to work. As for the Facebook coffeescript, there's no such thing as a turbolinks:fetch or turbolinks:change event, but that's okay. Read on for a more elegant solution.
Reed's Facebook coffeescript uses Javascript to transfer the #fb-root element from the body of the old page to the new one. As I understand it, this is done to preserve the Facebook functionality. Turbolinks 5 has a new feature that allows you to mark a page element as permanent. User #pomartel mentioned this here, and I believe this is a much more elegant solution. Simply add the data-turbolinks-permanent attribute to the #fb-root div, and we remove the need for the saveFacebookRoot and restoreFacebookRoot functions.
The complete Facebook solution is below. In the <body>:
<body>
<div id="fb-root" data-turbolinks-permanent></div>
In the <head>:
<%= javascript_include_tag 'facebook_sdk', 'data-turbolinks-track': 'reload' %>
And the facebook_sdk.coffee file, which belongs in ./app/assets/javascripts:
$ ->
loadFacebookSDK()
bindFacebookEvents() unless window.fbEventsBound
bindFacebookEvents = ->
$(document)
.on('turbolinks:load', ->
FB?.XFBML.parse()
)
#fbEventsBound = true
loadFacebookSDK = ->
window.fbAsyncInit = initializeFacebookSDK
$.getScript("//connect.facebook.net/en_US/sdk.js")
initializeFacebookSDK = ->
FB.init
appId : 'YOUR-APP-ID-HERE'
status : true
cookie : true
xfbml : true
autoLogAppEvents : true
version: 'v2.11'
Don't forget to add the Facebook and Twitter coffeescripts to the precompiled asset pipeline!
The problem was a ruby gem that is bundled by default, called Turbolinks.
The solution is found in the Turbolinks Compatibility Project:
Facebook Like Box
Twitter Widget
Related
I have a very simple Rails app with a react component that just displays "Hello" in an existing div element in a particular page (let's say the show page).
When I load the related page using its URL, it works. I see Hello on the page.
However, when I'm previously on another page (let's say the index page and then I go to the show page using Turbolinks, well, the component is not rendered, unless I go back and forth again. (going back to the index Page and coming back to the show page)
From here every time I go back and forth, I can say that the view is rendered twice more time.Not only twice but twice more time! (i.e. 2 times then 4, then 6 etc..)
I know that since in the same time I set the content of the div I output a message to the console.
In fact I guess that going back to the index page should still run the component code without the display since the div element is not on the index page. But why in a cumulative manner?
The problems I want to solve are:
To get the code run on the first request of the show page
To block the code from running in other pages (including the index page)
To get the code run once on subsequent requests of the show page
Here the exact steps and code I used (I'll try to be as concise as possible.)
I have a Rails 5.1 app with react installed with:
rails new myapp --webpack=react
I then create a simple Item scaffold to get some pages to play with:
rails generate scaffold Item name
I just add the following div element in the Show page (app/views/items/show.html.erb):
<div id=hello></div>
Webpacker already generated a Hello component (hello_react.jsx) that I modified as following in ordered to use the above div element. I changed the original 'DOMContentLoaded' event:
document.addEventListener('turbolinks:load', () => {
console.log("DOM loaded..");
var element = document.getElementById("hello");
if(element) {
ReactDOM.render(<Hello name="React" />, element)
}
})
I then added the following webpack script tag at the bottom of the previous view (app/views/items/show.html.erb):
<%= javascript_pack_tag("hello_react") %>
I then run the rails server and the webpack-dev-server using foreman start (installed by adding gem 'foreman' in the Gemfile) . Here is the content of the Procfile I used:
web: bin/rails server -b 0.0.0.0 -p 3000
webpack: bin/webpack-dev-server --port 8080 --hot
And here are the steps to follow to reproduce the described behavior:
Load the index page using the URL http://localhost:3000/items
Click New Item to add a new item. Rails redirects to the item's show page at the URL localhost:3000/items/1. Here we can see the Hello React! message. It works well!
Reload the index page using the URL http://localhost:3000/items. The item is displayed as expected.
Reload the show page using the URL http://localhost:3000/items/1. The Hello message is displayed as expected with one console message.
Reload the index page using the URL http://localhost:3000/items
Click to the Show link (should be performed via turbolink). The message is not shown neither the console message.
Click the Back link (should be performed via turbolink) to go to the index page.
Click again to the Show link (should be performed via turbolink). This time the message is well displayed. The console message for its part is shown twice.
From there each time I go back to the index and come back again to the show page displays two more messages at the console each time.
Note: Instead of using (and replacing) a particular div element, if I let the original hello_react file that append a div element, this behavior is even more noticeable.
Edit: Also, if I change the link_to links by including data: {turbolinks: false}. It works well. Just as we loaded the pages using the URLs in the browser address bar.
I don't know what I'm doing wrong..
Any ideas?
Edit: I put the code in the following repo if interested to try this:
https://github.com/sanjibukai/react-turbolinks-test
This is quite a complex issue, and I am afraid I don't think it has a straightforward answer. I will explain as best I can!
To get the code run on the first request of the show page
Your turbolinks:load event handler is not running because your code is run after the turbolinks:load event is triggered. Here is the flow:
User navigates to show page
turbolinks:load triggered
Script in body evaluated
So the turbolinks:load event handler won't be called (and therefore your React component won't be rendered) until the next page load.
To (partly) solve this you could remove the turbolinks:load event listener, and call render directly:
ReactDOM.render(
<Hello name="React" />,
document.body.appendChild(document.createElement('div'))
)
Alternatively you could use <%= content_for … %>/<%= yield %> to insert the script tag in the head. e.g. in your application.html.erb layout
…
<head>
…
<%= yield :javascript_pack %>
…
</head>
…
then in your show.html.erb:
<%= content_for :javascript_pack, javascript_pack_tag('hello_react') %>
In both cases, it is worth nothing that for any HTML you add to the page with JavaScript in a turbolinks:load block, you should remove it on turbolinks:before-cache to prevent duplication issues when revisiting pages. In your case, you might do something like:
var div = document.createElement('div')
ReactDOM.render(
<Hello name="React" />,
document.body.appendChild(div)
)
document.addEventListener('turbolinks:before-cache', function () {
ReactDOM.unmountComponentAtNode(div)
})
Even with all this, you may still encounter duplication issues when revisiting pages. I believe this is to do with the way in which previews are rendered, but I have not been able to fix it without disabling previews.
To get the code run once on subsequent requests of the show page
To block the code from running in other pages (including the index page)
As I have mentioned above, including page-specific scripts dynamically can create difficulties when using Turbolinks. Event listeners in a Turbolinks app behave very differently to that without Turbolinks, where each page gets a new document and therefore the event listeners are removed automatically. Unless you manually remove the event listener (e.g. on turbolinks:before-cache), every visit to that page will add yet another listener. What's more, if Turbolinks has cached that page, a turbolinks:load event will fire twice: once for the cached version, and another for the fresh copy. This is probably why you were seeing it rendered 2, 4, 6 times.
With this in mind, my best advice is to avoid adding page-specific scripts to run page-specific code. Instead, include all your scripts in your application.js manifest file, and use the elements on your page to determine whether a component gets mounted. Your example does something like this in the comments:
document.addEventListener('turbolinks:load', () => {
var element = document.getElementById("hello");
if(element) {
ReactDOM.render(<Hello name="React" />, element)
}
})
If this is included in your application.js, then any page with a #hello element will get the component.
Hope that helps!
I was struggling with similar problem (link_to helper method was changing URL but react content was not loaded; had to refresh page manually to load it properly). After some googling I've found simple workaround on this page.
<%= link_to "Foo", new_rabbit_path(#rabbit), data: { turbolinks: false } %>
Since this causes a full page refresh when the link is clicked, now my react pages are loaded properly. Maybe you will find it useful in your project as well :)
Upon what you said I tested some code.
First, I simply pull out the ReactDOM.render method from the listener as you suggested in your first snippet.
This provide a big step forward since the message is no longer displayed elsewhere (like in the index page) but only in the show page as wanted.
But something interesting happen in the show page. There is no more accumulation of the message as appended div element, which is good. In fact it's even displayed once as wanted. But.. The console message is displayed twice!?
I guess that something related to the caching mechanism is going on here, but since the message is supposed to be appended why it isn't displayed twice as the console message?
Putting aside this issue, this seems to work and I wonder why it's necessary in the first place to put the React rendering after the page is loaded (without Turbolinks there was the DOMContentLoaded event listener)?
I guess that this has do with unexpected rendering by javascript code executed when some DOM elements are yet to be loaded.
Then, I tried your alternative way using <%= content_for … %>/<%= yield %>.
And as you expected this give mitigate results ans some weird behavior.
When I load via the URL the index page and then go to the show page using the Turbolink, it works!
The div message as well as the console message are shown once.
Then if I go back (using Turbolink), the div message is gone and I got the ".. unmounted.." console message as wanted.
But from then on, whenever I go back to the show page, the div and the console message are both never displayed at all.
The only message that's displayed is the ".. unmounted.." console message whenever I go back to the index page.
Worse, if I load the show page using the URL, the div message is not displayed anymore!? The console message is displayed but I got an error regarding the div element (Cannot read property 'appenChild' of null).
I will not deny that I completely ignore what's happening here..
Lastly, I tried your last best advice and simply put the last code snippet in the HTML head.
Since this is jsx code, I don't know how to handle it within the Rails asset pipeline / file structure, so I put my javascript_pack_tag in the html head.
And indeed, this works well.
This time the code is executed everywhere so it makes sense to use page-specific element (as previously intended in the commented code).
The downside, is that this time the code could be messy unless I put all page-specific code inside if statements that test for the presence of the page-specific element.
However since Rails/Webpack has a good code structure, it should be easily manageable to put page-specific code into page-specific jsxfiles.
Nevertheless the benefit is that this time all the page-specific parts are rendered at the same time as the whole page, thus avoiding a display glitch that occurs otherwise.
I didn't address this issue at the first place, but indeed, I would like to know how to get page specific contents rendered at the same time as the whole page.
I don't know if this is possible when combining Turbolink with React (or any other framework).
But in conclusion I leave this question for later on.
Thank you for your contribution Dom..
Whats a better solution for initializing jquery plugins on browser back button that aren't just transforming elements when using turbolinks in Rails 5 like masterslider (photo gallery) or slick (carousel), than reloading the page as I do below?
document.addEventListener 'turbolinks:load', ->
slickElementsPresent = $('.event-card').find('div.slick-slide')[0]
if slickElementsPresent?
window.location.reload();
else
$('.event-card').each ->
$(#).not('.slick-initialized').slick {
infinite: false,
nextArrow: $(#).find('.event-more-details-button'),
prevArrow: $(#).find('.event-card-slide-back-button')
}
To be clear, I check to see on 'turbolinks:load' if there are any html elements that would only be present if the plugin had been initialized, if so, then refresh the page because even though the elements are there, the plugin isn't initialized. And then I initialize the plugin on all the elements that have the class I want it on.
Some people encountered this problem here: https://github.com/turbolinks/turbolinks/issues/106 where someone points out
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.
Reloading the page if the plugin has already changed the DOM because it's being retrieved from the cache when someone presses the back button just seems pretty bad, but its the best I've come up with so I'm turning to the world for more ideas!
UPDATE:
So some jquery plugins have great 'undo'/'destroy' methods and if that's the case it's better to add an event listener on "turbolinks:before-cache" and then call that method like so:
document.addEventListener "turbolinks:before-cache", ->
$('.event-card').each ->
$(#).slick('unslick');
but some jquery plugins do not have destroy functions or destroy functions that achieve this. Like masterslider has a $('your-slider-element').masterslider('destroy') function, but it doesn't 'undo' the javascript magic it applies to your html so much as just getting rid of it entirely, and so when you come back to the page from the browser back or forward button, the slider simply doesn't exist, because the html element it gets triggered on has been destroyed. That means for some plugins, the best answer I still have is to reload the page entirely when the page they are on is navigated to via the browser back and forward buttons.
So the best answer I've come up with for dealing with 'Restoration' visits (as I have since learned browser back and forward button visits are called in the world of turbolinks) that involve jquery plugins that don't have an 'undo' method is to simply opt the page out of cache-ing. I implemented this by throwing:
<% if content_for?(:head) %>
<%= yield(:head) %>
<% end %>
in the <head> section of my application.html.erb file and then at the top of the page I don't want turbolinks to include in it's cache, I put:
<% content_for :head do %>
<meta name="turbolinks-cache-control" content="no-cache">
<% end %>
That way turbolinks fetches the page from the network and not the cache as would be the normal behavior for a 'restoration' visit. Just had to read the documentation very carefully and figure out how to implement it in rails which wasn't too bad.
I am using the Ruby on Rails 5 and use turbo-link as shown below:
<%= javascript_include_tag 'custom-plugins', 'data-turbolinks-track' => true %>
It is working fine with Ruby On Rails 4 but have issue with Ruby on Rails version 5. Once I click on back the js/css do not loaded correctly.
Any help will be appreciated.
As Refereed to Rails 5 Awesome features
Turbolinks has been part of Rails since version 4, probably one of the features that people hate it or love; there is no middle ground here.
With Rails 5 we will be receiving a new version that, with the help of HTML5 custom data attributes, we will expect better speed and rendering in our Rails applications.
The most significance change in this new version is the Partial Replacement feature. From the client side, we will be able to tell Turbolinks what content do we need to change/replace and what we don’t.
Turbolinks will look for HTML5 custom attributes and to decide the replacement strategy in our .
To trigger a replacement in the client side we could use or to update our . The difference between and is that the first one will issue a to the server to obtain the HTML that must be used to replace our while expects from us the HTML that should be used for its operation.
With both functions, we can pass a hash with an or an array of of HTML elements to or.
Action Result
Turbolinks.visit(url, { change: ['entries'] }) Will replace any element with custom attribute and any element with its id listed in change.
Turbolinks.visit(url) Will keep only elements with custom attribute and replace everything.
Turbolinks.visit(url, { keep: ['flash'] }) Will keep only elements with custom attribute and any element with its id listed in keep, everything else will be replaced.
Turbolinks.visit(url, { flush: true }) Will replace everything
We can trigger the same functionality from the server-side with and , both can receive , and as options but can also receive with or to force a redirect with or without Turbolinks.
Whether you like Turbolinks or not, this might be a good time to try out and find out if it could be a good fit somewhere in your application.
It is a common occurrence in for turbolinks with js. Turbolinks helps load a particular page a lot faster. But what it also does is stops the js from functioning sometimes. So while loading this particular page use this line
<%= link_to "example_page", example_page_path, :"data-no-turbolink" => true %>
or
write this in your layout
<body <%= "data-no-turbolinks='true'".html_safe if controller_name=="example_controller" && action_name=="example_page" %>>
to stop turbolink from working in this particular page.
So I have a new Rails 4 App using foundation and I started to integrate DataTables today and ran into one minor setback after setting it up to page using ajax calls.
When I click a link from one page (home in this instance) that sends me to a page that contains the datatables, the tables render without any entries or search box or pagination etc. However, if I refresh my browser, the page will fully refresh and the ajax call is made and the the table populates correctly.
After looking at the network traffic, I'm seeing that after clicking the link the response is 304: Not Modified. Since none of the other requests for the JS and CSS and etc, I'm assuming that the JS doesn't reload and make the proper ('#tasks').dataTable({...}) call.
Also, one thing to note is that the table is residing within a partial 'tasks_index.html.haml'.
One thing I did remember however was that I was still using the Turbolinks gem. I tried disabling it to see if that would fix my problem and surprisingly enough, it did.
So what would cause Turbolinks to prevent normal javascript to load on the page? Is there any way to force turbolinks to always load certain pages? Am I better off not even using Turbolinks?
I guess you are using the $(document).ready event to initialize the datatables. When using turbolinks when you click a link this event won't fire because turbolinks loads the page itself. So you should use the document page:change event instead. See here: http://guides.rubyonrails.org/working_with_javascript_in_rails.html#page-change-events
Just like lunr said, you have to put $(document).on "turbolinks:load", -> in your .coffee file
I successfully used the following CoffeeScript code to make DataTables work correctly on Rails 5 / Turbolinks 5.
If you use this script, you will no longer have the problem where the DataTables wrapper reloads after you hit the back button on your browser.
Please note that I also am using the turbolinks-compatibility script.
I'm hopeful this will help someone else who is Googling / struggling with this like I was.
TurboLinks is great for speed ... but it makes loading JavaScript a pain.
$(document).on 'turbolinks:load', ->
tableElementIds = [
'### TABLE ID HERE ###'
]
i = 0
while i < tableElementIds.length
tableElementId = tableElementIds[i]
if $.isEmptyObject($.find(tableElementId))
i++
continue
table = undefined
if $.fn.DataTable.isDataTable(tableElementId)
table = $(tableElementId).DataTable()
else
table = $(tableElementId).DataTable(### OPTIONS HERE ###)
document.addEventListener 'turbolinks:before-cache', ->
table.destroy()
return
i++
return
I have a "normal" link in my jqm page like this:
<a href="http://www.mysite.com/mobile/page.php?attribute=value">
And if I click it it won't properly refresh taking into account the attribute value and loading everything that's needed for it dynamically based on the attribute value. I understand that this is due to the fact that jqm tries to do an ajax call like mentioned here:
When you use pageChange an Ajax request will be made to that url and it will be
loaded only the content inside the div with data-role="page". So everything you
have out of this element will be ignored (JS and CSS).
So, I found out in the docs that I should use $.mobile.ajaxEnabled=false; or rel=external on links or target=_blank on the link.
Strange thing though for me is that only when I set the target=_blank property to my links will this truly happen. So, am wondering if someone had this kind of a problem and how did you solve it? The thing is, I would like to refrain myself form using target=_blank as it opens a new tab in my browser (as expected, but this is not nice from users' POV).
jqm version I use is 1.2
This question now at the top of google search results, so figured I'd answer:
Use the data-ajax attribute and set it to false to force reload upon clicking a link:
data-ajax="false"
use it like:
<a href="/" data-ajax="false">
<img id="mainLogo" src="logo.svg" width="215" />
</a>
And then your link will force reload the page!
Linking without Ajax
Links that point to other domains or that have rel="external",
data-ajax="false" or target attributes will not be loaded with Ajax.
Instead, these links will cause a full page refresh with no animated
transition. Both attributes (rel="external" and data-ajax="false")
have the same effect, but a different semantic meaning: rel="external"
should be used when linking to another site or domain, while
data-ajax="false" is useful for simply opting a page within your
domain from being loaded via Ajax. Because of security restrictions,
the framework always opts links to external domains out of the Ajax
behavior.
Parts taken from https://stackoverflow.com/a/22951472
Make function for the onclick event of the link.See the below code example.Hope this helps!
<script type="text/javascript">
function loadPage(url){
document.location.href = url;
}
<script/>
<a href="#" onClick="loadPage('http://www.mysite.com/mobile/page.php?attribute=value');">