here is the gist of my code: https://gist.github.com/tconroy/e52e0e7402face8f048e
Basically, my program is broken down into several steps:
retrieve user input from N number of inputs (user can add/remove)
perform AJAX query on each input, retrieving JSON formatted weather data for each.
on successful AJAX, pass the data to dataReady() function.
dataReady() function stores the data into a global Array[]
The problem is the AJAX data is not storing in the global array. how can I save the JSON response for use later in the program? I need to store all my weather data in one array, so I can iterate through it to create my graph later in the program.
The part causing issues in particular:
function getWeatherData(){
// set up query strings.
var queryBase = "http://api.worldweatheronline.com/free/v1/weather.ashx?q=",
queryEnd = "&format=json&num_of_days=5&key="+weatherAPIKey;
// iterate through each address input
$('.inp').each(function(){
// setup query
var inp = this;
var addr = encodeURIComponent( inp.value );
var query = queryBase + addr + queryEnd;
// perform query
$.ajax({
url: query,
async: false,
dataType: 'jsonp',
success: function(json){
// format our response data into object, push it into container array.
var objName = String(decodeURIComponent(addr));
var objVals = json.data.weather;
dataReady(objName, objVals);
},
error: function(){
alert(errMsg);
}
});
}); // end $('.inp').each();
// setup the graph
setupGraph();
} // end getWeatherData();
function dataReady(objName, objVals) {
console.log('dataReady() called.');
responseValues[objName] = objVals;
}
From what I understand (see comments) you are dealing with a typical problem with asynchronous calls. You call AJAX, then you call setupGraph() but the ajax response will arrive after that call, because it is asynchronous.
First of all, doing async: false is bad, wrong and the source of all evil. Don't use it never ever. In your case it won't even work, because you can't force JSONP to be synchronous. But even if you could let me repeat my self, because this is important: don't ever use async: false.
Now back to your problem. What you should is to use deferred callbacks instead:
var reqs = [];
$('.inp').each(function(){
// some code
reqs.push(
$.ajax({
// some settings
})
);
});
$.when.apply($, reqs).then(function() {
setupGraph();
});
Read more about $.when here: https://api.jquery.com/jQuery.when/
Related
I'm doing a webapp with html+jquery and a java rest-service backend.
I have a textfield, with typeahead suggestions, so every character the user types in the field
will trigger a server-round trip and update the list of typeahead suggestions.
Essential parts of the code:
var showTypeaheadSuggestions = function(data) {
// update ui-element ...
}
var displayFailure = function() {
// update ui-element ...
}
var searchText = $("#searchText");
var searchTextKeyup = function() {
var txt = searchText.val();
$.ajax({
url : typeaheadUrl(txt),
type : 'GET',
dataType : 'json',
}).done(showTypeaheadSuggestions).fail(displayFailure);
}
searchText.on('keyup', searchTextKeyup);
It's basically working.
But I was thinking abt what happens if you type, for example, 2 letters "ab" (that will trigger first a request for "a" and then a request for "ab")...
Then, what happens if the "a" response takes a bit longer to process, and arrives after the "ab" response?
Do I need to detect this in my code, to throw away the "a" response?
In http://api.jquery.com/jquery.ajax/ it does says:
Promise callbacks ā .done(), .fail(), .always(), and .then() ā are
invoked, in the order they are registered.
What does that mean exactly?
I was hoping this means $.ajax() would automatically handle the above scenario correct.
But when I do a small test (on the server-side I simply injected a 2 secs sleep-delay, only when the search-string is exactly "a"),
it turns out it does not behave as I expected.
The typeahead list will first get updated with the "ab" response, and then when the "a" response
arrives, it also updates, so the typeahead list gets the wrong suggestions.
What is the established way to handle this correctly?
There's another approach if you want to keep the server side code without changes. You can actually wrap the return functions inside a class and create instances for each request, then store the latest instance in a global scoped variable and check if the owner of the invoked method does match the latest instance:
var lastRequest;
var searchText = $("#searchText");
function requestClass()
{
var that = this;
this.showTypeaheadSuggestions = function(data) {
//Update ui-element
if (lastRequest == that)
console.log('suggestions retrieved: ' + data);
else
console.log('this response (' + data + ') is ignored');
};
this.displayFailure = function() {
//Update ui-element
console.log('failure');
};
}
var searchTextKeyup = function() {
var request = new requestClass();
lastRequest = request;
var txt = searchText.val();
$.ajax({
url : typeaheadUrl(txt),
type : 'GET',
dataType : 'json',
}).done(request.showTypeaheadSuggestions).fail(request.displayFailure);
}
searchText.on('keyup', searchTextKeyup);
I have tested this with the small-test you proposed in the question (adding a 2 seconds delay when the search string does match the 'a' character) and the result is the following:
suggestions retrieved: ab
this response (a) is ignored
One of the ways I approached this problem was to assign an ID for each time you call it, and pass it as an ID to server side. When your server is done processing it, it then sends the result back along with it's id.
Then, every time the client side code runs, the ID will increment. For example:
var callback_id = 0;
var searchText = $("#searchText");
var searchTextKeyup = function() {
callback_id ++;
var txt = searchText.val();
$.ajax({
url : typeaheadUrl(txt),
data : callback_id,
type : 'GET',
dataType : 'json',
}).done(showTypeaheadSuggestions).fail(displayFailure);
}
searchText.on('keyup', searchTextKeyup);
Then, when you receive the response back, you check if the id is the current one. In the event that the user fires two events at once, your ajax event will be triggered twice, once with callback_id = 0, and one with callback_id = 1.
The last thing you have to then do is an if statement only updating your TypeaheadSuggestions if the callback_id is the most current one by comparing the id sent back from your server response.
You must compare new input text with text you sent, and if it what user wants to find - you will show it, otherwise do nothing with response.
For example :
var searchText = $("input").val()
$.ajax({
....
data: {searchText : searchText}
success: funtion(){
if($("input").val()==searchText){
//SHOW RESULTS
}
}
})
The Promises interface returns to you a "Promise object" immediately so that you can use a different syntax for the callback.
Instead of:
asyncCall(callback);
You can use:
asyncCall()
.then(callback);
And you can chain these:
authorizeAccount()
.then(getNames)
.then(processNames);
The callbacks will be executed in the order you register them -- processNames will wait for getNames to resolve first.
The "established" way to handle this "correctly" is probably to add some client-side debouncing so that only the whole request ('query' instead of 'q', 'qu' 'que'...) is processed when you are typing the word:
http://underscorejs.org/#debounce with a timeout that expires a response if it takes too long to come back.
I want to store blocks of a json file into an array.
Here is my current code, in controller (ctrl) :
var ctrl = this;
var id = (location.href).replace(/.*\//g, ''); //use m.route() ?
ctrl.list = [];
m.request({method: "GET", url: "/data/"+id}).then(function(blocks){
blocks.map(function(block) {
ctrl.list.push(block);
});
});
console.log(ctrl.list); //Result : an empty array. Why ?
m.request is an asynchronous operation: it needs to make a request to the server, wait for the server to answer, load the contents, and then give you the response ā this is why it implements then: to give you a callback so you can do things with the data when it arrives.
But your console.log is happening immediately after you make the request: the response isn't ready yet. Anything that depends upon the server data needs to be invoked inside the then callback function.
controller: function() {
var response = m.prop(); return {
response: m.request({method:"GET",
url:"http://yourUrl", data:{date to send in Json format}}),
in the view
m("ul" , ctrl.response().map(function(folder){
return [
m("li" , folder.name),
Folder in this case is the Json that you accept from the server, suppose that you have this in the response ["name" : "Jhon"], with folder.name the value of your li will become Jhon.
Remember that m.request want at least method and url data it is not essential
I'm currently building a custom serialization function. This turns an array like this
{user_ids: [1,2,3], types: ['foo', 'bar']}
into a string like this:
ui-1,2,3,,ty-foo,bar
I have an XML file which tells me that "user_ids" maps to "ui", etc. I'm using XML2JSON to turn the XML file into something JavaScript can work with.
I want to store the abbreviations in local storage so the next time I need them I don't need to fetch the XML file or run the XML2JSON javascript again. However, I'm having trouble with getting everything to be executed in the correct order.
The abbreviation function can be called multiple times from various parts of the page. The first thing it does is that it checks whether the local storage is filled. If it's not, XML has to be loaded, XML2JSON has to be executed, the result has to be put in local storage, and only then I can continue.
Asset.javascript has an onLoad(), Request has onComplete, but they're not waiting nicely for eachother (seems that Asset.javascript is asynchronous no matter what).
I've tried to use Chain with callChain() in each function (so they only proceed once they have truly finished doing their part), but then I'm still stuck with having to return something - the result of the abbreviation function - which I can't figure out.
var Shorten = {
dict: [],
compact: function(args){
if(dict.length == 0){
// Check local storage
if(!localStorage.dict){
new Request({
url: 'shorten.xml',
contentType: 'application/xml',
method: 'get',
onSuccess: (function(rdf){
Asset.javascript('xml2json.js', {
id: 'xml2json',
onLoad: (function(){
var x2js = new X2JS();
var jsonObj = x2js.xml_str2json(rdf);
var dict = [];
jsonObj.parameters.alias.each(function(alias){
dict[alias._long] = alias._short;
});
localStorage.dict = dict;
})
});
})
}).send();
}
else{
this.dict = localStorage.dict;
}
}
else{
args.each(function(item){
// Check dictionary and replace with abbreviated version.
});
}
}
};
you cannot solve this in any meaningful way by returning immediately, you have 2 async methods to deal with.
you could refactor along the lines of (done in 5 mins but you get the callback idea):
var Shorten = {
getSchema: function(rdf, done){
Asset.javascript('xml2json.js', {
id: 'xml2json',
onLoad: function(){
var x2js = new X2JS(),
jsonObj = x2js.xml_str2json(rdf),
dict = {};
jsonObj.parameters.alias.each(function(alias){
dict[alias._long] = alias._short;
});
localStorage.setItem('dict', JSON.encode(dict));
done(dict);
}
});
},
compact: function(args, done){
var self = this,
dict = localStorage.getItem('dict'),
process = function(dict){
args.each(function(item){
// Check dictionary and replace with abbreviated version.
});
done();
};
if (!dict){
// Check local storage
new Request({
url: 'shorten.xml',
contentType: 'application/xml',
method: 'get',
onComplete: this.getSchema.bind(this, process)
}).send();
} else {
process(JSON.decode(dict));
}
}
};
Shorten.compact([{user_ids: [1,2,3], types: ['foo', 'bar']}], function(result){
console.log(result);
});
i've not had a chance to test this but it should get you started on the right path.
once it's been resolved, schema is actually stored in localStorage so subsequent calls will return nice and fast - you could also refactor to make the schema prequisite to returning your module and make it sync like var shortString = Shorten.compact(args); - but you need to nest the schema calls beforehand - if you know it will need to happen, you may as well.
you can also pre-load xml2json.js so you don't rely on Asset.javascript JIT lazy loading, meaning you can process things faster with just the one async call to get the schema. you could even make the ajax request synchronous (blocking UI) for the purpose of chaining and value immediate return, though callbacks / promises is a normal pattern for this kind of stuff
p.s. dict is an Object hash, not an Array
I am creating a basic piece of functionality to allow users to send their location to a server which then queries a database and returns locations near to them. I am using the below jQuery .ajax wrapper to POST data to the server. This takes the form of a latlon point which is then used as the basis for a geosearch in MongoDB using nodejs and express on the backend. The results of the search are then intended to be returned to the client and rendered by the createMapListings function.
The /find page is initially rendered through a GET request to the database via mongodb separate from the below code. However subsequent to initial rendering, I then want to return results dependent on the location provided.
The POST method works fine and the location is posted to the server, with the search results being returned as I can print contents out through the console log.
However, I then want to render the results on the client-side. As mentioned, the results of the search render in the console, but when I attempt to pass through to the client, I can render the data itself (in the form of an array of objects) in the #output div, but the createMapListings function does not seem to catch the data.
In fact, the below function appears to be called but prints out over a thousand rows with the data that should be caught described as 'undefined'. I have tried to use res.render and res.redirect, but in the first case, the view renders in the div (which I suppose is expected) and the redirect fails.
The createMapListings function works fine when a simple GET request is made to the server, for example, for all objects in a collection, using ejs template. However, I think the issue here may be a combination of a POST request and then wanting to pass the results back to the AJAX request using the complete callback.
I apologise if the below code is somewhat obtuse. Iām definitely what you would call a beginner. I appreciate the above functionality may not possible so if there is a better way, I would of course be open to it (res.direct perhaps).
Here is the relevant client side script:
$(document).ready(function(){
$("#geolocate").click(function(){
navigator.geolocation.getCurrentPosition(geolocate, function(){
});
});
});
function geolocate(pos){
var latlonpt = [];
var x = pos.coords.latitude;
var y = pos.coords.longitude;
latlonpt.push(x);
latlonpt.push(y);
var obj = {
userlocation: latitudelongitudept
};
$.ajax({
url: "/find",
type: "POST",
contentType: "application/json",
processData: false,
data: JSON.stringify(obj),
complete: function (data) {
$('#output').html(data.responseText);
$('#infooutput').children().remove();
createMapListings(data.responseText);
}
});
};
function createMapListings(maps) {
for (var i = 0; i < maps.length; i++) {
var url = maps[i]._id;
var fullurl = "<a href='/show?id=" + url + "'>Route</a></div>";
var title = "<div>" + maps[i].title + " - " + fullurl +"";
$('#infooutput').append(title);
};
};
</script>
Here is the relevant route used in a basic express app to handle the post request made by the above .ajax wrapper.
exports.findbylocation = function(req, res) {
console.log(req.body.userlocation);
var userlocation = req.body.userlocation;
Map.ensureIndexes;
Map.find({loc :{ $near : userlocation }}, function(err, maps) {
if (err) {
console.log(err)
}
else {
var jmaps = JSON.stringify(maps);
console.log(jmaps);
res.send(jmaps);
}
});
};
By convention, the data variable name in an $.ajax callback signature refers to the parsed HTTP response body. Since your callback is on complete, we're actually passed the XMLHttpRequest used, by convention called xhr. You rightly grab the responseText property, but this needs parsing to be useful. So long as we take care over our Content-Type's and don't explicitly disable processData, jQuery will do the encoding/unencoding for us - we just deal with objects. This is a good thing, since the transport format isn't usually of any particular importance to the application logic. If we use res.json(maps) in place of res.send(jmaps), we can write our call more simply:
$.ajax({
url: '/find',
type: 'POST',
data: obj,
success: function(data) {},
error: function(xhr, text, err) {}
});
Here data is a Javascript object already parsed and ready to use. We also use a default application/x-www-form-urlencoded request rather than explicitly setting a contentType. This is the same as far as express is concerned: it will just be parsed by urlencoded instead of json.
Assuming you solved your client-sie problem.
As you are using express there is no need for JSON.stringfy,
you can use res.json(maps).
I have a javascript function which needs to do a numerical calculation. Some of the numbers used in this calculation are stored in a database, and they will differ depending on how a user fills out an online form. Once the user fills out the form, they will click the CALCULATE button. At this time, in the JS function, I would like to use ajax to get values from a database that correspond to some other value chosen by the user.
For a simple example: there are 3 sizes of t-shirts, with different prices based on each size (stored in database). The user chooses the size, and when they click CALCULATE, I use ajax to get the price associated with the size they chose.
The question is, i want to use ajax to update some variables that I will use later on in the script. The way I am trying to do it now doesn't work, the variable in the script doesn't get updated from ajax, I can only access the value from the database inside the success function of the ajax call. I understand this is because ajax in asynchronous by nature, and it takes some time, waiting for the data to be returned from the server, while the function still continues to run
In the following example, the ajax call returns JSON data, and I have a function called isjson() that tests if the returned string is in fact JSON data.
Example code:
function calculate_cost(){
var price = 0;
var size = $('form#tshirt_form [name="size"] option:selected').val();
$.ajax({
url:'my_script.php',
type:'post',
data:'select=price&table=tshirts.prices&where=size = "' + size + '"',
success:function(data){
if(isjson(data)){
data = $.parseJSON(data);
data = data[0];
price = data['price'];
}else{
//display error getting data
}
}
});
// continue code for calculation
// this alert will display "0", but I want the price from the database in there
alert(price);
//perhaps do other ajax calls for other bits of data
//...
return final_price;
}
Does anyone know how I can accomplish this, updating variables with ajax in real-time??
Thanks a lot!
** EDIT **
Thanks everyone for the help, I understand about ajax being asynchronous. I would really like an answer where I don't have to continue the calculation inside the success function, because my actual problem involves many values from quite a few different tables. I would also like to be able to expand on the calculation in the future without it getting too convoluted. If this is not possible, ever, than i will have to live with that.
;-)
** EDIT 2 **
OK, we got the answer: of course it is right near the top on the docs page, :-/ sorry about that. The async property in jQuery ajax call. http://api.jquery.com/jQuery.ajax/
That is because ajax executes the request asynchronously by default and before the control reaches alert(price); the request has not yet executed and price still holds the old value.
If you want to execute it synchronously then you can set async to false.
$.ajax({
async: false,
.....
.....
});
you need to calculate inside the success function
function calculate_cost(){
var price = 0;
var size = $('form#tshirt_form [name="size"] option:selected').val();
$.ajax({
url:'my_script.php',
type:'post',
data:'query=select price from tshirts.prices where size = "' + size + '"',
success:function(data){
if(isjson(data)){
data = $.parseJSON(data);
data = data[0];
price = data['price'];
// continue code for calculation
// this alert will display "0", but I want the price from the database in there
alert(price);
//perhaps do other ajax calls for other bits of data
//...
}else{
//display error getting data
}
}
});
// return final_price; this function wont be able to return a value
}
ajax is asynchronous and for this reason you should refactor your code so that you do what you need to do in the callback
$.ajax({
url:'my_script.php',
type:'post',
data:'query=select price from tshirts.prices where size = "' + size + '"',
success:function(data){
if(isjson(data)){
data = $.parseJSON(data);
data = data[0];
price = data['price'];
//call another function (maybe make another ajax call) from here
dosomethingwithprice(price);
}else{
//display error getting data
}
}
});
Your ajax code takes time to execute (albeit not much); however the code after the ajax call is executed asynchronously, and most likely before the results of the ajax call come in.
Instead, why don't you try moving alert(price) into the body of the if(isjson(data)) region, and then execute a callback function which returns the price to whatever other utility you need it to be used at?
you have to do your calculation inside callback stack. Ajax work async, that means that, codes outsides callback function run before callback function start. So you should do your calculation in callback.
function calculate_cost(){
var price = 0;
var size = $('form#tshirt_form [name="size"] option:selected').val();
$.ajax({
url:'my_script.php',
type:'post',
data:'query=select price from tshirts.prices where size = "' + size + '"',
success:function(data){
if(isjson(data)){
data = $.parseJSON(data);
data = data[0];
price = data['price'];
}else{
//display error getting data
}
// continue code for calculation
// this alert will display "0", but I want the price from the database in there
alert(price);
//perhaps do other ajax calls for other bits of data
//...
return final_price;
}
});
}
I suggest having the ajax call return a deferred object. Then, when the final price is completely calculated, resolve the deferred object with that value.
function calculate_cost() {
var price = 0,
size = $('#tshirt_form [name="size"] option:selected').val(),
def = $.Deferred();
$.ajax({
data: {size:size},
url: 'my_script.php',
type: 'post',
dataType: 'json',
}).done(function(data){
data = data[0];
price = data['price'];
def.resolve(price);
}).fail(function(){
// do on error stuff
});
return def.promise();
}
// ...
calculate_cost("large").done(function(price){
alert(price);
}).fail(function(){
alert("Failed to retrieve price");
});