Why can't I access variable inside a nested function? - javascript

So I have been using the geolocation to find the user location and then find the distance to it and a csv list of locations. I want to save the distance to the json object, but can't access it in my nested function.
function onLocationFound(e) {
console.log(e);
L.marker([e.latitude, e.longitude], {icon: home_marker}).bindPopup("You are here!").addTo(m);
console.log(typeof(e.latitude));
console.log(typeof(e.longitude));
$.get('vac_sites.csv', function (csvString) {
var data = Papa.parse(csvString, { header: true, dynamicTyping: true }).data;
console.log(data)
for (let i = 0; i < data.length; i++) {
//get distance and then use dynamic variable names, make them into js objects, then store in dic with distance
var row = data[i];
console.log(row)
var myRoute = L.Routing.osrmv1({
serviceUrl: 'https://xxx.xxx.xxx:443/route/v1'
});
var self_loc = new L.Routing.Waypoint;
self_loc.latLng = L.latLng([e.latitude, e.longitude]);
var site_loc = new L.Routing.Waypoint;
site_loc.latLng = L.latLng([row.Latitude, row.Longitude]);
myRoute.route([self_loc, site_loc], function(err, routes) {
distance = routes[0].summary.totalDistance;
console.log('routing distance: ' + distance);
row.distance = distance
console.log(row)
});
When I open the console, it appears to have created a new json object and added row to it.
How can I access the original row variable and add the distance to it? Is it a problem with function scope?
EDIT:
When I open console I get this for the first console.log(row):
...
{Name: "Cate Pharmacy", Address: "500 N Missouri Ave, Corning, Arkansas", Telephone: "(870) 857-6766", Website: "www.catepharmacy.com/", Latitude: 36.4155144, …}
...
I want to add a key and value pair for this that is the distance of the route in the form distance: xxxxx.
Desired result is:
...
{Name: "Cate Pharmacy", Address: "500 N Missouri Ave, Corning, Arkansas", Telephone: "(870) 857-6766", Website: "www.catepharmacy.com/", Latitude: 36.4155144, distance: xxxxxx, …}
...
But instead at the second console.log(row) I get this:
{Name: null, distance: 265184.8}

Talking on the chat we solved the problem.
It was narrowed down to changing var row; to let row;
It sounds like row is leaked into your function somehow. You should read this to understand the differences between the two variable declaration keywords.
In a nutshell, var is bound to the immediate function body while let is bound to the immediate closing block. That could be what caused it. Otherwise, I don't know.
It's best to use let because var is almost always unnecessary and can cause problems.

Related

Split a JSON array into JS variables - Bixby

So I have a API outputting JSON to my JS code (http://api.opentripmap.com/0.1/ru/places/bbox?lon_min=-123.049641&lat_min=37.550392&lon_max=-122.049641&lat_max=38.550392&kinds=foods&format=geojson&apikey=5ae2e3f221c38a28845f05b685eac8210f10fb196793a9d4f6653c25).
However it contains a JSON Array that looks like this- "coordinates": [ -122.510216, 37.769474 ]
Is there a way to split it into separate JS variables like one variable for the left side and another for the right side. At the moment the array is causing my code to crash as it can only accept one input in each slot.
Sorry If this is a simple question, I haven't been able to solve this yet...
EDIT:
Sorry for the terrible question layout. I have tried to splice and splitting the array with no success (splicing lead to a whole load of undefined errors).
My current code is
module.exports.function = function findLunch(myLocation) {
var loc_long_max = Number(myLocation.longitude) //grab longitude from user
var loc_lat_min = Number(myLocation.latitude) //grab latitude from User
var loc_long_min = loc_long_max - 0.5;
var loc_lat_max = loc_lat_min + 0.5;
var url_all = "http://api.opentripmap.com/0.1/ru/places/bbox?lon_min=" + loc_long_min + "&lat_min=" + loc_lat_min + "&lon_max=" + loc_long_max + "&lat_max=" + loc_lat_max + "&kinds=foods&format=geojson&apikey=5ae2e3f221c38a28845f05b685eac8210f10fb196793a9d4f6653c25"
var results = http.getUrl(url_all, { format: 'json' }); //gets json info from url_all.
console.log(results.features[rand].properties.name)
//console.log(results.feautres[rand].properties.rate)
console.log(results.features[rand].geometry.coordinates)
//console.log(results);
for (var i = rand; i < results.features.length; i++) {
console.log(results.features[rand].properties.name)
console.log(results.features[rand].properties.rate)
var businesses = {
name: results.features[rand].properties.name,
rating:results.features[rand].properties.rate,
coordinates: results.features[rand].geometry.coordinates
}
}
return businesses
So the coordinates need to be split and then be in the businesses var which is then outputted to Bixby....
EDIT 2 : Fixed it - thanks everyone for the help!
Thanks!
Not sure if this is smth you're asking but you can use destructuting assignment to assign items in an array to variables:
const coordinates = [ -122.510216, 37.769474 ];
const [coordinateOne, coordinateTwo] = coordinates;
console.log(coordinateOne) // -122.510216
console.log(coordinateTwo) // 37.769474
You can use destructuring to assign the first and second elements of the array to variables in a simple way:
const coordinates = [-122.510216, 37.769474];
const [left, right] = coordinates;
console.log(left);
console.log(right);

How do I join separate json objects output from a for loop into an array?

I am scraping websites using CasperJS and one of the tasks involve crawling across url set by a for loop counter. The url looks like this
www.example.com/page/no=
where the no is any number from 0-10 set by the for loop counter. The scraper then goes through all the pages, scrapes the data into a JSON object and repeats until no=10.
The data that I am trying to get is stored in discrete groups in each page- what I would like to work with is a single JSON object by joining all the scraped output from each page.
Imagine Page1 has Expense 1 and the object I am getting is { expense1 } and Page 2 has Expense 2 and object that I am getting is { expense2 }. What I would like to have is one JSON at the end of scraping that looks like this:
scrapedData = {
"expense1": expense1,
"expense2": expense2,
}
What I am having trouble is joining all the JSON object into one array.
I initialized an empty array and then each object gets pushed to array.
I have tried a check where if iterator i in for loop is equal to 10, then the JSON object is printed out but that didnt seem to work. I looked up and it seems Object spread is an option but I am not sure how to use it this case.
Any pointers would be helpful. Should I be using any of the array functions like map?
casper.then(function(){
var url = "https:example.net/secure/SaFinShow?url=";
//We create a for loop to go open the urls
for (i=0; i<11; i++){
this.thenOpen(url+ i, function(response){
expense_amount = this.fetchText("td[headers='amount']");
Date = this.fetchText("td[headers='Date']");
Location = this.fetchText("td[headers='zipcode']");
id = this.fetchText("td[headers='id']");
singleExpense = {
"Expense_Amount": expense_amount,
"Date": Date,
"Location": Location,
"id": id
};
if (i ===10){
expenseArray.push(JSON.stringify(singleExpense, null, 2))
this.echo(expenseArray);
}
});
};
});
Taking your example and expanding on it, you should be able to do something like:
// Initialize empty object to hold all of the expenses
var scrapedData = {};
casper.then(function(){
var url = "https:example.net/secure/SaFinShow?url=";
//We create a for loop to go open the urls
for (i=0; i<11; i++){
this.thenOpen(url+ i, function(response){
expense_amount = this.fetchText("td[headers='amount']");
Date = this.fetchText("td[headers='Date']");
Location = this.fetchText("td[headers='zipcode']");
id = this.fetchText("td[headers='id']");
singleExpense = {
"Expense_Amount": expense_amount,
"Date": Date,
"Location": Location,
"id": id
};
// As we loop over each of the expenses add them to the object containing all of them
scrapedData['expense'+i] = singleExpense;
});
};
});
After this runs the scrapedData variable should be of the form:
scrapedData = {
"expense1": expense1,
"expense2": expense2
}
Updated code
One problem with the above code is that inside the for loop when you loop over the expenses, the variables should be local. The variable names also should not be Date and Location since those are built-in names in JavaScript.
// Initialize empty object to hold all of the expenses
var scrapedData = {};
casper.then(function(){
var url = "https:example.net/secure/SaFinShow?url=";
//We create a for loop to go open the urls
for (i=0; i<11; i++){
this.thenOpen(url+ i, function(response){
// Create our local variables to store data for this particular
// expense data
var expense_amount = this.fetchText("td[headers='amount']");
// Don't use `Date` it is a JS built-in name
var date = this.fetchText("td[headers='Date']");
// Don't use `Location` it is a JS built-in name
var location = this.fetchText("td[headers='zipcode']");
var id = this.fetchText("td[headers='id']");
singleExpense = {
"Expense_Amount": expense_amount,
"Date": date,
"Location": location,
"id": id
};
// As we loop over each of the expenses add them to the object containing all of them
scrapedData['expense'+i] = singleExpense;
});
};
});

Google Apps Script: how to copy array of objects to range?

I have an array of objects called membership:
[{name: 'Linus Pauling', address: '1805 Main Street', phone: '(615) 555-1010',
email: 'linus.pauling#gmail.com' },
{name: 'Maury Povich', address: '382 North Street', phone: '(423) 555-1997',
email: 'maury#themauryshow.org'}]
(Although only 4 are shown here, I really have ten key/value pairs per member.)
What is the best way to copy bits of this array to a range of cells in Google Sheets? I am trying to find a method to call the object values directly, and use the .SetValues method to copy them en masse, as opposed to one at a time.
To get all the members' names in column A, I've tried:
sheet.getRange(1,1,membership.length,1).setValues(membership[{member.name}]);
...which gives Missing : after property ID.
Or:
sheet.getRange(1,1,membership.length,1).setValues([membership[name]]);
...which gives ReferenceError: "name" is not defined.
Or:
sheet.getRange(1,1,membership.length,1).setValues([member.name]);
...which gives the "Cannot convert Array to Object[][]" error.
I apologize for the newbie question. I have seen answers about how to copy values from a multidimensional array to a sheet's range, but not an array of objects.
Are you looking to produce a table in the form:
name | Address | phone | email
-----+---------+-------+------
.... | .... | .... | ....
etc?
If so, then the following snippet may help. The key thing to point out here is that you can't expect a given order when iterating through an object. i.e. Just because your representation of membership lists name first, doesn't mean that if you were to use a for ... in loop you could guarantee that name would come back first - Objects in JavaScript are unordered.
To ensure a given order, the snippet I list defines an array headings in which you specify the order of the columns you want in the Sheet. This is used to guarantee the column order in the output:
var membership = [
{
name: 'Linus Pauling', address: '1805 Main Street', phone: '(615) 555-1010',
email: 'linus.pauling#gmail.com'
},
{
name: 'Maury Povich', address: '382 North Street', phone: '(423) 555-1997',
email: 'maury#themauryshow.org'
}
];
// Headings in the column order that you wish the table to appear.
var headings = ['name', 'address', 'phone', 'email'];
var outputRows = [];
// Loop through each member
membership.forEach(function(member) {
// Add a new row to the output mapping each header to the corresponding member value.
outputRows.push(headings.map(function(heading) {
return member[heading] || '';
}));
});
// Write to sheets
if (outputRows.length) {
// Add the headings - delete this next line if headings not required
outputRows.unshift(headings);
SpreadsheetApp.getActiveSheet().getRange(1, 1, outputRows.length, outputRows[0].length).setValues(outputRows);
}
Output is :
How about following script? This script is a container bound script of spreadsheet. There are 2 patterns. The arrangement of data is different for between pattern 1 and 2.
Pattern 1 is
Pattern 2 is
function main() {
var data = [{name: 'Linus Pauling', address: '1805 Main Street', phone: '(615) 555-1010',
email: 'linus.pauling#gmail.com' }, {name: 'Maury Povich', address: '382 North Street', phone: '(423) 555-1997',
email: 'maury#themauryshow.org'}];
var sheet = SpreadsheetApp.getActiveSheet();
var result = pattern1(data);
var result = pattern2(data);
sheet.getRange('a1').offset(0, 0, result.length, result[0].length).setValues(result);
}
function pattern1(data){
var ar = [];
for (var i in data){
for (var key in data[i]){
ar.push([key, data[i][key]]);
}
}
return ar;
}
function pattern2(data){
var ar = [];
var keys = [];
var values = [];
for (var i in data){
for (var key in data[i]){
if (i == 0) keys.push(key);
values.push(data[i][key]);
}
if (i == 0){
ar.push(keys);
keys = [];
}
ar.push(values);
values = [];
}
return ar;
}
If my understanding for your questions was wrong, I apologize.
See the script at the bottom of the Simple Mail Merge Tutorial for some useful code to retrieve data. Copy all the code from this comment down:
//////////////////////////////////////////////////////////////////////////////////////////
//
// The code below is reused from the 'Reading Spreadsheet data using JavaScript Objects'
// tutorial.
//
/////////
/////////////////////////////////////////////////////////////////////////////////
// getRowsData iterates row by row in the input range and returns an array of objects.
Once you have, the getRowsData returns not only the data, but a second item which is a list of the Headers. Using this, you can modfy the code from #Bardy to allow for the Headers in any order, and also other possible columns in between.

Get Firebase unique ID from filtered object

I’m building an Angular app that searches geographic locations through the 500px API and returns photos based on the search. If someone searches the same location multiple times, I’ll need to increment the search for page=x on the API request, so that fresh results are returned.
My current way of handling this is to query all my locations in Firebase, and filter through them using Undescore.js _findWhere feature. If a location name matches the searched term, I increment it, otherwise I create a new one.
Currently I have it working so that my object is being returned when it matches, but in order to increment it, I need the unique ID of that object that Firebase assigned to it.
Here's my code (converted from CoffeeScript; apologies):
getSearchCount = function(result) {
var data;
// fetch the data from firebase as an object
data = $firebase(ref).$asObject();
return data.$loaded().then(function() {
var passed_data, plucked_result;
// search the object to see if a location matches the currently searched term
plucked_result = _.findWhere(data.locations, {
name: result.formattedAddress
});
// this is where I want to return the unique ID of the plucked result
return passed_data = [result, plucked_result];
});
};
saveLocation = function(passed_data) {
var plucked_result, result, search_count;
result = passed_data[0];
plucked_result = passed_data[1];
// if the search term doesn't exist, create a new one
if (plucked_result === null) {
search_count = 1;
return locationsRef.push({
name: result.formattedAddress,
lat: result.lat,
lng: result.lng,
search_count: search_count
});
} else {
// increment the search count on the query
// search_count = plucked_result.search_count + 1
// plucked_result.search_count = search_count
}
};
Here's the object I’m getting returned for console.log(data):
d {$$conf: Object, $id: null, $priority: null, foo: "bar", locations: Object…}
$$conf: Object
$id: null
$priority: null
locations: Object
-JUBNhmr_0kwSmHLw4FF: Object
lat: 51.5073509
lng: -0.12775829999998223
name: "London, UK"
search_count: 1
__proto__: Object
-JUBQREGJpQnXxiMIaKm: Object
lat: 48.856614
lng: 2.3522219000000177
name: "Paris, France"
search_count: 1
__proto__: Object
__proto__: Object
photos: Object
__proto__: Object
Here's the object I’m getting return for console.log(plucked_result):
Object {lat: 51.5073509, lng: -0.12775829999998223, name: "London, UK", search_count: 1}
So, to summarise, I want the Firebase unique ID (-JUBNhmr_0kwSmHLw4FF).
Or perhaps I’m doing this in a completely convoluted way that could be simplified? All I essentially need to do it create a way of paginating my API request so that I’m not pulling in all the same page of results twice.
Short answer:
You can replace the underscore.js findWhere() call with a loop like this:
var key;
var location;
for(key in data.locations) {
location = data.locations[key];
if(location.name == result.formattedAddress) {
break;
}
}
This will yield your object of interest stored in location and its name in key.
Why this is the case
The unique id that you're looking for (which the Firebase docs call the object name) is the parent of your location object in your firebase. One object in data.locations might look something like this:
{ '-JUBNhmr_0kwSmHLw4FF' :
{ lat: 51.5073509, lng: -0.12775829999998223,
name: "London, UK", search_count: 1 }
}
And the findWhere() function can locate it, but it only returns the matched object and does not provide a way to step up the tree one to get the parent node.

How to set array value as function parameter, when iterating

I use google maps API to display markers on a map.
Each marker points to a shop on the map.
My problem is with creating the event handler for each mark, which will display the tooltip with information about the shop when the given mark will be clicked.
I use the text input to let the user type the city, and with jquery I handle the click event on the nearby submit button. Then I send the query to the php site, get following object back:
d: Object
error: false
lat: "52.3744440000"
lng: "9.7386110000"
shop: Array[2]
0: Object
address: "Addressstreet 12"
lat: "52.3761209000"
lng: "9.7387242000"
name: "Shop 1"
tel: "1234"
__proto__: Object
1: Object
length: 2
etc.
Now I use the lat and lng values which are coordinates of the city to center the map.
Then I send the shop array to the paintShops(shops) method:
function paintShops(shops){
for (var i = 0; i < shops.length; i++){
kl = shops[i];
var ka = parseFloat(kl.lat);
var kb = parseFloat(kl.lng);
tel = kl.tel;
address = kl.address;
var son = new google.maps.Marker({
position: new google.maps.LatLng(ka, kb),
map: map,
title: kl.name,
clickable: true
});
google.maps.event.addListener(son, 'click', function(son, tel, address) {
displayShopTooltip(son, tel, address);
}
My problem is in the last line of the above snippet: when I click on the mark, the parameters tel and address of the displayShopTooltip are undefined.
I'd like to know what to do to do not have this problem, or alternatively another approach.
Thanks in advance.
The problem you have is that by declaring tel and address within the listener you are setting them to undefined within the event handler callback (as there is no mechanism for passing in arbitrary parameters). The solution is to make sure the handler is within the same scope as (i.e. wrapped inside a function with) the marker. I'd recommend reading up about scope in javascript as there were one or two other errors you made (e.g. not properly declaring variables) - scope in javascript is handled very differently to other programming languages and takes a bit of getting used to.
function paintShops(shops){
for (var i = 0; i < shops.length; i++){
makeMarker(shops[i]);
}
function makeMarker(kl) {
var ka = parseFloat(kl.lat);
var kb = parseFloat(kl.lng);
var tel = kl.tel;
var address = kl.address;
var son = new google.maps.Marker({
position: new google.maps.LatLng(ka, kb),
map: map,
title: kl.name,
clickable: true
});
google.maps.event.addListener(son, 'click', function(son) {
displayShopTooltip(son, tel, address);
}
}
You have defined tel and address as parameters of your click handler, which doesn't make sense. You probably wanted to access your global variables instead:
google.maps.event.addListener(son, 'click', function(son) {
displayShopTooltip(son, tel, address);
}
Still, this way there can be only one shop (there is only one global tel and address). Use local variables and a closure instead:
function paintShops(shops) {
for (var i = 0; i < shops.length; i++) {
(function() {
var kl = shops[i];
var ka = parseFloat(kl.lat);
var kb = parseFloat(kl.lng);
var tel = kl.tel;
var address = kl.address;
var son = new google.maps.Marker({
position: new google.maps.LatLng(ka, kb),
map: map,
title: kl.name,
clickable: true
});
google.maps.event.addListener(son, 'click', function(son) {
displayShopTooltip(son, tel, address);
}
})();
}
}
The $.each() function is not the same as $(selector).each(), which is used to iterate, exclusively, over a jQuery object. The $.each() function can be used to iterate over any collection, whether it is a map (JavaScript object) or an array. In the case of an array, the callback is passed an array index and a corresponding array value each time. (The value can also be accessed through the this keyword, but Javascript will always wrap the this value as an Object even if it is a simple string or number value.) The method returns its first argument, the object that was iterated.

Categories

Resources