Javascript - getting the variable with value outside loop and function [duplicate] - javascript

This question already has answers here:
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
How to return value from an asynchronous callback function? [duplicate]
(3 answers)
Closed 10 months ago.
I am learning javascript and I create a project in which when a city is entered in the field, it will display its pollution level and what country it is in.
And the last thing is that after entering this city, it should automatically display marked with a marker on the map along with the level of pollution fetched from the Open AQ API.
I need to get variable city and air with data fetched from loop. And use variable city, with name of the searched city, in String format, outside function in another variable called query_addr. Thanks to this, the city will be immediately marked on the map.
Many thanks for any help.
Here is the whole project:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Laboratory Leaflet and OpenAQ</title>
<meta http-equiv="refresh" content="s">
<script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
<script src="https://unpkg.com/leaflet-geosearch#3.0.0/dist/geosearch.umd.js"></script>
<link rel = "stylesheet" href = "http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.css"/>
<script src = "http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.js"></script>
<link rel="stylesheet" href="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.css" />
<script src="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.js"></script>
</script>
</head>
<body>
<input id="mySearchField" name="search" placeholder="Search.." type="text">
<button id="mySearchButton">Search</button>
<div id="myContentArea"></div>
<script>
$(function() {
var _myContentArea = document.getElementById("myContentArea");
var _mySearchButton = document.getElementById("mySearchButton");
_mySearchButton.onclick = getData;
var city;
var air;
function getData(){
var _mySearchField = document.getElementById("mySearchField");
$.ajax({
url: "https://api.openaq.org/v1/cities?city="+_mySearchField.value,
method: "GET",
dataType: "json",
success: function(data) {
var str = "";
for(var i= 0; i < data.results.length; i++){
str +='City : '+data.results[i].city+' <br> Country : '+data.results[i].country+'<br>Count : '+data.results[i].count+'<br>';
city = data.results[i].city;
air = data.results[i].count;
}
_myContentArea.innerHTML = str;
}
});
}
var map = L.map( 'map', {
center: [ 51.5, -0.1],
zoom: 12
});
// Add tiles (streets, etc)
L.tileLayer( 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap',
subdomains: ['a','b','c']
}).addTo( map );
var query_addr = city;
const provider = new window.GeoSearch.OpenStreetMapProvider()
var query_promise = provider.search({ query: query_addr});
query_promise.then( value => {
for(i=0;i < value.length; i++){
var x_coor = value[i].x;
var y_coor = value[i].y;
var label = value[i].label;
var marker = L.marker([y_coor,x_coor]).addTo(map)
marker.bindPopup("<b>Found location</b><br>"+label+"The level of air pollution"+ air).openPopup();
};
}, reason => {
console.log(reason);
} );
});
</script>
<div id = "map" style = "width: 600px; height: 400px"></div>
</body>
</html>
I don't know how to make this variable outside the function and the loop have a value which is the name of the city from the api

Related

Minimise/optimized code for toggling layers in Leaflet JS Maps

I developed a map displaying a large number of datasets (around 70 different layers at this point) using leaflet. To toggle each of these layers I am using a Javascript function and switch case within that to add/remove layers based on checkbox status. To toggle 3 layers I had to write 6 case statements (two lines of code for each layer), for 70 layers in my primary project I had to write 140 case statements. Tried different things like loops, variable switching and others to reduce this code but couldn't crack it just wanted to know if there is an efficient way of doing this. I consciously named my layers to match the name with checkbox IDs and the argument passed when the function toggleLayer() from HTML is called to take advantage of it.
Below is a basic example of what I am doing in my primary project. Here is my HTML part of the code
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simple Example</title>
<link href="https://fonts.googleapis.com/css2?family=Nunito+Sans:wght#200&display=swap" rel="stylesheet">
<link rel="stylesheet"
href="https://unpkg.com/leaflet#1.8.0/dist/leaflet.css"
integrity="sha512-hoalWLoI8r4UszCkZ5kL8vayOGVae1oxXe/2A4AO6J9+580uKHDO3JdHb7NzwwzK5xr/Fs0W40kiNHxM9vyTtQ=="
crossorigin=""/>
<link rel="stylesheet" href="style.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
</head>
<body>
<main>
<div class="address-list">
<p>Use checkboxs to toggle layers</p><br>
<div class="inputs" id="years">
<input type="checkbox" id="layer_points_state" onclick='toggleLayer("layer_points_state")' checked="true">States<br>
<input type="checkbox" id="layer_points_territory" onclick='toggleLayer("layer_points_territory")' checked="true">Territories<br>
<input type="checkbox" id="layer_polygon" onclick='toggleLayer("layer_polygon")' checked="true">State Polygons
</div>
</div>
<div id="map"></div>
</main>
<!-- Leaflet js Packages/libraries -->
<script src="https://unpkg.com/leaflet#1.8.0/dist/leaflet.js"
integrity="sha512-BB3hKbKWOc9Ez/TAwyWxNXeoV9c1v6FIeYiBieIWkpLjauysF18NzgR1MBNBXf8/KABdlkX68nAhlwcDFLGPCQ=="
crossorigin=""></script>
<script src="./leaflet-providers.js"></script>
<!-- data files -->
<script src="state_points.js"></script>
<script src="state_polygons.js"></script>
<script src="script.js"></script>
</body>
</html>
and JS file
var osmMapHum = L.tileLayer.provider('OpenStreetMap.HOT');
var baseMaps = {'OSM Humanitarian':osmMapHum,}
var map = L.map('map', {
center: [-26.750867654966356, 136.22808234118338],
zoom:4.8,
worldCopyJump: false,
layers:[osmMapHum]
});
// Start of States Points
function filter_state(feature){
if (feature.properties.type === 'State')
return true
}
var layer_points_state = new L.GeoJSON(json_state_point, {filter: filter_state}).addTo(map);
function filter_territory(feature){
if (feature.properties.type === 'Territory')
return true
}
var layer_points_territory = new L.GeoJSON(json_state_point, {filter: filter_territory}).addTo(map);
// Start of States Polygon
var layer_polygon = new L.GeoJSON(json_state_polygon, {}).addTo(map);
var overlayMaps = {
"States": layer_points_state,
"Territories": layer_points_territory,
"State Polygon": layer_polygon,
};
var layerControl = L.control.layers(baseMaps, overlayMaps, {collapsed:false}).addTo(map);
//toggle layers with checkbox status
function toggleLayer(layer_toggled){
id_to_check = '#' + layer_toggled
const cb = document.querySelector(id_to_check);
if (cb.checked === true){
switch (layer_toggled){
case 'layer_points_state': return map.addLayer(layer_points_state);
case 'layer_points_territory': return map.addLayer(layer_points_territory);
case 'layer_polygon': return map.addLayer(layer_polygon);
}
}else{
switch (layer_toggled){
case 'layer_points_state': return map.removeLayer(layer_points_state);
case 'layer_points_territory': return map.removeLayer(layer_points_territory);
case 'layer_polygon': return map.removeLayer(layer_polygon);
}
}
}
This is how the map looks
How Project looks
Thanks in advance for the suggestions
You can create the html and the logic dynamically. Just add in layers your wanted filters.
var layerContainer = document.getElementById('years');
var layers = [
{
filter: 'State',
displayName: 'States'
},
{
filter: 'Territory',
displayName: 'Territories'
}
];
var overlays = {};
function prepareFilter(filterType){
return function(feature) {
if (feature.properties.type === filterType)
return true
}
}
//toggle layers with checkbox status
function toggleLayer(e){
var elem = e.target;
var leafletId = parseInt(elem.id);
var layerObj = layers.find(x=>x.id === leafletId);
if(!layerObj){
console.error('Layer '+leafletId+' not found!');
return;
}
var layer = layerObj.layer;
if(!elem.checked){
layer.removeFrom(map);
} else {
layer.addTo(map);
}
}
layers.forEach((layerObj)=>{
var geojsonLayer = new L.GeoJSON(json_state_point, {filter: prepareFilter(layerObj.filter)}).addTo(map);
// to hide inputs which are not found
if(geojsonLayer.getLayers().length === 0){
return;
}
overlays[layerObj.filter] = geojsonLayer;
layerObj.id = L.stamp(geojsonLayer);
layerObj.layer = geojsonLayer;
var input = document.createElement('input');
input.type = 'checkbox';
input.id = L.stamp(geojsonLayer); //unique leaflet id of the layer
input.checked = true;
layerContainer.appendChild(input);
L.DomEvent.on(input, 'click', toggleLayer);
var label = document.createElement('label');
label.innerHTML = layerObj.displayName;
label.htmlFor = input.id;
layerContainer.appendChild(label);
layerContainer.appendChild(document.createElement('br'));
});
var layer_polygon = new L.GeoJSON(json_state_polygon, {}).addTo(map);
overlays["State Polygon"] = layer_polygon;
var layerControl = L.control.layers({}, overlays, {collapsed:false}).addTo(map);
https://plnkr.co/edit/ddYaazIpSkbZKplC

Use JavaScript Objects with jquery ui autocomplete

I use jquery ui autocomplete on a form to autocomplete city and postal code from a text file.
The text file is formatted like this :
FR;24108;Bergerac
FR;24109;Bergerac
FR;24110;Léguillac-de-l'Auche
FR;24110;Saint-Léon-sur-l'Isle
FR;24110;Manzac-sur-Vern
The autocomplete works on the corresponding field but I would like when user choose a postal code to fill automatically the city in the field city.
And this is where where it doesn't work. I've tried to create an object with label / value :
autoCompleteData.push({cp: values[1]+'', city: values[2] + ''})
If I do a console.log(), I can see my object but I have difficulty to use it in order to use it in response needed by jquery ui autocomplete. I've seen some examples based on json response but I don't know how to adapt this to my needs. I've also tried to convert my object to json but doesn't work.
Could your explain me how to do this ?
Here is my working code
$.ajax({
url: "path-to-file/ZipCodes.txt?id=1",
dataType: "text",
success: function (data) {
var autoCompleteData = data.split('\n');
var lines = data.split(/\r\n|\n/);
//Set up the data arrays
var autoCompleteData = Array();
for (var j = 0; j < lines.length; j++) {
var values = lines[j].split(';'); // Split up the comma seperated values
//postcodes.push(values[1], values[2]);
autoCompleteData.push(values[1] + '');
//autoCompleteData.push({cp: values[1], city: values[2] + ''});
//console.log(autoCompleteData[0][1]);
$("#edit-code-postal").autocomplete({
source: function (request, response) {
var results = $.ui.autocomplete.filter(autoCompleteData, request.term);
response(results.slice(0, 10)); // Display the first 10 results
},
// We fill the city field
select: function (event, ui) {
// here I need help to use my object
}
});
}
});
And a working snippet without Ajax since I can't load my file on SO. I juste use an array instead :
$(document).ready(function() {
var data = [
"FR;24001;Périgueux",
"FR;24002;Périgueux",
"FR;24100;Saint-Laurent-des-Vignes",
"FR;24100;Lembras",
"FR;24100;Bergerac"
];
//var autoCompleteData = data.split('\n');
var lines = data;
//Set up the data arrays
var data1 = [];
var data2 = [];
var data3 = [];
var autoCompleteData = Array();
//var headings = lines[0].split(';'); // Splice up the first row to get the headings
for (var j = 0; j < lines.length; j++) {
var values = lines[j].split(';'); // Split up the comma seperated values
//postcodes.push(values[1], values[2]);
autoCompleteData.push(values[1] + '');
//autoCompleteData.push({cp: values[1], city: values[2] + ''});
}
//console.log(autoCompleteData[0][1]);
$("#edit-code-postal").autocomplete({
source: function(request, response) {
var results = $.ui.autocomplete.filter(autoCompleteData, request.term);
response(results.slice(0, 10)); // Display the first 10 results
},
// On remplit aussi la ville
select: function(event, ui) {
$('#edit-ville').val(ui.item.city);
}
});
});
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>jQuery UI Autocomplete</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.0/jquery-ui.min.js"></script>
</head>
<body>
<div class="ui-widget">
<label for="tags">Postal code (try "24...": </label>
<input id="edit-code-postal">
<label for="tags">City: </label>
<input id="edit-ville">
</div>
</body>
</html>
}
//console.log(autoCompleteData[0][1]);
$("#edit-code-postal").autocomplete({
source: function (request, response) {
var results = $.ui.autocomplete.filter(autoCompleteData, request.term);
response(results.slice(0, 10)); // Display the first 10 results
},
// On remplit aussi la ville
select: function (event, ui) {
$('#edit-ville').val(ui.item.city);
}
});
}
});
You can push two values in autoCompleteData one will be label which we will be using in searching input field values and other any variable i.e : in below code i have use data: values[2] then we get this value and apply to your textbox using $('#edit-ville').val(ui.item.data);
Demo code :
$(document).ready(function() {
var data = [
"FR;24001;Périgueux",
"FR;24002;Périgueux",
"FR;24100;Saint-Laurent-des-Vignes",
"FR;24100;Lembras",
"FR;24100;Bergerac"
];
//var autoCompleteData = data.split('\n');
var lines = data;
//Set up the data arrays
var autoCompleteData = Array();
for (var j = 0; j < lines.length; j++) {
var values = lines[j].split(';'); // Split up the comma seperated values
autoCompleteData.push({
label: values[1],
data: values[2]
});
}
//console.log(autoCompleteData[0][1]);
$("#edit-code-postal").autocomplete({
source: function(request, response) {
var results = $.ui.autocomplete.filter(autoCompleteData, request.term);
response(results.slice(0, 10)); // Display the first 10 results
},
// On remplit aussi la ville
select: function(event, ui) {
$('#edit-ville').val(ui.item.data);//setting value in textfield
}
});
});
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>jQuery UI Autocomplete</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.0/jquery-ui.min.js"></script>
</head>
<body>
<div class="ui-widget">
<label for="tags">Postal code (try "24...": </label>
<input id="edit-code-postal">
<label for="tags">City: </label>
<input id="edit-ville">
</div>
</body>
</html>

Value from Google GeoCode API Won't Return Properly

I've been struggling with this one error for a project I'm doing in a web design class for about a day now and I am nowhere close to figuring out how to solve it. For the project, I'm using a combination of two API's to read in a user specified location, get the latitude and longitude of that location using the Google GeoCoding API, and then using that latitude and longitude display the current weather of that location. The problem is, no matter what I do, the function I made to return a Location object (name, latitude, and longitude) to the main index.html file script never returns properly, and I believe it might be because it is running asynchronously. I'm a bit new to javascript, so my apologies in advance if I ask a lot of questions to your answers.
Here's the code from my location.js file:
class Location
{
constructor(name = "Dummy Location", lat = 0.0, lng = 0.0)
{
this.name = name;
this.lat = lat;
this.lng = lng;
}
}
function geocode(location)
{
axios.get('https://maps.googleapis.com/maps/api/geocode/json?',
{
params: {
address: location,
key: googleAPIKey
}
})
// Get a reponse from that request
.then(function(response)
{
///Format the address
var formattedAddress = response.data.results[0].formatted_address;
// Get the various address components
var locationName = `${response.data.results[0].address_components[0].long_name}, ${response.data.results[0].address_components[2].short_name}`;
var locationLat = response.data.results[0].geometry.location.lat;
var locationLng = response.data.results[0].geometry.location.lng;
// Create a new Location based on those components
var geoLocation = new Location(locationName, locationLat, locationLng);
// (Debug) Log that location
console.log(geoLocation);
// Return that location
return geoLocation;
})
.catch(function(error)
{
console.log(error);
})
}
And here's the code from my index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Cool Weather App</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<link rel = "stylesheet" href="css/styles.css">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
<header>
<div id = "locationinputcontainer">
<input id="locationfield" type="text" value="Location">
<input id = "submitbutton" type="submit" value="Submit">
<input id = "savelocationbutton" type = "submit" value = "Save Location">
</div>
</header>
<div id = "locationweathercontainer">
<h1 id = "locationname">Dummy Value</h1>
<h2 id = "weather">Weather</h2>
<h1 id = "temperature">68°F</h1>
<h3 id = "precipitation">Precipitation:</h3>
<h3 id = "humidity">Humidity: </h3>
<h3 id = "windspeed">Wind Speed: </h3>
</div>
<div id = "mylocationscontainer" class = "dropdowncontainer" style = "float:left">
<h1>My Locations</h1>
<ol>
</ol>
</div>
<div id = "settingscontainer" class = "dropdowncontainer" style = "float:right">
<h1>Settings</h1>
</div>
</body>
<script src = "js/location.js"></script>
<script>
const locationField = document.querySelector("#locationfield");
const submitButton = document.querySelector("#submitbutton");
const saveButton = document.querySelector("#savelocationbutton");
const locationNameElement = document.querySelector("#locationname");
const locationsListElement = document.querySelector("#mylocationscontainer");
const prefix = "asb9599-";
const storedLocationsKey = prefix + "storedLocations";
const storedCurrentLocationKey = prefix + "storedCurrentLocation";
let currentLocation;
let locationInput;
/* Functions */
function UpdateCurrentLocation()
{
currentLocation = geocode(locationInput);
console.log("(index.html) Current Location: " + currentLocation);
locationNameElement.innerHTML = currentLocation.name;
}
/* Events */
submitButton.onclick = UpdateCurrentLocation;
locationField.onchange = e => {locationInput = e.target.value};
</script>
</html>
Andddd here's the response I'm getting:
Sample Response Image
params: {
address: location,
key: googleAPIKey(paste your apikey here)
}
Get the api key from google and paste it in the place of key.Otherwise it won't work.

Linking Google Script Variable to the Javascript in HTML

So I feel I'm almost there to the solution but I'm really in need of help here. What I'm trying to do is to create an array using .getValues() to get a range that contains four columns (Name, Address, Latitude, and Longitude). After that I want to return the variable back into a global variable and then call that variable from the HTML side. I tried linking the google script with the HTML and then calling the variable there but having quite a bit of trouble with that. Thank you guys for all of your help!
Below is the google script:
var id = 'Spreadsheet Key';
function doGet(e) {
var html = HtmlService.createTemplateFromFile('Sample');
return html.evaluate().setTitle('Directory Map');
}
function entries() {
var blop =
SpreadsheetApp.openById(id).getSheetByName('Sheet1').getRange('A1:D').getValues();
return blop;
}
This is the HTML in Google Script.
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<input id="pac-input" class="controls" type="text" placeholder="Search Box">
<div id="map"></div>
<script>
function initAutocomplete() {
var map = new google.maps.Map(document.getElementById('map'), {
center: {lat: 39.8283, lng: -98.5795},
zoom: 5,
mapTypeId: 'roadmap',
gestureHandling: 'greedy'
});
var locations = [blop];
for (var i = 0; i < locations.length; i++) {
var sites = locations[i];
var myLatLng = new google.maps.LatLng(sites[2],sites[3]);
var sites = new google.maps.Marker({
position: myLatLng,
map: map,
title: sites[0],
});
};
}
</script>
<script> google.script.run.entries(); </script>
<script src="https://maps.googleapis.com/maps/api/js?key=MyAPIKey&libraries=places&callback=initAutocomplete"async defer></script>
<script src="https://maps.google.com/maps/api/js?sensor=true"></script>
<script type="text/javascript" src="SampleCode.gs"></script>
</body>
</html>
The starting point is:
<script> google.script.run.entries(); </script>
The above code runs when the page is loaded in the browser. You need a "success handler", and then the success handler can store the data somewhere. You could put the data into a window variable, or local browser storage.
<script>
window.storeSheetValues = function(theReturnedData) {
console.log('theReturnedData: ' + theReturnedData)
console.log('typeof theReturnedData: ' + typeof theReturnedData)
window.mySheetData = theReturnedData;//Put the data into a window variable
}
google.script.run
.withSuccessHandler(storeSheetValues)
.entries();
</script>
Check the data type of the return value coming back from the server. If it's a string, you may want to turn it back into an array.

Passing Variables by Reference in JavaScript

I am trying to display and center a map for the users current location. Everything works fine if I manually enter a hard coded latitude and longitude, but these needs to be dynamic as one user often changes location.
I suspect I am making a basic mistake, but my logic seems like it is correct to me. Please check my work and let me know what I am doing wrong? The line that is remarked out with Latitude and Longitude is the line I want to use instead of the previous line with the hard coded values.
<!DOCTYPE html>
<html>
<head>
<title>W123</title>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8'/>
</head>
<body>
<div id='printoutPanel'></div>
<div id='myMap' style='width: 100vw; height: 100vh;'></div>
<script type='text/javascript'>
function showlocation() {
navigator.geolocation.getCurrentPosition(getLocation);
}
function getLocation(position) {
var latitude = position.coords.latitude;
var longitude = position.coords.longitude;
}
function loadMapScenario() {
var mapOptions = {
credentials: 'My API key code goes here',
center: new Microsoft.Maps.Location(39.1887643719098, -92.8261546188403),
//center: new Microsoft.Maps.Location(latitude, longitude),
mapTypeId: Microsoft.Maps.MapTypeId.road,
zoom: 8
};
var map = new Microsoft.Maps.Map(document.getElementById('myMap'), mapOptions);
var urlTemplate = 'http://mesonet.agron.iastate.edu/cache/tile.py/1.0.0/nexrad-n0q-{timestamp}/{zoom}/{x}/{y}.png';
var timestamps = ['900913-m50m', '900913-m45m', '900913-m40m', '900913-m35m', '900913-m30m', '900913-m25m', '900913-m20m', '900913-m15m', '900913-m10m', '900913-m05m', '900913'];
var tileSources = [];
for (var i = 0; i < timestamps.length; i++) {
var tileSource = new Microsoft.Maps.TileSource({
uriConstructor: urlTemplate.replace('{timestamp}', timestamps[i])
});
tileSources.push(tileSource);
}
var animatedLayer = new Microsoft.Maps.AnimatedTileLayer({ mercator: tileSources, frameRate: 500 });
map.layers.insert(animatedLayer);
}
</script>
<script type='text/javascript' src='http://www.bing.com/api/maps/mapcontrol?branch=experimental&callback=loadMapScenario' async defer></script>
</body>
</html>
You want to pass in the latitude and longitude into your loadMapScenario function as seen below
function loadMapScenario(latitude,longitude) {
....your code here....
}
Change your callback in the bing map include to a new function like "mapUserLocation" then have mapUserLocation perform the following tasks
function mapUserLocation() {
// code here to get the latitude and longitude from users position
loadMapScenario(latitude,longitude);
}

Categories