I have an function inside it i am using $.each method. I want to call another function alertMsg() after $.each completely executed. But when i use breakpoints i can see that before finishing the $.each method it executes the alertMsg function. why? how to solve it.
function test(hospitalJson,blockJson){
$.each(hospitalJson.organisationUnits, function (i, curr_hos) {
if (curr_hos.id == orgUnit.id) {
var stringPath=[];
stringPath= curr_hos.path.split("/");
outerloop:
for(var i=0;i<stringPath.length;i++){
for(var j=0;j<blockJson.length;j++){
if(stringPath[i]==blockJson[j].id){
document.getElementById('blockID').innerHTML = blockJson[j].id;
break outerloop;
}
}
}
// to get district name
var districtNameURL="../api/organisationUnits.json?fields=name&filter=id:in:[" + curr_hos.path.split("/")[4] + "]" ;
$.get(districtNameURL,function(district){
districtName=district.organisationUnits[0].name;
console.log(districtName);
document.getElementById('districtID').innerHTML = districtName;
});
alertMsg = 1;
return false;
}
});
//this message execute before finishing $.each
alert(alertMsg);
}
Due to the fact that $.each has multiple AJAX calls inside, you need to create an array containing Promise objects that need to be resolved . Since you may not know the exact size of the parsed JSON object and jQuery $.when cannot handle arrays you need to extend it's functionality.
function test(hospitalJson, blockJson) {
var deferreds = [];
$.each(hospitalJson.organisationUnits, function(i, curr_hos) {
//...
deferreds.push(
$.get(districtNameURL, function(district) {
districtName = district.organisationUnits[0].name;
console.log(districtName);
document.getElementById('districtID').innerHTML = districtName;
}));
}
return deferreds;
});
}
var resolveData = test(hospitalJson, blockJson);
$.when.apply(null, resolveData).done(function() {
alert(alertMsg);
});
JSfiddle demo
Change:
$.get(districtNameURL,function(district){
districtName=district.organisationUnits[0].name;
console.log(districtName);
document.getElementById('districtID').innerHTML = districtName;
});
To:
$.ajax({
url: districtNameURL,
type: "GET",
async: false
})
.success(function (district) {
districtName = district.organisationUnits[0].name;
console.log(districtName);
document.getElementById('districtID').innerHTML = districtName;
});
This will stop the get action being asynchronous and therefore your logic will be processed in the expected order.
Related
Is it possible to stop the execution of a previous event when the event is called again?
To clarify, I have a button <button onclick='load()'>load</button> that calls a load() function which gets an array, processes each element and displays it in a list <ul id='main'></ul>
function load(event) {
$("#main").empty(); //empty old elements
$.get("load.php", '', function (data) {
var arr = JSON.parse(data);
for (i = 0; i < arr.length; ++i) {
process(arr[I]); //process and append to #main
}
});
}
Problem is, that if I click the button again while its still putting the elements into the array, I get the new list plus the rest of the old list.
Is there a way to stop the first event while its still executing but still execute the second event?
You should try this:
var xhr;
function load(ev){
if(ev.eventPhase === 2){
if(xhr)xhr.abort();
$('#main').empty();
xhr = $.get('load.php', function(data){
var a = JSON.parse(data);
for(var i=0,l=a.length; i<l; i++){
process(a[i]);
}
});
}
}
I can be wrong, but...
var req = $.ajax({
$("#main").addEventListener("click",()=>{req.abort()})
...
...
$("#main").removeEventListener("click",()=>{req.abort()})
});
As noted, you can stop the event by setting a flag and checking it, but a better approach would simply be to assign the new value directly. If your code works it means JSON.parse is returning an array already.
That means
"use strict";
(function () {
function load(event) {
$("#main").empty();
$.get("load.php", '', function (data) {
process = JSON.parse(data);
$("#main").whateverMethodFillsTheElement(process);
});
}());
Also, when writing asynchronous JavaScript code that makes HTTP requests, promises are preferred to callbacks. Since $.get returns a Promise you can write
"use strict";
(function () {
function load(event) {
$("#main").empty();
$.get("load.php")
.then(function (data) {
var items = JSON.parse(data);
$("#main").whateverMethodFillsTheElement(items);
});
}
}());
As discussed in comments, the aim is to use each item in another request which provides the actual value to add to 'main'. So loading data triggers an asynchronous call for each loaded item.
To accommodate this, we need to determine a key field that we can use to track each item so we do not append existing items to the list. We will call this field id for the sake of exposition.
"use strict";
(function () {
var allItems = [];
function load(event) {
$("#main").empty();
$.get("load.php")
.then(function (data) {
return JSON.parse(data);
})
.then(function (items) {
items.forEach(item => {
processItem(item)
.then(function (processed) {
var existingItem = allItems.filter(i => i.id === item.id)[0];
if(existingItem) {
var existingIndex = allItems.indexOf(existingItem);
allItems[existingIndex] = processed;
}
else {
allItems.push(processed);
}
});
});
});
}
}());
Ok, seems like it's not possible to stop an Ajax success function after it began executing or to stop a past event without aborting the current one.
But the following solution worked for me so I figured I'll post it here:
var num = 0;
function load() {
var curNum = ++num;
$("#main").empty();
$.get("load.php", '', function (data) {
var arr = JSON.parse(data);
for (i = 0; i < arr.length; ++i) {
process(arr[i], curNum);
}
});
}
function process(item, curNum) {
if(curNum === num) { //don't process if a new request has been made
//get 'data' based on 'item'...
if(curNum === num) { //check again in case a new request was made in the meantime
$("#main").append(data);
}
}
}
I appreciate everyone's help.
I was wondering if there is a way to pull and use JSON data from two different sources. Currently, the code looks like this:
//JSON1
$.getJSON('url1',function(data){
$.each(data,function(key,val){
//code
});
});
//JSON2
$.getJSON('url2',function(data){
$.each(data,function(key,val){
//code
});
});
When I do this, i seems that variables created from one JSON function aren't available in the other one, which makes it hard for them to be useful together.
Is there a better way to have these two work together?
This function takes an array of urls and a callback as parameters:
function getMultiJSON(urlList,callback) {
var respList = {};
var doneCount = 0;
for(var x = 0; x < urlList.length; x++) {
(function(url){
$.getJSON(url,function(data){
respList[url] = data;
doneCount++;
if(doneCount === urlList.length) {
callback(respList);
}
});
})(urlList[x]);
}
}
You would use it like this:
getMultiJSON(['url1','url2'],function(response) {
// in this case response would have 2 properties,
//
// response.url1 data for url1
// response.url2 data for url2
// continue logic here
});
You might want to add a timeout as the function will never call your handler should any of the URLs fail to load
Variable declared within the functions using var (or blocks, using let) are not available outside of the functions (or blocks).
$.getJSON('url1',function(data){
$.each(data,function(key,val){
var only_accessible_here = key;
});
});
So if you want variables that are accessible outside the scope of the function they are declared in, you need to declare them outside of the function they are used in.
var combined_stuff = ''
$.getJSON('url1',function(data){
$.each(data,function(key,val){
combined_stuff += val;
});
});
//JSON2
$.getJSON('url2',function(data){
$.each(data,function(key,val){
combined_stuff += val;
});
});
As Marc B says, there is no way to know which order the combined_stuff variable will be updated, either by JSON1 first, or by JSON2 first, or by only one, if one of the getJSON calls fail, or by neither if both fail.
If the order of updating is important, call the one you want to use second in the function of the one you want to call first.
var combined_stuff = ''
$.getJSON('url1',function(data){
$.each(data,function(key,val){
combined_stuff += val;
//JSON2
$.getJSON('url2',function(data){
$.each(data,function(key,val){
combined_stuff += val;
});
});
});
});
Easily using the open source project jinqJs (http://www.jinqJs.com)
var data1 = jinqJs().from('http://....').select();
var data2 = jinqJs().from('http://....').select();
var result = jinqJs().from(data1, data2).select();
The example does a sync call, you can do an async call by doing something like this:
var data1 = null;
jinqJs().from('http://....', function(self){ data1 = self.select(); });
Result will contain both results combined.
If you control the endpoint, you could make it return all of the data you want in one shot. Then your data would look like:
{
"url1_data": url1_json_data,
"url2_data": url2_json_data
}
If you still have 2 endpoints you need to hit, you can pass the result of your first ajax call to the second function (but this makes your 2 ajax calls synchronous):
function getJson1(){
$.getJSON('url1',function(data){
getJson2(data);
});
}
function getJson2(json1Data){
$.getJSON('url2',function(data){
//Do stuff with json1 and json2 data
});
}
getJson1();
I would recommend you to use $.when function available in jquery to execute both the methods in parallel and then take the action. See the code snipped below,
var json1 = [], json2 = [];
$.when(GetJson1(), GetJson2()).always(function () {
//this code will execute only after getjson1 and getjson2 methods are run executed
if (json1.length > 0)
{
$.each(json1,function(key,val){
//code
});
}
if (json2.length > 0)
{
$.each(json2,function(key,val){
//code
});
}
});
function GetJson1()
{
return $.ajax({
url: 'url1',
type: 'GET',
dataType: 'json',
success: function (data, textStatus, xhr) {
if (data != null) {
json1 = data;
}
},
error: function (xhr, textStatus, errorThrown) {
json1 = [];//just initialize to avoid js error
}
}
function GetJson2()
{
return $.ajax({
url: 'url2',
type: 'GET',
dataType: 'json',
success: function (data, textStatus, xhr) {
if (data != null) {
json2 = data;
}
},
error: function (xhr, textStatus, errorThrown) {
json2 = [];//just initialize to avoid js error
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
The returned data from each AJAX call are not available outside its own callback function. I'm sure there are more elegant (complex?) solutions, but a couple of simple, Occamic, solutions include global variables, or storing the received data in hidden input elements.
Within each callback function, just loop until the data from the other call is present:
function getJson1(){
$.getJSON('url1',function(data){
var d2 = '';
$('#hidden1').val(data);
while ( d2 == '' ){
//you should use a time delay here
d2 = $('#hidden2').val();
}
getJson2();
});
}
function getJson2(){
$.getJSON('url2',function(d2){
var d1 = '';
$('#hidden2').val(d2);
while ( d1 == '' ){
//you should use a time delay here
d1 = $('#hidden1').val();
}
//Do stuff with json1 and json2 data
});
}
getJson1();
Have been looking into getting callbacks for ajax() responses using $.when I'm still unsure how this works fully but this is what I would like the below to do.
When a user adds a town and country per line, it goes to the url in the .ajax() I get a response and it pushes the array to be usable outside of the .each() loop.
At the moment you will see inside here at jsbin that when the button is pressed firstly the response in console.log is [] then when I press it again the addresses show up. then a 3rd press will add the addresses again which shouldn't happen.
jQuery
var addresses,town;
var arrayLocation = [];
$('button').click(function(){
addresses = function() {
deferred = new $.Deferred();
var arrayOfLines = $('#gps').val().split('\n');
$.each(arrayOfLines, function(index, item) {
town = item.split(',');
$.ajax({
url: 'http://maps.googleapis.com/maps/api/geocode/json?address='+town[0]+'&sensor=false',
dataType: 'json',
success: function (data) {
add = data.results[0].address_components[0].long_name;
lat = data.results[0].geometry.location.lat;
lng = data.results[0].geometry.location.lng;
arrayLocation.push("['"+add+"', "+lat+", "+lng+"]");
console.log("['"+add+"', "+lat+", "+lng+"]");
}
});
});
return arrayLocation;
};
$.when(addresses()).then(function(arrayLocation){
console.log(arrayLocation);
});
});
You are not using $.when correctly. The main problem is that the addresses function returns a bare array. It's true that this array will be populated in the future when an async operation (the set of AJAX calls) completes, but from the point of view of addresses's caller this is impossible to know. Therefore the caller has absolutely no chance of reacting to the operation being completed.
What you would normally do is return the return value of $.ajax to the caller, somewhat like this:
addresses = function() {
return $.ajax({ ... });
};
The caller could then do
$.when(addresses()).then(function(result) { ... });
In this particular example however this is not directly possible because there are multiple AJAX calls being made, so you need some way of "combining" all of them into one package. There are multiple ways to do this, so there is also a matter of what you prefer here.
One solution would be to use an array of AJAX promises:
$('button').click(function(){
var arrayLocation = [];
addresses = function() {
var promises = [];
var arrayOfLines = $('#gps').val().split('\n');
$.each(arrayOfLines, function(index, item) {
town = item.split(',');
promises.push($.ajax({
url: 'http://maps.googleapis.com/...',
dataType: 'json',
success: function (data) {
add = data.results[0].address_components[0].long_name;
lat = data.results[0].geometry.location.lat;
lng = data.results[0].geometry.location.lng;
arrayLocation.push("['"+add+"', "+lat+", "+lng+"]");
console.log("['"+add+"', "+lat+", "+lng+"]");
}
}));
});
return promises;
};
$.when.apply($, addresses()).then(function(){
console.log(arrayLocation);
});
});
There are a couple points of note here:
Returning an array of independent promises means that you cannot feed them to $.when directly because that function is designed to accept multiple individual promises instead of an array; you need to use apply to compensate.
I have moved the declaration of arrayLocation inside the click event handler so that it gets reset each time you click the button. The problem of results being added again after each click was due to this array not being reset.
The final handler does not accept any arguments. That's because the arguments that will be passed are the jqXHR objects representing the individual AJAX requests, which is not really useful. Instead of this it captures arrayLocation by closure, since you know independently that the results will be stored there.
Slightly different approach without a shared global variables
$('button').click(function () {
var addresses = function () {
var arrayOfLines = $('#gps').val().split('\n'),
arrayLocation = [];
$.each(arrayOfLines, function (index, item) {
var town = item.split(',');
var xhr = $.ajax({
url: 'http://maps.googleapis.com/maps/api/geocode/json?address=' + $.trim(town[0]) + '&sensor=false',
dataType: 'json'
});
arrayLocation.push(xhr);
});
return $.when.apply($, arrayLocation).then(function () {
return $.map(arguments, function (args) {
if (!$.isArray(args[0].results) || args[0].results.length == 0) {
return undefined;
}
var data = args[0].results[0];
var location = data.geometry.location;
var add = data.address_components[0].long_name;
var lat = location.lat;
var lng = lng;
return "['" + add + "', " + lat + ", " + lng + "]";
});
});
return arrayLocation;
};
addresses().done(function (arrayLocation) {
console.log(arrayLocation)
})
});
Demo: Fiddle
I have created a small JavaScript application with the following function that calls a function to retrieve JSON data:
var months = function getMonths(){
$.getJSON("app/data/Cars/12Months", function (some_data) {
if (some_data == null) {
return false;
}
var months_data = new Array();
var value_data = new Array();
$.each(some_data, function(index, value) {
months_data.push(index);
value_data.push(value);
});
return[months_data,value_data];
});
}
I have then created, in the same file, another function that does something when a specific page is loaded. In this function the variable 'months' is passed to the variable 'result'.
$(document).on('pageshow', '#chartCar', function(){
$(document).ready(function() {
var result = months;
var date = result[0];
var values = result[1];
//more code here...
});
}
the problem is that, based on the debugger, the getMonths() function works fine and produces the expected output, but the 'result' variable in the second function can't obtain the values passed to it by the variable 'months'. Do you know how to solve this issue?
The problem is that you $.getJSON() function is asynchronous, so your data gets loaded later then you read it. There're two workarounds:
1. Replace your $.getJSON with $.ajax and setting async: false;
2. Put your code in $.getJSON callback:
var months = function getMonths(){
$.getJSON("app/data/Cars/12Months", function (some_data) {
if (some_data == null) {
return false;
}
var months_data = new Array();
var value_data = new Array();
$.each(some_data, function(index, value) {
months_data.push(index);
value_data.push(value);
});
var date = months_data;
var values = value_data;
//more code here..
})
}
There must be a syntax error.
replace
});
}
With
});
});
$.getJSON() is a wrapper around $.ajax which is async by default. But you treat it like a sync call.
You can use $.ajaxSetup()
$.ajaxSetup( { "async": false } );
$.getJSON(...)
$.ajaxSetup( { "async": true } );
or use $.ajax with async: false
$.ajax({
type: 'GET',
url: 'app/data/Cars/12Months',
dataType: 'json',
async: false,
success: function(some_data) {
//your code goes here
}
});
or if possible change the behavior of your app so that you process your data in a callback function.
I have a the following java script object
function eventTypeObj() {
allEventTypes = [];
// When the object is created go and get all the event types that can be included in journey or clusters.
$.ajax({
url: "/ATOMWebService.svc/GetDisplayEventTypes",
dataType: "json",
success: function(result) {
allEventTypes = eval("(" + result.d + ")");
}
});
// Returns a list of all the event type IDS.
this.getEventTypeIds = function() {
var eventTypeIDs = [];
for (var i = 0; i < allEventTypes.length; i++) {
eventTypeIDs.push(allEventTypes[i].Id);
}
return eventTypeIDs;
};
}
I was wondering if there is a way stop some one calling the eventTypeObj.getEventTypeIds(); before the ajax call in the constructor has succeeded, and there is no data in the allEventTypes array?
Something like this would be way better (im not guaranteeing this is 100% working, but the concept is sound):
function eventTypeObj() {
this.allEventTypes = [];
this.hasLoadedEventTypes = false;
var loadEventTypes = function(cb) {
$.ajax({
url: "/ATOMWebService.svc/GetDisplayEventTypes",
dataType: "json",
success: function(result) {
this.allEventTypes = eval("(" + result.d + ")");
this.hasLoadedEventTypes = true;
cb();
}
});
};
this.getEventTypeIds = function(updateEventTypes, callback) {
var _getEventTypeIds = function() {
var eventTypeIDs = [];
for (var i = 0; i < this.allEventTypes.length; i++) {
eventTypeIDs.push(this.allEventTypes[i].Id);
}
return eventTypeIDs;
};
if (!this.hasLoadedEventTypes || updateEventTypes) {
loadEventTypes(function(){ callback(_getEventTypeIds()); });
}
else callback(_getEventTypeIds());
};
}
Example usage:
var eto = new eventTypeObj();
eto.getEventTypeIds(false, function(eventTypeIdArray) {
// do stuff with the id array
});
/*
somewhere later on you want to get an updated eventTypeId array
in case the event types have changed.
*/
eto.getEventTypeIds(true, function(eventTypeIdArray) {
// do stuff with the updated ids
});
var allowCall = false;
function eventTypeObj() {
allEventTypes = [];
// When the object is created go and get all the event types that can be included in journey or clusters.
$.ajax({
url: "/ATOMWebService.svc/GetDisplayEventTypes",
dataType: "json",
success: function(result) {
allEventTypes = eval("(" + result.d + ")");
allowCall = true;
}
});
// Returns a list of all the event type IDS.
this.getEventTypeIds = function() {
if(!allowCall) return; // or pop up a message
var eventTypeIDs = [];
for (var i = 0; i < allEventTypes.length; i++) {
eventTypeIDs.push(allEventTypes[i].Id);
}
return eventTypeIDs;
};
}
Or just check if allEventTypes is empty or not.
There is no way to prevent someone from calling it too soon. What would you want to have happen if they call it too soon?
It looks like your code now currently returns an empty array if allEventTypes hasn't yet been filled in. You can decide whether the empty array is the right result or if you should throw an exception when it's called too early to make it absolutely clear to the caller that the data is not yet available.
You could provide some helper code for people who need that information, but it might not yet be available. For example, you could allow them to register a callback that would get called from the success handler after the data had been filled in. You could allow them to query whether the data is available yet.
If you don't want the responsibility for the timing to be on the callers, then you cannot offer a synchronous way to get this information. Instead, you would only offer a callback mechanism for getting the data. If the data is ready, the callback would get called immediately. If the data is not ready, the callback would get called when the ajax function completes. In either case, the caller would have to process the data in the callback only and getEventTypeIds would not be a normal call to get the data like it is now, but rather a call to register a callback that would be called with the data when was ready. This would relieve the caller from having to know implementation details of when the data was ready, but would force them to use the asynchronous nature of the callback mechanism.
this.getEventTypeIds = function(callback) {
if (allEventTypes.length > 0) {
// data is ready call the callback with the data now
} else {
// store the callback to be called later from the success handler
}
}
You can check if the eventType array is empty, right?
if(allEventTypes.length == 0)
{
return;
}