Pass javascript data from view into coffeescript function - javascript

I have a view that generates a map for a specific district (available in #district). My map plotting code is coffeescript available to every page but it needs data available as a set of json files (district1.json, district2.json, etc). The first way I got this working was to load this variable in my view, making it globally available.
# in my view
<script type="text/javascript">
var d_data = <%= render file: "#{Rails.root}/data/district_data/#{#district}.json" %>
&lt/script>
Then I use the following coffeescript accepting this global variable:
$(document).ready ->
if $("#users-district-display")
myLatLng = new google.maps.LatLng(d_data.centroid.lat,d_data.centroid.lon)
This works well for this page, but I now throw errors on all pages that don't define d_data. Also, I made these json files available at mysite.com/district_data and was thinking of using .get to bring in the data from ajax (and let the view load quickly), but I still have to the the #district parameter in there.
I know I could just render the js as a partial, and am going to do that unless I can find a way to make this coffeescript work.
Any thoughts appreciated.
Regards,
Tim

Why don't you just check to see if the data was defined?
$(document).ready ->
if $("#users-district-display") and d_data
myLatLng = new google.maps.LatLng(d_data.centroid.lat,d_data.centroid.lon)
Also, if you want to create a new LatLng object with zero parameters, you can always use the existential operator (?):
$(document).ready ->
if $("#users-district-display")
myLatLng = new google.maps.LatLng(
d_data?.centroid.lat || 0
d_data?.centroid.lon || 0
)

Related

Rails 5 - How to use javascript in production on heroku [duplicate]

I have been struggling for 4 years to figure out how I can use google maps in my Rails app.
I've tried to use gmaps4rails but given up because it's too hard. I managed to find a solution on SO, which worked except that i couldn't specify a zoom level. However, in production, all of my other javascripts didn't work. The solution for that was to move the:
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
out of the head tag and to the bottom of the body tag.
That works to let all of my other js files operate as they do in the development environment, when in production mode, however, now the map won't render at all.
Is there a solution which:
allows me to use my javascript files (other than the google maps js file)- which I think is achieved by having the javascript include tag at the end of the body instead of the head
allows me to render a google map in production? If I move the javascript include tag back to the head I can render the map, but the rest of my js files don't work (in production only).
allows me to specify a zoom level on my map?
I have tried to ask for help here, here and here (and a million other times on this board). I haven't managed to find help yet.
In my address.js, I have:
function initMap() {
var map = new google.maps.Map(document.getElementById('map'), {
zoom: 5
});
var bounds = new google.maps.LatLngBounds();
// var opts = {
// zoom: 10,
// max_zoom: 16
// }
var n = addresses.length;
for (var i = 0; i < n; i++) {
var lat = addresses[i].latitude;
var lng = addresses[i].longitude;
if (lat == null || lng ==null){
console.log(addresses[i].name + " doesn't have coordinates");
}else {
var address = new google.maps.Marker({
position: {lat: parseFloat(lat), lng: parseFloat(lng)},
title: addresses[i].name,
map: map //,
// zoom: 8
});
bounds.extend(address.position);
}
}
map.fitBounds(bounds);
}
As I outlined in this post, I get an error that says:
initMap is not a function
I've tagged all the relevant posts that suggest solutions in that linked post, together with the results of my efforts to use those solutions to solve my problem.
In relation to the zoom level on the map, the answer in this post explained the problem as being attributable to the js asking for a map with each address in it. That somehow forces the zoom level to be the most zoomed in, regardless of the level I try to specify. So there are 2 issues, the first is how to remove the bit that forces the most zoomed in level so that I can set a zoom level that will be acknowledged and the second is whether there has been some change in the way to express the zoom level, because so many answers on SO (including those linked in my post) suggest using "setZoom" instead of "zoom: 5". I can't find a solution that worked.
As I outlined in this post, the js files (except for google maps) don't work in production if I have the javascript include tag in the head. I only arrived at this solution after a long, expensive session on codementor. I can't afford another session to find out how to solve the problems that this solution has created. I also didn't get an explanation as to the fundamental reason why this change is necessary. The rails guides suggest including it there so I don't understand why, for the most part, moving it to the end of the body tag is a solution that gets the js to work in the production environment. This breaks the address js though, so it isn't the right solution
In my config.rb I have
config/initializers/assets.rb
# Be sure to restart your server when you modify this file.
# Version of your assets, change this if you want to expire all your assets.
Rails.application.config.assets.version = '1.0'
# Add additional assets to the asset load path
# Rails.application.config.assets.paths << Emoji.images_path
# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
# Rails.application.config.assets.precompile += %w( search.js )
config/production.rb
# Do not fallback to assets pipeline if a precompiled asset is missed.
config.assets.compile = false
I have tried each of the solutions posted in the linked posts (which are mostly from others who have posted on this board. I would love to learn the principle involved in figuring this out (so that I can begin to learn to think the way that rails wants me to). I'm baffled by why this is so difficult and why so little is written about configuring apps to work in production in any of the texts. There is clearly a problem that needs a solution (I have combed through 100s of other posts on this topic, but cannot find a principled answer) or a solution that works.
If it makes any difference, my production environment is on heroku. Any advice, for principles that are relevant would be very much appreciated - if a direct answer is not immediately apparent.
TAKING R_G'S SUGGESTION
I changed my address.js file to include a new first line and a new last line. That file now has:
;(function ready() {
function initMap() {
var map = new google.maps.Map(document.getElementById('map'), {
zoom: 5
});
var bounds = new google.maps.LatLngBounds();
// var opts = {
// zoom: 10,
// max_zoom: 16
// }
var n = addresses.length;
for (var i = 0; i < n; i++) {
var lat = addresses[i].latitude;
var lng = addresses[i].longitude;
if (lat == null || lng ==null){
console.log(addresses[i].name + " doesn't have coordinates");
}else {
var address = new google.maps.Marker({
position: {lat: parseFloat(lat), lng: parseFloat(lng)},
title: addresses[i].name,
map: map //,
// zoom: 8
});
bounds.extend(address.position);
}
}
map.fitBounds(bounds);
}
})();
When I start the server and try to render the page, the map still does not render (just an empty white space where it should be). There are no console errors showing in the chrome inspector (just as there are not when I do not use the first and last lines suggested by R_G).
RESPONSE TO ENGINEER DAVE
In response to your questions:
Q1: Are your production assets being precompiled?
No - the reason for that is that my production.rb notes tell me not to. See here:
# Do not fallback to assets pipeline if a precompiled asset is missed.
config.assets.compile = true
# `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
I understand these notes to mean that assets.rb handles the precompilation, so there is no longer a need to precompile production assets. The first line of the instructions that I copied above tells me not to fallback to the assets pipeline so I'm not sure what you're expecting to see differently.
In asset.rb, the notes say:
# application.js, application.css, and all non-JS/CSS in app/assets folder are already added.# Do not fallback to assets pipeline if a precompiled asset is missed.
I understand this to mean everything listed in application.js is already handled so there is no need for further recompilation steps. I understand (from previous attempts over the years to solve this problem that doubling up on the compilation is a cause of a separate set of problems).
My address.js file is listed in my application.js file (via the require tree), so it should be dealt with by whatever the process that assets.rb uses.
Q2. Are you requiring the tree in application.js
Yes. I am.
To your comment: "Also if you're not requiring the whole JS tree of files in application.js then the files you have flagged for pre-compilation in assets.rb, e.g. address.js, will get precompiled but will not be loaded."
My application.js has:
//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require jquery-fileupload/vendor/jquery.ui.widget
//= require jquery-fileupload/jquery.iframe-transport
//= require jquery-fileupload/jquery.fileupload
//= require bootstrap-sprockets
//= require moment
//= require bootstrap-datetimepicker
//= require pickers
// require underscore
// require gmaps/google
//= require markerclusterer
//= require cocoon
//= require cable
//= require_tree .
I am requiring the tree. Address.js is in my app/assets/javascripts folder so it should be picked up by the require_tree line at the end of the list in application.js
I'm not sure I've understood any of your comment. Is there a suggestion that I could try to get this working? Is your suggestion to ignore the instructions in the assets.rb and production.rb setup?
What is the difference between 'precompiled' and 'loaded'? I'll try to do some research to understand what that means now.
ENGINEERDAVE'S COMMENT ON R_G'S SUGGESTION
EngineerDave suggests referring to this post as a potential solution. This post suggests:
moving the javascript include tag to the end of my body tag (which I have done, with the effect that the google map no longer works but the hide /show js in the other file that wasn't working now does work).
using this line:
document.addEventListener("DOMContentLoaded", function(event) {
//do work
});
That post goes on to suggest a simpler implementation of that line, as:
An actual working plain javascript implementation here if someone
wants code they can just drop in:
stackoverflow.com/questions/9899372/… – jfriend00 Dec 13 '14 at 7:58
That link, suggests:
<script>
// self executing function here
(function() {
// your page initialization code here
// the DOM will be available here
})();
</script>
My current code in address.js begins with this line:
function initMap() {
Taking the suggestion in this post, I try replacing that line with:
(function() {
function initMap() {
//then continue with the rest of my address.js file
})();
When I save this and try it, I get the same outcome as when I tried R_G's first suggestion. No map. No console errors showing in the chrome inspector.
THE DOWNVOTE
I don't understand the reason for the down vote on this question. I have been struggling for years to try to learn how to work with rails with javascript. I have spent thousands of dollars on codementor.io/ upwork and a bunch of the other sites trying to pay for professional help to learn this. I've been to every meetup in my city and make a point to try them in different cities when I have the opportunity to do so. I haven't found a person who has been able to figure this out. 4+ years and I'm still trying to solve this. What's the reason to down vote this. It doesn't give me any insight as to why this forum is not the right place to seek help. I'm out of options for alternatives. I just don't have any more budget to waste on codementor.io. The down vote only costs me points which slows me down from being able to start another bounty on this forum to try and find some information that will help to solve this problem. If you have constructive feedback on how better to ask this question or do the research necessary to find the answer - either of those things would be gratefully appreciated. Have a nice day.
To everyone else, thank you for trying to help me.
BRENZY'S SUGGESTION
Thank you so much for taking the time to make the repo. It's so helpful to see someone else lay this out. That said, this doesnt work for me. I had to adapt some of your approach to get rid of some of the errors this made for me. I've set out how I incorporated your suggestion and the error it gives below:
Application.html.erb
Changes:
I moved the javascript include tag back to the head tag
Above the js include tag, I put the google source link. I've shown the link you suggested in comment below, and the link I used beneath it. I had to change the link because I got an error saying no API keys using your link.
<%= csrf_meta_tags %>
<%= favicon_link_tag %>
<!-- <script src="https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false"></script> -->
<script src="https://maps.googleapis.com/maps/api/js?key=<%= ENV["GOOGLE_MAPS_API_KEY"] %>"async defer></script>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<!-- Latest compiled and minified CSS -->
<%= stylesheet_link_tag href="https://maxcdn.bootstrapcdn.com/bootstrap/latest/css/bootstrap.min.css" %>
<!-- Optional theme -->
<%= stylesheet_link_tag href="https://maxcdn.bootstrapcdn.com/bootstrap/latest/css/bootstrap-theme.min.css" %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
app/assets/javascripts/initialise.js
I made a new file - like yours.
function initialize() {
var losAngeles = new google.maps.LatLng(34.0500, -118.2500);
var pasadena = new google.maps.LatLng(34.14778, -118.14452);
var mapOptions = {
zoom: 10,
center: losAngeles,
disableDefaultUI: true,
mapTypeControlOptions: google.maps.MapTypeId.SATELLITE
};
map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
}
google.maps.event.addDomListener(window, 'load', initialize);
The current error is pointing at the last line of this file.
One thing I don't understand from your commentary is that you think this file is outside of the asset pipeline. I don't understand that because your application.js has require_tree, which I understand to mean that everything in app/assets/javascripts gets included in the pipeline.
My view is now the same as yours:
<div id="map-canvas"></div>
When I save all of this and try it, I get an error that says:
Uncaught ReferenceError: google is not defined
at initialize.self-f919dc6….js?body=1:13
(anonymous) # initialize.self-f919dc6….js?body=1:13
There is just a blank white space instead of a map.
THE BIGGER PROBLEM
I pushed this to heroku and tried the production version. Now, the js that hides & shows form fields based on other form selections doesnt work. This was the original problem that led to putting the javascript include tag at the end of the body instead of the head tag.
That js is in a file called app/assets/javascripts/stance/commercial.js
jQuery(document).ready(function() {
jQuery('[name="organisation[commercial_attributes][standard_financial_split]"]').on('click', function() {
if (jQuery(this).val() == 'true' ) {
jQuery('#commercial_standard_financial_split_content').removeClass('hidden');
} else {
jQuery('#commercial_standard_financial_split_content').removeClass('hidden').addClass('hidden');
}
});
});
This did work when the javascript include tag was moved to the end of the body tag. Now that it's back in the head tag - it doesnt work.
EDIT: See update at the end for a more detailed explanation, but I just saw what is your most likely error. If you follow my recommendation, it should certainly help. I assumed that you only pasted in one function of your code, not your entire file. It is (most often) important in JavaScript to ensure that the entire page has loaded before your code begins to run. That is the purpose of the "function(){...}" that I recommended. It waits until the document is loaded before script execution starts. Without that, you will likely see spurious errors in your scripts, like "initMap is not a function". Follow my recommendation and see if that helps.
You could already be doing this, so let me know if that is true but...
If your results vary according to placement within the scripts, my thought would be that you are polluting the global namespace and suffering conflicting variables as a result. It's much safer to wrap all your code such that it's structure is local and does not impact the global.
Pure JavaScript would have this pattern:
;(function() {
// Document is ready here
})();
I use jQuery, and so I do it this way:
;$(function ($, window, document) {
// Document ready here
}(window.jQuery, window, document));
Exactly how you want to do this depends upon your own pattern, but the goal is to not pollute the global environment.
Hey, it's at least worth a shot.
UPDATE: Since you asked and I referred you to the first suggestion, let me parse that out for you. The code to use is:
;(function() {
// Document is ready here
})();
Replace "// Document is ready here" with your current code. If you have multiple files containing your current code, do the same for each.
The first semicolon is just there to terminate any preceding code, in case they forgot to terminate it. The parenthesis after the semicolon is the key to the local isolation. It pairs with the first parenthesis in the last line. Any variables or functions that you define within those parenthesis will be isolated from anything outside of them. The last two paired parenthesis on the last line executes all of the code as a function, therefore instantiating your internal code. Without that, your code would be inaccessible.
I have created an extremely simple Rails 5 app at https://github.com/brenzy/sample1 that loads a google map in both dev and production environments.
1) I added the google maps script outside of the asset pipeline and made sure it was above the application.js include:
<script src="https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false"></script>
2) As per EngineerDave, I used the following line to make sure the map doesn't get initialized until it is loaded.
google.maps.event.addDomListener(window, 'load', initialize);
I didn't touch any of the default configurations settings.
Hopefully this is along the lines of what you are after, and you can compare it to what your code is doing.

Clojurescript Extern for Nested Function

I am developing a Single Page Application in Clojurescript, and I want to use TinyMCE as a WYSIWYG editor for certain fields. For space efficiency, I want to eventually minify the project using the google clojure compiler in advanced mode. Since the tinymce dev javascript files seems to be unsuitable for use as an extern file, I'm forced to write my own.
There is one particular function call that I can't get to work. In clojurescript, I call:
(.setContent (.get js/tinymce id) cont)
which I'd imagine would compile to something like:
tinymce.get(id).setContent(cont);
I have tried many different function definitions in my externs, yet I keep getting an error:
TypeError: tinymce.get(...).Tl is not a function
Which tells me setContent gets obscured away by the compiler. My current extern file looks like this:
//all seems to be well here...
var tinymce;
tinymce.get = function(name){};
tinymce.remove = function(node){};
tinymce.init = function(editor){};
tinymce.on = function(name, callback, prepend, extra){};
//tinymce setContent attempts
var tinymce.Editor;
tinymce.Editor.prototype.setContent = function(content){};
tinymce.Editor.setContent = function(content){};
tinymce.get(name).setContent = function(content){};
tinymce.get(name).prototype.setContent = function(content){};
var Editor;
Editor.prototype.setContent = function(content){};
Editor.setContent = function(content){};
Which currently is kind of a throw-everything-against-the-wall-and-see-what-sticks attempt. The object get(name) returns should be in the namespace tinymce.Editor.
Is there a proper way of writing an externs to catch these chained function calls? Or is there a way to rewrite the first code snippet so my externs properly preserve the function names? Thanks in advanced.
This line:
var tinymce.Editor;
is not syntactically correct in JS:
SyntaxError: missing ; before statement
Remove it and leave this line:
tinymce.Editor.prototype.setContent = function(content){};
This should work fine.
Also you may always fall back to accessing properties via string literals which will never be renamed:
(require '[goog.object :as gobj])
(gobj/get your-object "i-wont-be-renamed")

Show Javascript function filename programmatically

I need to know if there is a way to extract the script name when calling a variable or a function in javascript. The scenario is running chrome through webdriver or node and when calling for example window.jQuery.Animation I would like to get the filename (i.e., jquery.js) where this function is defined. I know in chrome you can click on 'show function code' and jump to the file, however I am looking to do the same in a programmatic way.
Thanks
Maybe an Error object can help here:
var e = new Error;
console.log(e.stack);
The first two lines of the stack:
Error
at Object.<anonymous> (/home/joost/src/test/testfile.js:3:9)
You have no such way of doing that in pure JavaScript.
The different javascript engines (spiderMonkey, V8, ...) will often compile the javascript code in some way, so you will lose such information in the process.
To know where a function comes from, you will require this function's source map. If you don't know them, source maps will map a script to it's originating filename / line number.
The mozilla sourceMap library will help you with that.
Though, you will have to generate a source map for your file that uses jQuery as well.
if your script.js is like
var a = jQuery.method // this is line 10
^ col 9
You will have to execute
var sourceMap = require('source-map');
consumer = new SourceMapConsumer('script.map');
consumer.originalPositionFor({ line: 10, column: 9 })
// { source: 'jQuery.js',
// line: 12971,
// column: 222,
// name: 'whatever' }
You'll be then able to query the original position and filename where that object is declared.

Using AJAX to call a Rails controller method that takes a parameter

I know there are lots of other questions that address calling rails controller methods using AJAX, but I don't see any that call a rails controller method that requires a parameter, in my case, "def sum_graph(date)"
I am using the Chart.js bar graph and I get the values of the bars using this rails method. Upon updating the values that are used by the sum_graph(date) method via best_in_place, I would like to be able to call that method using the new values to update the chart without refreshing the page.
My code so far looks like this (I'm only updating one bar in the graph since I'm just trying to get it to work):
$(document).ready(function() {
$('.best_in_place').bind("ajax:success", function() {
myBar.datasets[0].bars[0].value = <%= sum_graph(Date.current - 24.months) %>;
myBar.update();
});
});
If I replace the sum_graph(date) method in this code with a random number generator, it indeed updates the bar with the random value every time I change a value using best_in_place so I think I'm headed in the right direction. So is my code indeed re-calling this method each time I change a value, just without the new values? Either way, any help here to resolve this problem would be much appreciated.
Thanks so much in advance.

Rails Javascript - Calling a javascript function from the view

I have a javascript file abc.js. I have kept it inside app/assets/javascripts/abc folder.
In my application.js file, I have given
//= require abc/abc
In my view, I need to display the object that is returned by a function inside the abc.js file.
I am calling the function by the following command
%h4.sub-header Test Project
%textarea.form-control{rows: 10}
:javascript
abc.diff(#a, #b)
I am getting the a & b values from the controller.
But the function is not being called. Instead the sentence "abc.diff(a,b)" is being displayed.
Could anyone tell me how to call the function inside my view?
Inside haml javascript blocks you can just do string interpolation. Also indentation is very important in haml (obviously). So your view should look like:
%h4.sub-header Test Project
%textarea.form-control{rows: 10}
:javascript
abc.diff(#{#a}, #{#b});
Without more info it is hard to say more, but in general this is not the preferred way: your code should be precompiled in javascript assets, and generally I define variable or add data attributes to specific items.
So either something like
:javascript
var a = #{#a};
var b = #{#b};
ad inside abs.js.coffee write
$ ->
abc.diff(a,b)
The cleanest solution imho is something like:
%body{'data-a': #a, 'data-b': #b}
and inside abs.js.coffee write
$ ->
a = $('body').data('a')
b = $('body'_.data('b')
abs.diff(a,b)
Of course, this depends whether it applies to your solution, my apologies for digressing :)

Categories

Resources