Rails 4, turbolinks, and coffeescript - javascript

Forgive me if I'm a bit confused, I'm venturing out into new terratory in my programming, and learning as I go. The basic concept I'm struggling with:
In my Rails 4 app I'm using Leaflet (a JS library for mapping) and calling some of my own code. Long story short, I was unable to get Leaflet and Turbolinks to play well together so for the pages in my site that need Leaflet I've disabled Turbolinks, additionally I'm loading page specific Coffeescript.
I've now decided that I want to add a back button. The usage would go like this - from a page that is using Leaflet, the user can click on a piece of the map and be taken to a page that uses Turbolinks (if it matters). From the Turbolinks page, I have a back button to take you back to the Leaflet map.
However, when I get back to the Leaflet page, my coffeescript either isn't being run, or is being run at the wrong time (I get several errors that weren't there the first time it ran). I'm guessing I would need to add another event at the beginning of the Coffeescript or choose a different event, but I'm just not sure what it should be.
I am in no way wedded to not using Turbolinks on the Leaflet page if you have a suggestion for how to make it work and it helps to get the back button to work. :)
_navbar.html.erb
<li><%= link_to("By grid", grid_path, data: {turbolinks: false}) %></li>
grid.html.erb
... code for layout goes here ...
<% content_for :header do %>
<%= javascript_include_tag "leaflet" %>
<% end %>
<% content_for :javascript do %>
<%= javascript_include_tag "leaflet-maps/#{filename}" %>
<% end %>
leaflet-maps/grid.coffee
$ ->
document.addEventListener 'page:restore', ->
app.init()
return
... rest of coffeescript goes here ...
... here is how the link is getting called
layer.on 'click', (e) ->
window.location.href='/show?grid='+feature.id
return
show.html.erb
<%= link_to "Back", :back %>
Update
grid.coffee - full coffeescript code
ready = ->
#--------------------------------------------------
# Build tooltip with plant name
#--------------------------------------------------
onEachFeature = (feature, layer) ->
if feature.properties and feature.properties.grid_name
layer.bindTooltip feature.properties.grid_name
layer.on 'click', (e) ->
window.location.href='/show?grid='+feature.id
return
return
#--------------------------------------------------
# Variables
#--------------------------------------------------
L.Icon.Default.imagePath = '/assets'
gridStyle = {
"color": "#ff7800",
}
neCorner = L.latLng([47.635103, -122.320525])
swCorner = L.latLng([47.634083, -122.321129])
#--------------------------------------------------
# Set view and load Google Maps
#--------------------------------------------------
map = L.map("map", zoomSnap: .25)
map.fitBounds([swCorner, neCorner])
map.invalidateSize(false)
map.options.maxZoom = 22
map.options.bounceAtZoomLimits = true
googleLayer = L.gridLayer.googleMutant(type: 'roadmap').addTo(map)
map.addLayer googleLayer
#--------------------------------------------------
# Get Ajax data
#--------------------------------------------------
$.ajax
dataType: 'text'
url: 'grid.json'
success: (data) ->
L.geoJSON(JSON.parse(data), style: gridStyle, onEachFeature: onEachFeature ).addTo map
error: ->
alert "Failed to load AJAX data"
document.addEventListener 'turbolinks:load', ready()
document.addEventListener 'DOMContentLoaded', ready()

So, there are two places where you might be going off-track. The first is here:
$ ->
document.addEventListener 'page:restore', ->
app.init()
What this is saying is... "when the web page emits the DOMContentLoaded event, then, when something else emits the page:restore event, then, run my JavaScript code."
Let's start with DOMContentLoaded. This is an event browsers emit by default, and which the jQuery framework uses to trigger event listeners. You can read more about jQuery's .ready() function here.
The page:restore event is NOT a standardly-emitted event, but was a custom event used in Turbolinks Classic (version <5.0, deprecated as of February 2016), which I believe you're using since you mentioned Rails 4.
Either way, stacking both listeners on top of one another is probably not what you want to do. What you could do it define a function to run on both ready and page:restore...
function ready() {
// All of the setup you want to do...
}
document.addEventListener("turbolinks:load", ready());
document.addEventListener("DOMContentLoaded", ready());
(Sorry, I realize you're writing CoffeeScript. That's not my jam. I hope my JavaScript is OK...)
That's one. I see another opportunity for improvement.
layer.on 'click', (e) ->
What you're saying is "when the document is loaded, listen for clicks on the layer object, and then do this thing."
I'm not exactly sure what layer is, and if it's Leaflet-specific I probably won't be able to help you. However, there's actually a more resilient way to write this.
$(document).on("click","layer", function() {
// do things with the layer
});
This might seem identical, but there's a subtle difference. It's saying "any time I click on the document, if I also happen to be clicking on a layer, do this thing."
This is more resilient because your line of JavaScript can always find the document when it's run. If your layer hasn't rendered by the time that line of code runs, it'll never catch those clicks.
Happy JavaScripting! :)

Related

How to dynamically update google recaptcha sitekey?

I have a plain js + jQuery app with a button protected by google recaptcha, everything works as expected, but I'm failing to update the sitekey on the fly. The reason I want this is that I have a couple of environments (staging, test, production etc.) and I'd like to have a separate sitekey for specific envs (in order to separate the test stats from data from real users).
I'm able to change the attribute on my recaptcha element, but it looks like the attributes are taken by the script on initialising the whole thing, how can I refresh/reset the widget to accept the new sitekey?
I've been experimenting with reset and render methods, but to no effect so far.
<div
id="google-recaptcha"
class="g-recaptcha"
data-sitekey="this-will-be-replaced-anyways"
data-callback="onSubmit"
data-size="invisible"
></div>
if (grecaptcha) {
$('#google-recaptcha').attr({
'data-sitekey': 'my-real-sitekey'
});
}
I think you are looking for Explicitly render the reCAPTCHA widget
1.Specify your onload callback function. This function will be called by JavaScript resource(see the next step)
<script>
function onloadCallback() {
grecaptcha.render('myCaptcha1', {
'sitekey': 'sitekey value', // required
'theme': 'light', // optional
'callback': 'onloadCallback' // optional
});
}
</script>
2.Insert the JavaScript resource, setting the onload parameter to the name of your onload callback function and the render parameter to explicit.
<script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit"></script>
3.Define the target HTML control that is supposed to be a captcha on your page.
<div id="myCaptcha1"></div>
I hope it helps you as helped me, good luck

ajaxStart/turbolinks:click are not fired when clicking on link using remote: true

trying to show a spinner whenever a link with remote true is being clicked, tried both ways
using turbolinks:events and ajaxStart/ajaxStop events.
turboinks:load event is successfully fired, but :click isn't.
both ajaxStart and ajaxStop do not work.
Using rails 6, turbolinks 5, webpacker.
links are being generated in a very normal way
example:
<%=link_to 'Documents', documents_path, remote: true %>
this code is placed in <head> </head> of application.html.erb
<script type="text/javascript">
$(document).on("turbolinks:load", function(){
alert('turbolinks load works');
//$(".sk-cube-grid").hide();
});
$(document).on("turbolinks:click", function(){
alert('this doesnt work');
//$(".sk-cube-grid").show();
});
</script>
code using ajaxStart/ajaxStop alternatively placed also in application.html.erb
$(document).ajaxStart(function(){
alert('started ajax - doesnt work');
});
$(document).ajaxStop(function(){
alert('stopped ajax - doesnt work');
})
EDIT
changed the event handling to the following:
var page_loaded = function() {
$(".sk-cube-grid").hide();
};
$(document).on("read page:load", page_loaded);
$(document).on("ajax:send", "a", function(xhr){
$('.sk-cube-grid ').show()
}).on("ajax:complete", "a", function(data, status, xhr) {
$(".sk-cube-grid").hide();
});
This code shows() the .sk-cube-grid when ajax:starts but not hidden when :complete nor when :success.
Setting the remote: true option tells Rails to use UJS (unobtrusive javascript) to handle the ajax. This is not handled by Turbolinks so the Turbolinks event you are listening to isn't fired.
You have two options: use UJS as you are but listen to the correct event: ajax:success or remove the remote: true and let Turbolinks handle it (and listen to turbolinks:load as you are doing now)

Calling ajax:beforeSend and ajax:complete on any link_to where remote is true ruby on rails

I'm working on a webapp using Ruby on Rails as framework. For all the partial renders, we used link_to with remote true as explained in this answer. However, I would like to bind the all those links to something equivalent to ajax:beforeSend and ajax:complete in order to implement a loader anytime a page is called in jQuery. So far I tried the following but it is not working :
$('a').on('ajax:beforeSend', function() {
alert("BEFORE");
});
$('a').bind('ajax:complete', function() {
alert("AFTER");
});
So if you have any other ideas it would be really appreciated if you could let me know. Thanks in advance.
EDIT: Here's an example of how I have been using link_to :
<%= link_to({action: "show_card", controller: "pages", id: activ.id, method: :get, :remote => true}, class: "nav-link p-0") %>
What is not working ajax or loader if your ajax is working then for loader it should be like this
<div id="mySpinner" style="displany:none;">
<img src="loader.gif">
</div>
$(function () {
$(document).ajaxStart(function() {
//$("#mySpinner").show();
alert("BEFORE");
}).ajaxStop(function() {
//$("#mySpinner").hide();
alert("AFTER");
});
});
How about to show loader on element onclick event and hide it on ajax:success?
$('a').on('click', function() {
alert("BEFORE");
});
$('a').ob('ajax:success', function() {
alert("AFTER");
});
Make sure to also cover ajax:error
You might also try to subscribe to ajax:send instead of ajax:beforeSend. Because ajax:beforeSend is a local event and can be subscribed to only within the Ajax request object as docs say
I found out what it was, when I render new staff partially, I need to rebind it to the event, and apparently even using
$( window ).load(function() {
$('a').on('ajax:beforeSend', function() {
alert("BEFORE");
});
$('a').bind('ajax:complete', function() {
alert("AFTER");
});
});
In the application.js file did not work, so basically to bind it initially I had to put a script tag at the bottom of my page which binds everything once it already rendered everything. Furthermore, anytime I do any remote partial rendering, I also need to rebind everything in the .js.erb file, that way it actually works. I'm there has to be a better way because this seems an awful solution; it is however, a solution for now.

Rails jquery.tagsinput.js not executing after link

Problem
I have a custom effect from a file jquery.tagsinput that does not work after clicking on a link, while it will be fixed if I refresh the page.
Tested Solutions
I tried the following:
Disabling turbolinks
Serching the file for ready or onload js functions - none was present
Description
The file jquery.tagsinput has the following structure
(function($) {
// Different functions and javascript staff
})(jQuery);
The function includes different javascript functions that i need to be executed, they work if i refresh the browser. for ex.
$.fn.tagsInput = function(options) {
// Some code
},options);
If I refresh the browser, I can see that the related html input field has two event handlers:
In jquery.tagsinput
$(data.holder).bind('click',data,function(event) {
$(event.data.fake_input).focus();
});
In jquery.js
if ( !( eventHandle = elemData.handle ) ) {
// Some Code
};
My edit.html.erb View
<%= f.text_field :skill_list, :class => "tagsinput form-control", :id => "tagsinput", value: f.object.skill_list.map { |t| t}.join(', ') %>
which generates the following html code:
Thanks a lot
Fabrizio Bertoglio
This is my application.js, it includes the following files:
//= require jquery.tagsinput
//= require form-component
//= require scripts
Jquery.tagsinput does not have a ready function, but the script.js function does and I the problem started when I edited the script.js ready function.
script.js has the following code:
function initializeJS() {
// Some js code
}
jQuery(document).ready(function(){
initializeJS();
});
The problem could have been caused by the editing I have done of the script.js ready function to be compatible with the turbolinks gem (instead of using .save i would use .on('turbolinks:load'..)
jQuery(document).on('turbolinks:load', function(){
initializeJS();
});
Once i corrected that line, the problem was remove, but I do not have understanding of the real reason it has been fixed. I did not disable turbolinks. I will in the future try to understand the real reason and post an explanation.

jQuery works and doesn't work when it first loads

I have a rails app that I'm trying to implement some jQuery in.
I have two scripts, one that works on first load, and one that requires a reload. I had a problem with the one that works now where I modified the turbolinks in application.js. Now the same problem is happening to another script.
The one that works:
jQuery(document).ready(function($){
//set animation timing
var animationDelay = 5000,
//loading bar effect
barAnimationDelay = 3800,
barWaiting = barAnimationDelay - 3000, //3000 is the duration of the transition on the loading bar - set in the scss/css file
//letters effect
lettersDelay = 50,
//type effect
typeLettersDelay = 150,
selectionDuration = 500,
typeAnimationDelay = selectionDuration + 800,
//clip effect
revealDuration = 600,
revealAnimationDelay = 1500;
initHeadline();
The one that doesn't work:
jQuery(document).ready(function($){
//if you change this breakpoint in the style.css file (or _layout.scss if you use SASS), don't forget to update this value as well
var MQL = 1170;
//primary navigation slide-in effect
if($(window).width() > MQL) {
var headerHeight = $('.box-header').height();
$(window).on('scroll',
{
previousTop: 0
},
...
...
so on so forth.
Why is this happening? Is there a problem with the CSS or the page in which it's being called?
Turbolinks can cause problems in your Rails app because it tries to save overhead by maintaining assets listed in the <head> element of your page (among other things). When you load a page using a full load (via refresh button), an actual ready event takes place and your scripts work. On the other hand, if you were to visit a page some other way (via link), Turbolinks tries to reuse the <head> element, so there's no ready event fired.
The solution in the past was to simply disable Turbolinks, either entirely, by removing its require line in application.js or by link (data: {no_turbolink: true}). With Rails 4.2.x and 5.x, you can listen for a Turbolinks related event that should work where a normal ready event would have worked before.
From the documentation:
However, because Turbolinks overrides the normal page loading process,
the event that this relies on will not be fired. If you have code that
looks like this
$(document).ready ->
alert "page has loaded!"
you must change your code to do this instead:
$(document).on "turbolinks:load", ->
alert "page has loaded!"
(quote rearranged slightly for clarity)

Categories

Resources