I have a strange issue with my method :
$('#search').on('keyup', function () {
var valNow = $('#search').val();
if (last !== valNow && valNow !== '') {
console.log(valNow + ' / ' + i);
//interrogate a server from a cities
$.get(path + '/' + strategy() + '/' + valNow,
function (data, status) {
//here
console.log(status);
if (status === 'success') {
cities = [];
cities = data;
}
},
'json');
// make new last
last = valNow;
//list result
var items = [];
console.log(cities[0]);
console.log(' / ' + i);
$(cities).each(function (index, value) {
console.log(value);
var notStrong = valNow.length;
var strong = value.length;
items.push('<li><strong>'+ valNow +'</strong>'+value.substr(notStrong)+'</li>');
});
$('.result').append(items).show();
i++;
console.log('finished');
}
}
);
the problem is simply when I use (/bind) this function I get finish message before console.log(status) (commented://here), the $.get function takes a lot of times to interrogate the web service , I don't know why I have this issue with $.get function, is it a thread or something like this ??? what I want is to get in order all statements (console.log(status) then console.log('finish')).
Try appending your options inside the function block which gives you the data
$('#search').on('keyup', function () {
var valNow = $('#search').val();
if (last !== valNow && valNow !== '') {
console.log(valNow + ' / ' + i);
//interrogate a server from a cities
$.get(path + '/' + strategy() + '/' + valNow,
function (data, status) {
if (status === 'success') {
cities = data;
// append all the options here
}
},'json');
}
}
);
Using AJAX to get data from a remote location always runs asynchronous, meaning that, when calling $.get, the call to the server will be made and the js code returns immediately. Then, after the code in between, console.log('finish') will be called, and some time later, when the $.get call receives the response from the server, the code inside the $.get anonymous function will be called, which then runs console.log(status).
That is the intended design for grabbing data from remote locations. If you want to run the other code strictly after that, you have to run it inside the callback function of $.get, like that:
$('#search').on('keyup', function() {
var valNow = $('#search').val();
if (last !== valNow && valNow !== '') {
console.log(valNow + ' / ' + i);
//interrogate a server from a cities
$.get(path + '/' + strategy() + '/' + valNow,
function(data, status) {
//here
console.log(status);
if (status === 'success') {
cities = [];
cities = data;
}
// make new last
last = valNow;
//list result
var items = [];
console.log(cities[0]);
console.log(' / ' + i);
$(cities).each(function(index, value) {
console.log(value);
var notStrong = valNow.length;
var strong = value.length;
items.push('<li><strong>' + valNow + '</strong>' + value.substr(notStrong) + '</li>');
});
$('.result').append(items).show();
i++;
console.log('finished');
},
'json');
}
});
There are other ways to make the code more pretty, for example using Promises.
Related
I'm working through the Google Place API documentation and I'm trying to get a script that pulls PlaceIDs from a webpage, and replace them with output from the Google Place API.
I managed to successfully get an output from multiple Place IDs by duplicating the code and changing the variable and function names, but now I'm trying to create a loop function so that I'm not duplicating code. Below is what I have, but I'm getting an error. By looking at the console, it seems to work up till the Callback function where it beaks down.
"Uncaught TypeError: Cannot set property 'innerHTML' of null
at callback (places.html:29)"
I've tried a few things, but no luck so far. Any suggestions would be appreciated. Thanks,
<body>
<div id="MY0">ChIJaZ6Hg4iAhYARxTsHnDFJ9zE</div>
<div id="MY1">ChIJT9e323V644kRR6TiEnwcOlA</div>
<script>
var request = [];
var service = [];
var div = [];
for (i = 0; i < 2; i++) {
request[i] = {
placeId: document.getElementById("MY" + i).innerHTML,
fields: ['name', 'rating', 'formatted_phone_number', 'geometry', 'reviews', 'photos'],
};
service[i] = new google.maps.places.PlacesService(document.createElement('div'));
service[i].getDetails(request[i], callback);
function callback(place, status) {
if (status == google.maps.places.PlacesServiceStatus.OK) {
div[i] = document.getElementById("MY" + i);
div[i].innerHTML = "<b>" + place.name + "</b><br>" + place.rating + "<br>" + place.reviews[1].author_name + "<br>" + place.reviews[1].rating + "<br>" + place.reviews[1].text + "<br><img src='" + place.photos[0].getUrl({'maxWidth': 250, 'maxHeight': 250}) + "'>";
}
}
}
</script>
</body>
Move the callback outside of the for loop and forget about the array named div (unless you need this...if so I will rewrite). The for loop is executing before the getDetails() call returns any result, because this call is asynchronous - since you don't have much control over the Google Places callback, I would save the IDs in an array and then use them in callback, like this:
function gp_callback(place, status) {
var el = document.getElementById(window.id_set[0]); // first in first out - the for loop should populate the IDs in correct order
if (status == google.maps.places.PlacesServiceStatus.OK) {
el.innerHTML = "<b>" + place.name + "</b><br>" + place.rating + "<br>" + place.reviews[1].author_name + "<br>" + place.reviews[1].rating + "<br>" + place.reviews[1].text + "<br><img src='" + place.photos[0].getUrl({'maxWidth': 250, 'maxHeight': 250}) + "'>";
}
if (window.id_set.length > 1) {
window.id_set.splice(0, 1); // remove first element from array because has been used - now the next element is at index 0 for the next async callback
}
}
var request = [];
var service = [];
var id_set = [];
for (i = 0; i < 2; i++) {
request[i] = {
placeId: document.getElementById("MY" + i).innerHTML,
fields: ['name', 'rating', 'formatted_phone_number', 'geometry', 'reviews', 'photos'],
};
id_set.push("MY" + i); // this ensures array is populated (in proper order, b/c it tracks the execution of the for loop) for use in callback before callback is called (since getDetails() is async)
service[i] = new google.maps.places.PlacesService(document.createElement('div'));
service[i].getDetails(request[i], function(place, status) {
gp_callback(place, status);
});
}
UPDATE: More scalable and elegant answer after I had a little more time to think about it.
<div id="MY0" class="gp_container">ChIJaZ6Hg4iAhYARxTsHnDFJ9zE</div>
<div id="MY1" class="gp_container">ChIJT9e323V644kRR6TiEnwcOlA</div>
.
.
.
<div id="MYN" class="gp_container">fvbfsvkjfbvkfvb</div> // the nth div
<script>
function populate_container(place, status, container_id) {
var el = document.getElementById(container_id);
if (status == google.maps.places.PlacesServiceStatus.OK) {
el.innerHTML = "<b>" + place.name + "</b><br>" + place.rating + "<br>" + place.reviews[1].author_name + "<br>" + place.reviews[1].rating + "<br>" + place.reviews[1].text + "<br><img src='" + place.photos[0].getUrl({'maxWidth': 250, 'maxHeight': 250}) + "'>";
}
}
function call_service(id_request_map) {
var i, container_id, request,
service_call = function(container_id, request) {
var service = new google.maps.places.PlacesService(document.createElement('div'));
service.getDetails(request, function(place, status) {
populate_container(place, status, container_id);
});
};
for(i in id_request_map) {
service_call(i, id_request_map[i]);
}
}
$(document).ready(function() {
var request, container_id,
id_request_map = {},
container_length = document.getElementsByClassName("gp_container").length,
i = 0;
for (; i < container_length; i++) {
container_id = "MY" + i;
request = {
placeId: document.getElementById(container_id).innerHTML,
fields: ['name', 'rating', 'formatted_phone_number', 'geometry', 'reviews', 'photos'],
};
id_request_map[container_id] = request; // build the association map
}
call_service(id_request_map);
});
</script>
I have to implement a search bar using AJAX and jQuery that displays results from 3 JSON files. At the moment I have it working with one but I am not sure how I might adapt this to live search 3 separate JSON files simultaneously.
const search = document.querySelector('#search');
search.addEventListener('keydown', liveSearch);
function liveSearch() {
const searchField = search.value;
const myExp = new RegExp(searchField, "i");
$.getJSON('weekday.json', function(data) {
var output = '<ul>';
$.each(data, function(key, val) {
if ((val.Title.search(myExp) !== -1) || (val.Description.search(myExp) !== -1)) {
output += '<li>';
output += '<strong>' + val.Title + '</strong>';
output += '<p>' + val.Description + ' - ' + val.Price + '</p>';
output += '</li>';
}
});
output += '</ul>';
$('#output').html(output);
});
}
Any help would be appreciated.
you can use $.when to execute multiple async promise
```
$.when(
$.getJSON('weekday1.json'),
$.getJSON('weekday2.json'),
$.getJSON('weekday3.json')
).then(function (results) {
var r1 = results[0]; // result in weekday1.json
var r2 = results[1]; // result in weekday2.json
var r3 = results[2]; // result in weekday3.json
})
Note: the promise(.then function) will only be resolved after all async task are resolved.
Ref: https://api.jquery.com/jquery.when/
'results' in the code provided by arfai1213 does not return an array that can be used as suggested.
Splitting results as per the code below returns separate arrays that can be used.
$.when(
$.getJSON('./data/file1.json'),
$.getJSON('./data/file2.json')
).then(function (r1, r2) {
$.each(r1[0], function(key, val){
//do something
})
$.each(r2[0], function(key, val){
//do something
})
});
I have a simple series of functions :
convertXML();
function convertXML(){
var xmlObj = xmlToJson(xml.responseXML)
.query.results.WMS_Capabilities;
console.log("convertXML");
(function checkReturn(){
if(typeof xmlObj != 'undefined'){
return (function(){ return createData(xmlObj)})();
}
else {
setTimeout(checkReturn, 50);
}
})();
}
function createData(xmlObj){
for (var i = 0; i < xmlObj.Capability.Layer.Layer.length; i++){
var row={};
row = xmlObj.Capability.Layer.Layer[i];
WMSLayers.push(row);
};
console.log("createdata",WMSLayers)
return (function(){return finish()})();
}
function finish(){
console.log(n == Server.length-1)
if (n == Server.length-1){
//n is defined as an argument
//this code is a part of a bigger function
//same for Server variable
createTable();
};
}
The problem is that that the convertXML function sometimes returns the callback function createData with the xmlObj variable undefined. So I have to check if the variable is defined to call the callback function.
My question is isn't a function suppose to return when all its variables are finished loading data?
UPDATE
This is how I make the request:
var req = {
"type" :"GET",
"dataType":"XML",
"data" : null,
"url" : url
};
//make the request (ajax.js)
ajax(req,ajaxSuccess,ajaxError);
function ajax(prop,onsuccess,onerror){
// data = data || null;
// var url = "wps"; // the script where you handle the form input.
$.ajax({
type: prop.type,
dataType: prop.dataType,
data: prop.data,
url: prop.url,
success: function (data, textStatus, xhr) {
console.log(xhr)
onsuccess(xhr);
},
error:function (data ,textStatus, xhr) {
onerror(xhr);
}
});
// e.preventDefault();
}
function ajaxSuccess(xhr){
$("#messages").append(
'<span style="color:blue">' +
getFullTime() +
'</span> Response HTTP status <b>' +
xhr.status +
' [' + xhr.statusText + ']' +
'</b> from:' +
' <a style="color:grey;text-decoration:none;" href="' +
url+
'" target="_blank">'+
Server[i].link +
Request["getCapabilities"]+
'</a><br>'
);
//create the wms
createWMS(xhr, Server[i],i);//this is where the convertXML,createData and finish functions are located
};
You can use the complete function of $.get(). Note, n does not appear to be defined within finish function.
function convertXML(xml, textStatus, jqxhr) {
var xmlObj = xmlToJson(jqxhr.responseXML)
.query.results.WMS_Capabilities;
console.log("convertXML");
if (typeof xmlObj != 'undefined') {
createData(xmlObj);
}
}
function createData(xmlObj){
for (var i = 0; i < xmlObj.Capability.Layer.Layer.length; i++){
var row = xmlObj.Capability.Layer.Layer[i];
WMSLayers.push(row);
};
console.log("createdata",WMSLayers)
finish();
}
$.get("/path/to/resource", convertXML, "xml")
.fail(function(jqxhr, textStatus, errorThrown) {
console.log(errorThrown)
});
I get the German text of a specific keyword (var title) and output it as html afterwards. This is working fine, but now I wanted to load the English text if the German text isn't available. This is also working fine with my code:
var length = 500;
var title = $('#title').attr('data-title');
var lang = 'de';
var url = 'https://' + lang + '.wikipedia.org/w/api.php?format=json&action=query' +
'&prop=extracts&exintro=&explaintext=&titles=' + title + '&redirects=0';
$.getJSON("http://query.yahooapis.com/v1/public/yql",
{
q: "select * from json where url=\"" + url + "\"",
format: "json"
},
function (data) {
$.each(data.query.results.json.query.pages, function (key, val) {
var text = val['extract'];
console.log('lang-' + lang + '-text: ' + text);
if (text) {
text = text.replace('Siehe auch:', '');
} else if (!text && lang != 'en') {
var url = 'https://en.wikipedia.org/w/api.php?format=json&action=query' +
'&prop=extracts&exintro=&explaintext=&titles=' + title + '&redirects=0';
$.getJSON("http://query.yahooapis.com/v1/public/yql",
{
q: "select * from json where url=\"" + url + "\"",
format: "json"
},
function (data) {
$.each(data.query.results.json.query.pages, function (key, val) {
text = val['extract'];
console.log('lang-en-text: ' + text);
});
});
}
console.log('lang-end-text: ' + text);
if (text) {
text = text.length > length ? text.substring(0, length - 3) + '...' : text;
$('#text').html(text);
} else {
setTimeout(function () {
$('#text').html('<?= __('EMPTY'); ?>');
}, 1000);
}
console.log(data);
});
});
But after the second $.getJSON is closed, text is empty again. That means that
console.log('lang-en-text: ' + text);
is working and outputs the correct English text in the console, but after closing the $.getJSON the variable text has no value anymore, what I can confirm with the output in the console:
console.log('lang-end-text: ' + text);
How can I keep the value? Also is there a better way to check if the specific content I want to get (the text in this case) is available BEFORE, so I don't have to make two $.getJSON requests? Or is my way the right way to do it?
EDIT: It's working now!
I found the solution thanks to moopet and used .done and a new function called .setText to set the text. Maybe this helps others too as the question seems to get upvoted a lot. This is my code now:
var length = 500;
var title = $('#title').attr('data-title');
var lang = 'de';
var url = 'https://' + lang + '.wikipedia.org/w/api.php?format=json&action=query' +
'&prop=extracts&exintro=&explaintext=&titles=' + title + '&redirects=0';
$.getJSON("http://query.yahooapis.com/v1/public/yql",
{
q: "select * from json where url=\"" + url + "\"",
format: "json"
},
function (data) {
$.each(data.query.results.json.query.pages, function (key, val) {
var text = val['extract'];
console.log('lang-' + lang + '-text: ' + text);
if (text) {
text = text.replace('Siehe auch:', '');
} else if (!text && lang != 'en') {
var url = 'https://en.wikipedia.org/w/api.php?format=json&action=query' +
'&prop=extracts&exintro=&explaintext=&titles=' + title + '&redirects=0';
$.getJSON("http://query.yahooapis.com/v1/public/yql",
{
q: "select * from json where url=\"" + url + "\"",
format: "json"
},
function (data) {
$.each(data.query.results.json.query.pages, function (key, val) {
text = val['extract'];
console.log('lang-en-text: ' + text);
});
}).done(function() {
setText(text);
});
}
console.log(data);
});
}).done(function() {
setText(text);
});
function setText(text) {
if (text) {
text = text.length > length ? text.substring(0, length - 3) + '...' : text;
$('#text').html(text);
} else {
$('#text').html('Text not available.');
}
}
You're running afoul of asynchronous javascript calls.
Your success callback:
function (data) {
$.each(data.query.results.json.query.pages, function (key, val) {
text = val['extract'];
console.log('lang-en-text: ' + text);
});
});
is called asynchronously. In other words, it's deferred until the HTTP request has finished.
Your
console.log('lang-end-text: ' + text);
is called immediately, before text is assigned, because that's how execution progresses. If you put the code for things you want to do with the text inside the callback function, you'll get the results you want.
This is just freakin weird to me. So if I don't
function BindAlbumAndPhotoData()
{
// Get an array of all the user's Albums
var aAlbums = GetAllAlbums(userID, token);
alert("aAlbums: " + aAlbums);
if (aAlbums == null || aAlbums == "undefined")
return;
// Set the default albumID
var defaultAlbumID = aAlbums[0].id;
};
So I get an undefined error on the line var defaultAlbumID = aAlbums[0].id; if I don't uncomment the alert("aAlbums: " + aAlbums);
what the heck? If I comment out alert("aAlbums: " + aAlbums); then I get an undefined for the var defaultAlbumID = aAlbums[0].id;
This is so weird. I've been working all night to figure out why I kept getting an undefined for the aAlbum[0] and as soon as I add back an alert that I used to have above it, all is fine...makes no sense to me.
Here's the full code of GetAllAlbums:
function GetAllAlbums(userID, accessToken)
{
var aAlbums = []; // array
var uri = "/" + userID + "/albums?access_token=" + accessToken;
alert("uri: " + uri);
FB.api(uri, function (response)
{
// check for a valid response
if (!response || response.error)
{
alert("error occured");
return;
}
for (var i = 0, l = response.data.length; i < l; i++)
{
alert("Album #: " + i + "\r\n" +
"response.data[i].id: " + response.data[i].id + "\r\n" +
"response.data[i].name: " + response.data[i].name + "\r\n" +
"response.data[i].count: " + response.data[i].count + "\r\n" +
"response.data[i].link: " + response.data[i].link
);
aAlbums[i] = new Album(
response.data[i].id,
response.data[i].name,
response.data[i].count,
response.data[i].link
);
alert("aAlbums[" + i + "].id : " + aAlbums[i].id);
}
});
return aAlbums;
}
so I'm not returning the array until I hit the callback of the FB.api async call so I don't see how my defaultAlbumID = aAlbums[0].id; line of code is executing before I have a valid array of data back. When I put in the alert, ovbvioulsly it's delaying before it hits my line defaultAlbumID = aAlbums[0].id; causing it to I guess luckily have data beacuse the async FB.api call is done but again I don't see how that's even possible to have an issue like this when I'm waiting for the call before proceeding on and returning the array to aAlbums in my BindAlbumAndPhotoData() method.
UPDATE #3
function BindAlbumAndPhotoData()
{
GetAllAlbums(userID, accessToken, function (aAlbums)
{
alert("we're back and should have data");
if (aAlbums === null || aAlbums === undefined) {
alert("array is empty");
return false;
}
var defaultAlbumID = aAlbums[0].id;
// Set the default albumID
var defaultAlbumID = aAlbums[0].id;
// Bind the album dropdown
alert(" defaultAlbumID: " + defaultAlbumID);
});
};
function GetAllAlbums(userID, accessToken, callbackFunctionSuccess)
{
var aAlbums = []; // array
var uri = "/" + userID + "/albums?access_token=" + accessToken;
FB.api(uri, function (response)
{
// check for a valid response
if (!response || response.error)
{
alert("error occured");
return;
}
for (var i = 0, l = response.data.length; i < l; i++)
{
alert("Album #: " + i + "\r\n" +
"response.data[i].id: " + response.data[i].id + "\r\n" +
"response.data[i].name: " + response.data[i].name + "\r\n" +
"response.data[i].count: " + response.data[i].count + "\r\n" +
"response.data[i].link: " + response.data[i].link
);
aAlbums[i] = new Album(
response.data[i].id,
response.data[i].name,
response.data[i].count,
response.data[i].link
);
alert("aAlbums[" + i + "].id : " + aAlbums[i].id);
}
// pass the array back to the callback function sent as a param to the GetAllAlbums method here
callbackFunctionSuccess(aAlbums);
});
}
It's not hitting my alert in the callback. I must still be doing something wrong here.
UPDATE #4 - for some reason it's not hitting my FB.api callback now.
function GetAllAlbums(userID, accessToken, callbackFunctionSuccess)
{
var aAlbums = []; // array
var uri = "/" + userID + "/albums?access_token=" + accessToken;
alert("uri: " + uri);
FB.api(uri, function (response)
{
// check for a valid response
if (!response || response.error)
{
alert("error occured");
return;
}
for (var i = 0, l = response.data.length; i < l; i++) {
alert("Album #: " + i + "\r\n" +
"response.data[i].id: " + response.data[i].id + "\r\n" +
"response.data[i].name: " + response.data[i].name + "\r\n" +
"response.data[i].count: " + response.data[i].count + "\r\n" +
"response.data[i].link: " + response.data[i].link
);
aAlbums[i] = new Album(
response.data[i].id,
response.data[i].name,
response.data[i].count,
response.data[i].link
);
alert("aAlbums[" + i + "].id : " + aAlbums[i].id);
}
alert("about to pass back the array to the callback function");
// pass the array back to the callback function sent as a param to the GetAllAlbums method here
callbackFunctionSuccess(aAlbums);
});
}
function BindAlbumAndPhotoData()
{
// Get an array of all the user's Albums
GetAllAlbums(userID, token, function(aAlbums){
// Set the default albumID
var defaultAlbumID = aAlbums[0].id;
});
};
and then in the GetAllAlbums function call the success function when you have the data back
//********* AFTER THE BREAK *******//
In response to the updated question: The FB API is mostly asynchronous, and will keep executing other code while it waits. So using your code, all I have done is passed in the function, and then call the function you've passed it at the end
function GetAllAlbums(userID, accessToken, funcSuccess)
{
var aAlbums = []; // array
var uri = "/" + userID + "/albums?access_token=" + accessToken;
alert("uri: " + uri);
FB.api(uri, function (response)
{
// check for a valid response
if (!response || response.error)
{
alert("error occured");
return;
}
for (var i = 0, l = response.data.length; i < l; i++)
{
alert("Album #: " + i + "\r\n" +
"response.data[i].id: " + response.data[i].id + "\r\n" +
"response.data[i].name: " + response.data[i].name + "\r\n" +
"response.data[i].count: " + response.data[i].count + "\r\n" +
"response.data[i].link: " + response.data[i].link
);
aAlbums[i] = new Album(
response.data[i].id,
response.data[i].name,
response.data[i].count,
response.data[i].link
);
alert("aAlbums[" + i + "].id : " + aAlbums[i].id);
}
funcSuccess(aAlbums);
});
}
Is your function GetAllAlbums() doing some HTTP requests? If so then you need to either make that call synchronous or you need to put your code into a function and pass that as a callback to the Ajax request.
Try three equals signs instead of two, and also... return false rather than nothing at all.
if (aAlbums === null || aAlbums === undefined)
return false;
Also, undefined doesn't need to be in quotes, otherwise, it's just considered a string with a value of "undefined"
On an added note, it's probably better to ALSO check if aAlbums is actually an array before you decide to return a key from it.
if ( aAlbums === null
|| aAlbums === undefined
|| (typeof(aAlbums)=='object'&& !(aAlbums instanceof Array))
} return false;
Try modifying your condition like this:
if (typeof aAlbums == 'undefined')
return;
Also make sure that aAlbums has values and is an array:
alert(aAlbums.length);
Or:
for(var i = 0; i < aAlbums.length; i++)
{
alert(aAlbums[i].id);
}