Rails 5 beta Turbolinks events not firing, progress bar not working - ruby-on-rails

I'm kind of new to Rails and web development as a whole.
I'm trying to set up the default progress bar in Turbolinks for rails just like described here.
At first, I thought it might have to do with the fact that the progress bar only shows if a page takes longer than 500ms to load. I tried testing this using sleep 10 in my controller but it doesn't seem to work.
Then I tried debugging the actual turbolink events to see if the callbacks are registering using vanilla js straight in my application.js:
document.addEventListener("turbolinks:load", function () {
console.log("load");
});
document.addEventListener("turbolinks:request-start", function () {
console.log("request start");
});
document.addEventListener("turbolinks:visit", function () {
console.log("visit");
});
document.addEventListener("turbolinks:render", function () {
console.log("render");
});
For some reason, only the turbolinks:load function seems to work.
What am I not seeing or doing wrong?
Thanks.

Related

Making offline.js work with turbolinks

I am using offline.js with turbolinks and on initial page load it works fine. But if I click another links then this wont work until I refresh the page. I can see the Offline.state is down but still the view is not showing. Is there any way to manually trigger the popup window?
Update
Other than the offline.js file the only js I have is this
var
$online = $('.online'),
$offline = $('.offline');
Offline.on('confirmed-down', function () {
$online.fadeOut(function () {
$offline.fadeIn();
});
});
Offline.on('confirmed-up', function () {
$offline.fadeOut(function () {
$online.fadeIn();
});
});
This is an old question, but I ran into the same problem, maybe it can help someone.
offline.js creating these dom elements on page load inside the body HTML
<div class="offline-ui offline-ui-up">
<div class="offline-ui-content"></div>
</div>
The reason why offline.js not working after page change is that on-page change the body HTML replaced with the new content returned by the server and the code above removed.
This is how Turbolinks works, so page load will be not triggered and the offline.js dom elements will be not created.
One solution will be to warp the offline.js inside a function and call it on every page change, but it will cause eventually memory leak (as "offline" and "online" event listener will be added to 'window' on each change)
Other solution will be to save the 'offline-ui' HTML before the new page loaded and bring it back after load:
# will fire before page change and save offline.js UI
document.addEventListener("turbolinks:before-render", function() {
if (!document.offlineHtml) {
document.offlineHtml = $('.offline-ui');
}
});
# will fire afterload and will check if there is a UI to append to the body
document.addEventListener("turbolinks:load", function() {
if (document.offlineHtml) {
$(document.body).append(document.offlineHtml);
}
});
At the moment this is the best way that I could find to fix that.
This could be a turbolinks issue. In app/assets/javascripts/application.js, wrap your javascript code within:
$(document).on('turbolinks:load', function() {
// your code
});

TurboLinks with Angular

I'm running Angular 1.6 along with TurboLinks 5. For the most part, things are working well. I disabled TurboLinks cache and am manually bootstrapping Angular per some of the suggestions on this post: Using angularjs with turbolinks
I have run into one issue though where I have an $interval running within a service. When changing pages via TurboLinks, Angular bootstraps again and the service creates a new interval, but the old one continues to run! Every time a page change event occurs, a new interval is created and they keep piling on top of each other.
I tried destroying the angular app when a TurboLinks link is clicked (using the code below), but that seems to cause the whole Angular app to quit working. I also can't seem to get a reference to the older interval after a page reload.
var app = angular.module('app', []);
// Bootstrap Angular on TurboLinks Page Loads
document.addEventListener('turbolinks:load', function() {
console.log('Bootstrap Angular');
angular.bootstrap(document.body, ['app']);
});
// Destroy the app before leaving -- Breaks angular on subsequent pages
document.addEventListener('turbolinks:click', function() {
console.log('Destroy Angular');
var $rootScope = app.run(['$rootScope', function($rootScope) {
$rootScope.$destroy();
}]);
});
Without the $rootScope.$destroy() on the turbolinks:click event, everything else appears to be working as expected.
I know I could fire off an event here and kill the interval in the service, but ideally I'd like some way where this is automatically handled and ensured nothing is accidentally carried over between TurboLinks requests. Any ideas?
After a lot of trial and error, this does the job, but is not exactly what I'm looking for. Would like to hear if anyone else has any suggestions. It would be ideal if this could happen automatically without a service having to remember to cancel it's own intervals. Also accessing $rootScope in this manner just feels so dirty...
// Broadcast Event when TurboLinks is leaving
document.addEventListener('turbolinks:before-visit', function(event) {
console.log('TurboLinks Leaving', event);
var $body = angular.element(document.body);
var $rootScope = $body.injector().get('$rootScope');
$rootScope.$apply(function () {
$rootScope.$broadcast('page.leaving');
});
});
I'm then injecting $rootScope into my service as well as keeping a reference to the interval. Once it hears the page.leaving event, it cancels the interval:
var self = this;
self.interval = $interval(myFunction, intervalPoll);
....
$rootScope.$on('page.leaving', function() {
$interval.cancel(self.interval);
});
So this gets the job done... but would love to find a better way. Credit for accessing $rootScope this way came from here: How to access/update $rootScope from outside Angular

Rails 4 breaks Stellar.js after link

My rails 4 app breaks stellar.js after link_to.
I know that is something related with turbolinks.
I installed jquery.turbolinks and my js functions from uikit are all working now.
But the stellar.js still don't..
It works when I type the address in browser: 0.0.0.0:3000, but do not after clicking any link.
I am calling stellar.js from my assets/javascripts/my-js.js like this:
$(function(){
$.stellar();
});
I also tried with no success:
function initialize() {
$.stellar();
}
$(document).ready(initialize);
$(document).on('page:load', initialize);
Thank you
Your issue is most certainly related to Turbolinks. I have a feeling that when you call $.stellar() after a Turbolink request, the call has no effect since it's already attached to the window object and that doesn't go away during a the request. Try destroying it prior to initializing it again in the initialize function.
var initialize = function() {
$.stellar( 'destroy' );
$.stellar();
}
$(document).ready( initialize );
$(document).on( 'page:load', initialize );
You can also bind the destroy method to the turbolink before-cache event :
$(document).on "turbolinks:before-cache", ->
$.stellar('destroy')

Making jQuery works with Turbolinks

I have a Rails 4 app, which uses Turbolinks. My understanding is that Turbolinks breaks jQuery code, as Turbolinks does not load new page, but only get new elements.
Therefore, navigating to new page may not trigger .ready, although it always triggers .page:load, and thus new jQuery code won't initialize.
I have a lot of jQuery code, so I don't want to modify my jQuery code to be compatible with Turbolinks.
Is it possible to add a javascript code to my application.js that overwrites .ready event to include page:load as well? How should I do it?
Rather than wait for $(document).ready to fire for your jQuery, just use page:load instead:
$(document).on 'page:load' ->
<your code>
Alternatively, you can set up the jquery.turbolinks gem: https://github.com/kossnocorp/jquery.turbolinks
With turbolinks 5.0.0, the events changed to turbolinks:load. See full list of turbolinks events.
The documentation recommends following code:
document.addEventListener("turbolinks:load", function() {
// ...
})
The jquery.turbolinks fork located at https://github.com/dalpo/jquery.turbolinks already reflects these changes and allows for a seamless drop-in of turbolinks. Nevertheless, I would go for the turbolinks:load event to have full control and not require another library.
i had to use the page:change event:
js:
$(document).on('page:change', function () {
<code here>
});
coffee script:
$(document).on 'page:change' ->
<code here>
With TurboLinks 5 / Rails 5 ... I would recommend instantiating DataTables like this.
It will prevent the heading and footer paging from showing up multiple times when the back button is used.
$(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
TLDR; Here is how the conventional approach works
$(document).ready(function() {
$("#tbl-account").tableSorter();
});
It uses jQuery to initialize a table-sorting plugin once the document finishes loading. One noticeable thing here is not teardown and re-run of this js when page component switches by Turbolink. There isn't any. There didn't need to be back in the day because the browser handled the cleanup. However, in a single-page application like Turbolinks, the browser doesn't handle it. You, the developer, have to manage the initialization and cleanup of your JavaScript behaviors.
When people try to port traditional web apps to Turbolinks, they often run into problems because their JS never cleans up after itself.
All Turbolinks-friendly JavaScript needs to:
Initialize itself when a page is displayed
Clean up after itself before Turbolinks navigates to a new page.
Capturing Events
Turbolinks provides its own events that you can capture to set up and tear down your JavaScript. Let's start with the tear-down:
```js
document.addEventListener('turbolinks:before-render', () => {
Components.unloadAll();
});
```
The `turbolinks:before-render` event fires before each pageview except the very first one. That's perfect because on the first pageview there's nothing to tear down.
The events for initialization are a little more complicated. We want our event handler to runs:
On the initial page load
On any subsequent visit to a new page
Here's how we capture those events:
// Called once after the initial page has loaded
document.addEventListener(
'turbolinks:load',
() => Components.loadAll(),
{
once: true,
},
);
// Called after every non-initial page load
document.addEventListener('turbolinks:render', () =>
Components.loadAll(),
);
Thanks to Starr Horne for writing the article on migrating from jquery/pjax to turbolinks

UI not updating on 'pageinit', probably timing issue

I have the following .js file for my project. This is running in a regular browser, using jquery 1.9.x and jquerymobile 1.3.1. The init function below appears to be running when the page loads and the UI is not updated. Though... I can copy the function into the console and run it, and the UI updates as it is supposed to, so this in not a case of incorrect file paths, or incorrect ids for the UI elements, but I suspect timing. I am also NOT using cordova or phone gap in this instance.
So, my question is, why is the UI not updating when the $(document).bind('pageinit', ...) function is called? If I put a breakpoint in the init method, it is getting called when the page loads. Any suggestions on using a different event or approach?
var simulator = simulator || {};
(function (feedback, $, undefined) { 'use-strict';
feedback.init = function () {
$.get('feedback-config.xml', function (data) {
$('#feedback-to').val($(data).find('email').text());
$('#feedback-subject').val($(data).find('emailSubject').text());
$('#feedback-display').html($(data).find('message').text());
$('#feedback-form').attr('action', $(data).find('serverurl').text()).ajaxForm({success: function () {
alert("Thank you for your feedback!");
}, error: function () {
alert("We're having difficulties sending your feedback, sorry for the inconvenience.");
}});
});
};
}(simulator.feedback = simulator.feedback || {}, jQuery));
$(document).bind('pageinit', function () { 'use strict';
simulator.feedback.init;
});
Thanks in advance.
Found it, simple mistake... In the 'pageinit' function I am calling simulator.feedback.init; instead of simulator.feedback.init(); Not sure why JSLint didn't pick that up initially, but it pointed it out when I tried again later. Thanks.

Resources