I'm attempting to create a simple weather API that replaces handlebars placeholder variables with user input of a city name linked to an api. It works very strangely, it will display the correct data after the next input is submitted. I.E. I submit "Dayton" and the placeholder data shows up again, then I submit "New York" and Dayton's correct info pops up. If I were to submit a third city, New York would display. Any ideas on why? Here's my code:
var currentWeather = {
cityName: "London",
temperature: 86,
description: 'cloudy'
};
var addCurrentWeather = function(data) {
var weather = {
cityName: data.name,
temperature: data.main.temp,
description: data.weather[0].main
}
};
var renderCurrentWeather = function () {
var weather= currentWeather;
var source = $('#weather-template').html();
var template = Handlebars.compile(source)
var weatherHTML = template(currentWeather);
$('#city').append(weatherHTML);
};
// fetch applying user input to grab data from api
var fetchCurrentWeather = function (query) {
$.ajax({
method: "GET",
url: "https://api.openweathermap.org/data/2.5/weather?q=" + query + "&APPID=MYKEY",
dataType: "json",
success: function (data) {
addCurrentWeather(data);
renderCurrentWeather();
currentWeather = {
cityName: data.name,
temperature: data.main.temp,
description: data.weather[0].main
}
},
error: function (jqXHR, textStatus, errorThrown) {
console.log(textStatus);
}
});
};
$('#search').on('click', function () {
var search = $('#search-query').val();
console.log(search);
fetchCurrentWeather(search);
});
renderCurrentWeather();
I will assume that you expect from your code to do following in the success handler.
Update your global weather object
Use that global object to render your template
That does not happen because your function addCurrentWeather does essentially nothing as it updates a local variable and discards it.
So make sure that this function instead updates the global variable.
var addCurrentWeather = function(data) {
currentWeather = {
cityName: data.name,
temperature: data.main.temp,
description: data.weather[0].main
}
};
and in the success handler you should have then only
addCurrentWeather(data);
renderCurrentWeather();
Reason why it then currently works the way it does is because after you call the render function you later update the global variable directly hence why this data is then used on next api fetch.
As a suggestion, try to avoid using global variables as it is easy to create bugs like this. Instead try to use pure functions as it will also help if you start unit test your code.
In your case have addCurrentWeather function accept data and return a weather object. And similarly have the render method accept weather object instead of reading it from global var.
Something like this
function getCurrentWeather(data) {
return {
cityName: data.name,
temperature: data.main.temp,
description: data.weather[0].main
}
};
function renderCurrentWeather(weather) {
var source = $('#weather-template').html();
var template = Handlebars.compile(source)
var weatherHTML = template(currentWeather);
$('#city').append(weatherHTML);
};
function fetchCurrentWeather(query) {
$.ajax({
method: "GET",
url: "https://api.openweathermap.org/data/2.5/weather?q=" + query + "&APPID=MYKEY",
dataType: "json",
success: function (data) {
const weather = getCurrentWeather(data);
renderCurrentWeather(weather);
},
error: function (jqXHR, textStatus, errorThrown) {
console.log(textStatus);
}
});
};
$('#search').on('click', function () {
var search = $('#search-query').val();
console.log(search);
fetchCurrentWeather(search);
});
fetchCurrentWeather('London');
Related
I'm working on a weather app, on top of having the option to get his geolocation the user can also input a city to get the weather info.
I've been stuck for a while, I'm trying to get the info of the weather using the input of the user, using my api request(which is working on codepen // I'm working on VSCode). Im using the same url of my geoLocation but changing it to taking the city instead of the lon and lat as input, but I always get a ERROr 400, but I can't seem to locate the source of my error.
// Get weather from input
function getVal() {
var val = document.querySelector('#search-city').value;
const url = 'https://api.openweathermap.org/data/2.5/weather?q=' + val + '&appid={API key}&units=metric';
$.ajax({
url: url,
type: "GET",
dataType: 'json',
success: (data) => {
$('#city').text(data.name)
$('#condition').text(data.weather[0].main);
$('h1').text(Math.round(data.main.temp));
backgroundChange(data.weather[0].main);
},
error: () => {
return false
}
});
};
// Get weather using location
function geoLocation() {
if ('geolocation' in navigator) {
navigator.geolocation.getCurrentPosition((position) => {
let lat = position.coords.latitude;
let long = position.coords.longitude;
let url = 'https://api.openweathermap.org/data/2.5/weather?lat=' + lat + '&lon=' + long + '&appid={API Key}&units=metric';
$.ajax({
url: url,
type: "GET",
dataType: 'json',
success: (data) => {
$('#city').text(data.name)
$('#condition').text(data.weather[0].main);
$('h1').text(Math.round(data.main.temp));
backgroundChange(data.weather[0].main);
},
error: () => {
return false
}
});
})
};
});
I would really appreciate your help !
Thank you very much!!
// RESOLVED
At first I created the function outside of my onlick event and the API Call was made even before the event was called, so I think the input couldn't make it's way to the URL.
Here is the modify version which is now working :
$('.search-icon').click(
function getVal() {
var city = document.querySelector('#search-city').value;
const url = 'https://api.openweathermap.org/data/2.5/weather?q='+city+'&appid=dc8c9152e8adaad0ec8bf635818c0d42&units=metric';
$.ajax({
url: url,
type: "GET",
dataType: 'json',
success: (data) => {
$('#city').text(data.name)
$('#condition').text(data.weather[0].main);
$('h1').text(Math.round(data.main.temp));
backgroundChange(data.weather[0].main);
},
error: () => {
return false
}
});
});
I believe in this part of API's url "ppid={API Key}" you are passing as a string. You should inform your API key in order to fetch the data.
Thanks for your responses but I found the solution ! Instead of creating an event using the function, I put the function in the event.
I have a CONTACT javascript object. I call CONTACT.load() to read the data for CONTACT.id through $.ajax(). On success, I am able to use the data returned by the ajax() call. Then I save some of the read data in object properties. However, the saved property values are lost. Here is my code:
var CONTACT=
{
id: 1,
referrals_loaded: false,
city: '',
};
CONTACT.load = function(cb)
{
$.ajax({
type: "POST",
url: "contactAjax.php",
data: {
ContactID: this.id,
actionID: 'LOAD_CONTACT_DATA',
},
dataType: "json",
success: function(data) {
success = true;
var address = data['address'];
var addr = address[0];
this.city = addr['city'].trim();
console.log("City (in ajax() ):" +this.city);
var province = addr['province'].trim();
// ...
if (typeof cb==='function') (cb)();
},
error: function () {
alert("Could not load Contact data through LOAD_CONTACT_DATA .");
}
});
console.log("City (after ajax() ):" +this.city);
}
My calling code is like this:
CONTACT.id = 123456;
CONTACT.load('testtest');
function testtest() {
console.log("Contact city is " + CONTACT.city);
CONTACT.city = "London";
console.log("Contact city is " + CONTACT.city);
}
And the console.log O/P is like this:
City (in ajax() ):MARKHAM
Contact city in testtest()
Contact city is London
Notice that when I set the value for CONTACT.city again in testtest(), the object retains the property value. Could someone please explain, why the CONTACT.city becomes empty when testest() is called?
'this' refers to a different object.
CONTACT.load = function(cb)
{
var self = this; // common hack
$.ajax({
type: "POST",
url: "contactAjax.php",
data: {
ContactID: this.id,
actionID: 'LOAD_CONTACT_DATA',
},
dataType: "json",
success: function(data) {
success = true;
var address = data['address'];
var addr = address[0];
self.city = addr['city'].trim(); // <-- assign to correct object
console.log("City (in ajax() ):" +this.city);
var province = addr['province'].trim();
// ...
if (typeof cb==='function') (cb)();
},
error: function () {
alert("Could not load Contact data through LOAD_CONTACT_DATA .");
}
});
console.log("City (after ajax() ):" +this.city);
}
I have two buttons that both performs AJAX call:
$("#save").click(function() {
$.ajax({
type: "POST",
url: saveEntryURL,
data: { id: $("#id").val() },
success: function(r) {
...
},
error: function(r) {
...
}
})
})
$("#tag-as-final").click(function() {
$.ajax({
type: "POST",
url: finalizeEntryURL,
data: { id: $("#id").val() },
success: function(r) {
...
},
error: function(r) {
...
}
})
})
The requirement is that when the user click the finalize button, the system will first perform a save before actually tagging it as final. To reuse the code attached to the save button, I call the onclick listener of the save button before the actual AJAX call like this:
$("#tag-as-final").click(function() {
$("#save").click()
^^^^^^^^^^^^^^^^^^
$.ajax({
type: "POST",
url: finalizeEntryURL,
But it will not do "save-and-finalize-after" behavior since both AJAX calls are asynchronous. I need to run one after another, but cannot afford to make the AJAX call of the save button synchronous (I'm doing also a lot of other things while the tagging occurs occurs). I know this would be silly but I'm thinking something similar to...
$("#tag-as-final").click(function() {
$("#save").click().peformAsyc()
^^^^^^^^^^^^
$.ajax({
type: "POST",
url: finalizeEntryURL,
...that will force it to finish performing first the chained function before continuing, but I know that is not available. Is there any way to do this? My current work-around is placing the same save AJAX function inside the finalize AJAX function, though it doesn't allow me to code DRY (Don't Repeat Yourself):
$("#tag-as-final").click(function() {
$.ajax({
type: "POST",
url: saveEntryURL,
data: { id: $("#id").val() },
success: function(r) {
...
$.ajax({
type: "POST",
url: finalizeEntryURL,
data: { id: $("#id").val() },
success: function(r) {
...
},
error: function(r) {
...
}
})
},
error: function(r) {
...
}
})
})
It's pretty simple, you are better using jquery "promises". Like so:
var generalSettings = { }; //Settings for AJAX call.
var jqXHR = $.ajax(generalSettings); //Do AJAX call.
generalSettings.data = 'newdata'; //update generalSettings
jqXHR.done(function(data){
$.ajax(generalSettings); //New Petition with updated settings.
});
This is using ES6 promises and jQuery promises:
function doAjaxAsPromise(settings){
return new Promise(function(resolve){
var jqXHR = $.ajax(settings);
jqXHR.done(function(data){
resolve(data);
});
});
}
var settings = { };
var petition = doAjaxAsPromise(settings);
var secondpetition = petition.then(function(data){
//work with data
//new settings
var settings = { };
return doAjaxAsPromise(settings);
});
var thirdpetition = secondpetition.then(function(data){
//work with data
//new settings
var settings = { };
return doAjaxAsPromise(settings);
});
//If needed to reuse settings object outside promise scope:
//var settings = Object.create(settings);
Some other nice thing you can do for code reuse:
function save(settings) {
var prom = doAjaxAsPromise(settings);
return prom.then(function(data){
//do something with your data.
});
}
function tagAsFinal(savedPromise, settings){
return savedPromised.then(function(){
var prom = doAjaxAsPromise(settings);
return prom.then(function(data){
//work with data;
});
});
}
$('save').on('click', function(){
save(settings); //settings = $.ajax settings.
});
$('tagAsFinal').on('click', function(){
var generalSettings = { };
var settingsone = Object.create(generalSettings);
var settingstwo = Object.create(generalSettings);
var saved = save(settingsone); //$.ajax settings.
tagAsFinal(saved, settingstwo);
});
//Can still be reduced.
As the title suggests I would like to load remote data once only.
I thought about loading a data with independent ajax call and set it "locally" at the control but wonder if there is more "built in" way to do so...
a solution can be found here:
https://github.com/ivaynberg/select2/issues/110
$("#selIUT").select2({
cacheDataSource: [],
placeholder: "Please enter the name",
query: function(query) {
self = this;
var key = query.term;
var cachedData = self.cacheDataSource[key];
if(cachedData) {
query.callback({results: cachedData.result});
return;
} else {
$.ajax({
url: '/ajax/suggest/',
data: { q : query.term },
dataType: 'json',
type: 'GET',
success: function(data) {
self.cacheDataSource[key] = data;
query.callback({results: data.result});
}
})
}
},
width: '250px',
formatResult: formatResult,
formatSelection: formatSelection,
dropdownCssClass: "bigdrop",
escapeMarkup: function (m) { return m; }
});
Edit:
I might have misinterpreted your question. if you wish to load all data once, then use that is Select2, there is no built in functionality to do that.
Your suggestion to do a single query, and then use that stored data in Select2 would be the way to go.
This is for Select2 v4.0.3:
I had this same question and got around it by triggering an AJAX call and using the data returned as the initialized data array.
// I used an onClick event to fire the AJAX, but this can be attached to any event.
// Ensure ajax call is done *ONCE* with the "one" method.
$('#mySelect').one('click', function(e) {
// Text to let user know data is being loaded for long requests.
$('#mySelect option:eq(0)').text('Data is being loaded...');
$.ajax({
type: 'POST',
url: '/RetrieveDropdownOptions',
data: {}, // Any data that is needed to pass to the controller
dataType: 'json',
success: function(returnedData) {
// Clear the notification text of the option.
$('#mySelect option:eq(0)').text('');
// Initialize the Select2 with the data returned from the AJAX.
$('#mySelect').select2({ data: returnedData });
// Open the Select2.
$('#mySelect').select2('open');
}
});
// Blur the select to register the text change of the option.
$(this).blur();
});
This worked well for what I had in mind. Hope this helps people searching with the same question.
To load data once:
Assumptions:
You have a REST API endpoint at /services that serves a JSON array of objects
The array contains objects which have at least a "name" and "id" attribute. Example:
[{"id": 0, "name": "Foo"}, {"id": 1, "name": "Bar"}]
You want to store that array as the global 'services_raw'
First, our function to load the data and create the global 'services_raw' (AKA 'window.services_raw'):
fetchFromAPI = function() {
console.log("fetchFromAPI called");
var jqxhr = $.ajax(
{
dataType:'json',
type: 'GET',
url: "/services",
success: function(data, textStatus, jqXHR) {
services_raw = data;
console.log("rosetta.fn.fetchServicesFromAPI SUCCESS");
rosetta.fn.refreshServicesSelect();
},
error: function(jqXHR, textStatus, errorThrown) {
console.log("Error inside rosetta.fn.fetchServicesFromAPI", errorThrown, textStatus, jqXHR);
setTimeout(rosetta.fn.fetchServicesFromAPI(), 3000); // retry in 3 seconds
}
}
)
.done(function () {
console.log("success");
console.log(jqxhr);
})
.fail(function () {
console.log("error");
})
.always(function () {
console.log("complete");
});
// Perform other work here ...
// Set another completion function for the request above
jqxhr.always(function () {
console.log("second complete");
});
};
Second, our Select2 instantiation code which transforms our data into a format that Select2 can work with:
refreshServicesSelect = function () {
// ref: http://jsfiddle.net/RVnfn/2/
// ref2: http://jsfiddle.net/RVnfn/101/ # mine
// ref3: http://jsfiddle.net/RVnfn/102/ # also mine
console.log('refreshServicesSelect called');
$("#add-service-select-service").select2({
// allowClear: true
data: function() {
var arr = []; // container for the results we're returning to Select2 for display
for (var idx in services_raw) {
var item = services_raw[idx];
arr.push({
id: item.id,
text: item.name,
_raw: item // for convenience
});
}
return {results: arr};
}
});
};
Here's what the Select2 element in HTML should look like before your call the above functions:
<input id="add-service-select-service" type="hidden" style="width:100%">
To use all of this, call (in JS):
window.fetchFromAPI();
window.refreshServicesSelect();
Lastly, here's a JSFiddle where you can play with a similar thing: http://jsfiddle.net/RVnfn/102/
Basically, in my example above, we're just using ajax to populate the equivalent of window.pills in the Fiddle.
Hope this helps :)
Please reply if you know how to do this via the Select2 .ajax function, as that would be a bit shorter.
In my condition, it is working perfectly with the given code
$('#itemid').select2({
cacheDataSource: [],
closeOnSelect: true,
minimumInputLength: 3,
placeholder: "Search Barcode / Name",
query: function(query) {
// console.log(query);
self = this;
var key = query.term;
var cachedData = self.cacheDataSource[key];
if(cachedData) {
query.callback({results: cachedData});
return;
} else {
$.ajax({
url: "./includes/getItemSelect2.php",
data: { value : query.term },
dataType: 'json',
type: 'POST',
success: function(data) {
self.cacheDataSource[key] = data;
query.callback({results: data});
}
});
}
},
});
And my data return from the ajax is in this form
<?php
$arr = [
["id" => 1, "text" => "Testing"],
["id" => 2, "text" => "test2"],
["id" => 3, "text" => "test3"],
["id" => 4, "text" => "test4"],
["id" => 5, "text" => "test5"]
];
echo json_encode($arr);
exit();
?>
I am sending json list from my controller:
public ActionResult LoadTree()
{
List<ListItem> list = new List<ListItem>() {
new ListItem() { Text = "Keyvan Nayyeri" },
new ListItem() { Text = "Simone Chiaretta" },
new ListItem() { Text = "Scott Guthrie" },
new ListItem() { Text = "Scott Hanselman" },
new ListItem() { Text = "Phil Haack" },
new ListItem() { Text = "Rob Conery" }
};
return new JsonResult { Data = list };
}
Trying to get the list in my view using:
var text =
$.ajax({
url: '/CourseCases/LoadTree',
dataType: 'json',
data: { },
cache: false,
type: 'GET',
success: function (data) {
}
});
alert(text);
I just get [object object]. How I can get the actual value of the object? Thanks in advance.
First you have to set the JsonRequestBehavior = JsonRequestBehavior.AllowGet in the JsonResult.
public ActionResult LoadTree()
{
List<ListItem> list = new List<ListItem>() {
new ListItem() { Text = "Keyvan Nayyeri" },
new ListItem() { Text = "Simone Chiaretta" },
new ListItem() { Text = "Scott Guthrie" },
new ListItem() { Text = "Scott Hanselman" },
new ListItem() { Text = "Phil Haack" },
new ListItem() { Text = "Rob Conery" }
};
return new JsonResult { Data = list, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}
<script type="text/javascript">
$.ajax({
url: '/Home/LoadTree',
dataType: 'json',
data: {},
cache: false,
type: 'GET',
success: function (data) {
alert(data.length); // 6
// do whatever with the data
}
});
</script>
in success function you have to parse json to get actual data e.g.
var jsObject = JSON.parse(data);
and then access each item like jsObject.List[0].Text etc
Simple problem here. In your controller, you're actually assigning the list to a variable named Data inside the response data collection. Just because your success function takes a data parameter doesn't mean that the Data value you set in your controller automagically will become the data variable.
As your Data list is inside the data object: you need to do:
data.Data
inside your success function. Try this:
success: function(data) {
alert(data.Data.length);
}
function $.ajax() does not return value from server, so var text = $.ajax() will not work. You need to look at success handler instead
success: function (data) {
// data is the result of your ajax request
}
I strongly recommend you to read more at jQuery.Ajax
success(data, textStatus, jqXHR) A function to be
called if the request succeeds. The function gets passed three
arguments: The data returned from the server, formatted according to
the dataType parameter; a string describing the status; and the jqXHR
(in jQuery 1.4.x, XMLHttpRequest) object. As of jQuery 1.5, the
success setting can accept an array of functions. Each function will
be called in turn. This is an Ajax Event.