JS, multiple JSON requests and callback functions - javascript

I need some help. I'm quite new to Javascript, I'm currently trying to do something using two differents api: SongKick and Deezer. The idea is simple, on the page you can type your city, 1) I do a first request to the Songkick Api to get the ID of this city, 2) then with the ID, I do another request to get a list of concerts and I only take the name of the artist (20 maximum), 3) then I with the list of names I use the deezer Api to get the picture of the artist and a mp3 preview.
I've tried many ways but I can't access the data everywhere of course, and I don't know how to use callback cause there is too many things, if you can take a look that would be awesome.
Thanks!
artistsArray = [];
artistsArray2 = [];
arr = [artistsArray,[],[]];
var dispName;
var areaId;
function search(){
area = document.getElementById('band').value;
function songKickArea(callback){
$.getJSON('http://api.songkick.com/api/3.0/search/locations.json?query=' + area + '&apikey=tIhpFoFn0dWpQ72A',function(data){
var areaId = data['resultsPage']['results']['location'][0].metroArea.id;
callback(areaId);
});
console.log("1 is done");
}
function findAreaId(callback){
songKickArea(function(callback){
console.log(callback);
});
$.getJSON("http://api.songkick.com/api/3.0/metro_areas/" + areaId + "/calendar.json?apikey=tIhpFoFn0dWpQ72A",function(data){
for (var i=0; i<20 ; i++)
{
artistsArray.push(data['resultsPage']['results']['event'][i].performance[0].displayName);
}
callback(artistsArray);
});
console.log("2 is done");
}
function addInfos(callback){
for (var i=0; i<20 ; i++)
{
DZ.api('/search?q=artist:' + '"'+ artistsArray[i]+'"' +'?limit=1', function(json){
if(json.data[0]){
artistsArray2.push({preview:json.data[0].preview, picture: json.data[0].artist.picture})
}
});
}
console.log("3 is done");
callback();
}
function runSearchInOrder(callback) {
songKickArea(function() {
findAreaId(function() {
addInfos(function() {
console.log(areaId);
});
});
});
}
runSearchInOrder(function(){console.log('finished')});
}
EDIT 09/17/2015
Thanks Vittore, I took a look at promises in JS and it's very interesting and perfect in my case. So now I'm here :
function songKickArea(areaId){
area = document.getElementById('band').value;
return $.getJSON('http://api.songkick.com/api/3.0/search/locations.json?query=' + area + '&apikey=XXXXXXX',function(data){
});
}
function findAreaId(data){
var areaId = data['resultsPage']['results']['location'][0].metroArea.id;
return $.getJSON("http://api.songkick.com/api/3.0/metro_areas/" + areaId + "/calendar.json?apikey=XXXXXXX",function(data){
});
}
function addInfos(data){
for (var i=0; i<20 ; i++)
{
artistsArray.push(data['resultsPage']['results']['event'][i].performance[0].displayName);
DZ.api('/search?q=artist:' + '"'+ artistsArray[i]+'"' +'?limit=1', function(json){
if(json.data[0]){
artistsArray2.push({preview:json.data[0].preview, picture: json.data[0].artist.picture})
}
});
}
}
And I use this onClick:
songKickArea().then(findAreaId).then(addInfos).then(createList);
So everything is working fine, in addInfos my array artistsArray2 get all the infos I need from deezer (preview and picture). But now the next step is to create a list to display these artists (or tracks) so the next function is like this.
function createList(json){
var html = '<ul>';
for (var i=0; i<17; i++)
{
html+= '<li>';
html += '<div class="picture">' + '<div class="player"><img src="svg/play43.svg" ></div>'+ '<a href=' + artistsArray2[i].preview + '>' + '<img src=' + artistsArray2[i].picture + '>' + '</a>' +'</div>';
html+= '<div class="arrow"><img src="css/svg/arrow487.svg"></div>';
html+= '</li>';
}
html+= '</ul>';
$('#results').append(html);
}
But here I have no idea how to pass the value of a full array from the last function to this one, could you help me ? Thanks a lot !

UPDATE: Little clarification on multiple calls to the services and array of results.
You original code has addInfos method that iterates ( for loop ) through array and calling web service in that loop. What you want to do is to get results of each of those calls all together. While there are many ways of doing that, what I am showing you is using array.map to "map" each element of an array with data from step X to the promise returned by AJAX call. Let me give an example:
Say you have an array:
var artistIds = [6664009,6664010,6664011]
Now you can map it to promises:
var artistCalls = artistIds.map(function(id) {
return $.getJson('~ get artists data service url ~' + id)
}
Which will give you array artistCalls each element of which will eventually have resolved promise with the data you need. While you can do all the crazy stuff with it, the easiest way of getting data from ALL calls is to use $.when helper method:
$.when(artistCalls).then(function(artists) {
// here artists will array where each element is data returned by each AJAX call
})
Now if you want to render html to show all artists on the page you might have code like that:
function renderArtistHtml(artist) {
return '<li>'
+= '<div class="picture"><div class="player"><img src="svg/play43.svg" ></div><img src="' + artistsArray2[i].picture + '"></div>'
+= '<div class="arrow"><img src="css/svg/arrow487.svg"></div>'
+= '</div></li>';
}
And a function that renders entire list:
function renderAllArtistsHtml(artists) {
return '<ul>' + artists.map(renderArtistHtml) + '</ul>'
}
Now that you have it you can create whole chain of your functions together:
$(... my button selector ...).on('click',function(e) {
var area = ... get area
songKickArea(area)
.then(findAreaId) // this thing returns promise that returns array
.then(addInfos) // this thing returns $.when(arr.map(...))
.then(renderAllArtistsHtml) // this thing converts array of data from all calls from previous step to html layout
.then(function(html) { // this part just adds it to DOM
$('#results').append(html);
});
})
Just answered similar question here.
Basically every ajax method in jquery returns promise ( and if your api does not return promise (like DZ.api ) you can wrap it in a $.deferred )
Once you return promises from your functions you can chain them:
function myajax1() {
return $.getJson(...)
}
function myajax2(data) {
return $.getJson(...)
}
myajax1().then(myajax2)
This will call myajax2 with data returned by myajax1 ajax call
You can chain it as many times as you want.
In case you need to wait for several you can use $.when:
$.when([myajax11(), myajax12()]).then(myajax2)
So closer to your actual code, you have 3 api calls:
songkick locations
songkick metro_areas
DZ.api
last one will require wrapping up in a promise, see example here: https://learn.jquery.com/code-organization/deferreds/examples/
Declare 3 functions:
function getLocations(area) {
return $.getJson(....) // location query
}
function getMetroArea(data) {
var areaId = data['resultsPage']['results']['location'][0].metroArea.id
return $.getJson(...) // metro query
}
function getArtists(data) {
var artist = data['resultsPage']['results']['event'][i].performance[0].displayName
return DZAPIWraper(...)
}
And chain them:
getLocations(...).then(getMetroArea).then(getArtists)
In case you really need to do several calls for several artists in the last step, your code will look similar to:
function getArtists(data) {
var artists = getArtistsArrayFromMetro(data)
var artistsCallbacks = artists.map(function(a) {
return DZAPIWrapper(...)
})
return $.when(artistCallbacks)
}
and that full chain is:
getLocations(...).then(getMetroArea).then(getArtists).then(function(artists) {
// here artists going to be all artists data from all api calls to DZ.api
})

Related

Javascript returning multiple checkbox values

I'm having some trouble trying to get multiple checkbox values. It currently is working, just not in the way I wanted/was hoping it would. Right now anything checked is appended to the bottom of the body and not inline with the function it was aiming to be inserted into.
I'm trying to avoid using JQuery or anything except JavaScript as it's all we've currently covered in our class.
function favMedia(media){
var media = document.forms['mediapref']['media'].value;
return media;
}
function pets(pet){
var pet = document.getElementsByName('pets')
for (var checkbox of pet){
if (checkbox.checked)
document.body.append(checkbox.value + ' ');
}
}
function about(text){
var info = document.forms['personal']['about'].value;
return info;
}
function infoForm(media, pet, text){
document.getElementById('infoset').innerHTML = favMedia(media) + "<br>" + pets(pet) + "<br>" + about(text);
}
Is there some way I can assign it just to a single variable to return and then throw into the last function?
Also please give me any tips or improvements on any aspect of the functions if you have any.
Put it in a string that you return from the function.
function pets(pet) {
var pet = document.querySelector('[name="pets":checked');
let selected = [...pet].map(p => p.value);
return selected.join(', ');
}

How to prevent duplicate rows from being displayed in html?

I've followed and completed this tutorial https://github.com/dappuniversity/election/tree/2019_update. However, duplicate rows show up at the end when I'm adding new votes in (shown in picture).
I'm not familiar with dApps, web development, or javascript so I don't know where my error is.
Code from https://github.com/dappuniversity/election/tree/2019_update.
I don't know where adding the new rows came in and I'm trying to prevent it.
the problem is in the asynchronous nature of JavaScript the app is not waiting for the response of the blockchain before removing the old so what happens is that the data get inserted to the dom two times, the fix is to handle the promises differently. Group all the promise calls to get candidates to an array, then waiting until all of them are resolved to add them to the dom.
App.contracts.Election.deployed()
.then(function(instance) {
electionInstance = instance;
return electionInstance.candidatesCount();
})
.then(function(candidatesCount) {
const promises = [];
// Store all prosed to get candidate info
for (var i = 1; i <= candidatesCount; i++) {
promises.push(electionInstance.candidates(i));
}
// Once all candidates are received, add to dom
Promise.all(promises).then(candidates => {
var candidatesResults = $("#candidatesResults");
candidatesResults.empty();
var candidatesSelect = $("#candidatesSelect");
candidatesSelect.empty();
candidates.forEach(candidate => {
var id = candidate[0];
var name = candidate[1];
var voteCount = candidate[2];
// Render candidate Result
var candidateTemplate =
"<tr><th>" +
id +
"</th><td>" +
name +
"</td><td>" +
voteCount +
"</td></tr>";
candidatesResults.append(candidateTemplate);
// Render candidate ballot option
var candidateOption =
"<option value='" + id + "' >" + name + "</ option>";
candidatesSelect.append(candidateOption);
});
});
return electionInstance.voters(App.account);
})

JavaScript Recursion with promises -- order of execution error

Building a method to create markup for a web app off canvas navigation. I am making async callback to another service that returns children of the parent menu node (see code below):
function GenerateMarkup(Terms, n) {
var termsEnum = Terms.getEnumerator();
var html = "<ul>";
// Process top level terms
while (termsEnum.moveNext()) {
var currentTerm = termsEnum.get_current();
html += "<li>"
if (currentTerm.get_termsCount() > 0) {
var childcall = function() {
var deferred = $.Deferred();
html += "" + currentTerm.get_name() + "<br><span>" + currentTerm.get_description() + "</span>";
SPTermStore.GetTermsFromTermSet(currentTerm).then(function(termSet) {
if (typeof termSet !== undefined) {
deferred.resolve(GenerateMarkup(termSet, n++));
}
else
deferred.reject("something bad happened");
});
return deferred.promise();
};
$.when(childcall()).done(function(markup) {
html += markup;
});
} // end if
else
html += "" + currentTerm.get_name() + "";
html += "</li>"
} // end while
html += "</ul>";
console.log("GenerateMarkup (" + n + "): " + html);
return html;
} // end function
The issue is the order the markup is generated is not right; in a normal synchronous the recursive call to GenerateMarkup would complete, but in this situation I am trying to wait for the returned promise (i.e. the call to GenerateMarkup to complete) so I can append the html. The idea is as it iterates through the while, top level nodes will have their child nodes processed etc.
If I look at the console.log output this is what I get; the problem is the first listed markup below is what is returned to the page and not the combination of the below.
GenerateMarkup (0): <ul><li>About<br><span>Our Company</span></li><li>Portfolio<br><span>Our Properties</span></li><li>Corporate Responsibility<br><span>Our Committment</span></li></ul>
GenerateMarkup (0): <ul><li>Careers</li><li>Core Values</li><li>Governance</li><li>History</li></ul>
GenerateMarkup (1): <ul><li>Core Market Strategy</li><li>Our Properties</li></ul>
GenerateMarkup (2): <ul><li>Community Involvement</li><li>CSR Report</li><li>Diversity</li><li>Sustainability</li></ul>
Any help would be appreciated.
Promises are asynchronous so they're not guaranteed to return in the order in which they're promised.
If the ordering is important, consider chaining the promises so that they execute in the sequence that you expect. Promise.then can be chained.
One possibility would be to loop through the termsenumerator, and load these into a deferred array. Then apply the $when, later. See sample below:
var deferreds = [];
for (var i = 0; i<termsenumerator; i++) {
deferreds.push(Grab data you want and push it into this array);
}
//Now that we have all the results... (deferred), process these.
$.when.apply($, deferreds).done(function () {
var resultdata = [];
for (var i = 0; i < arguments.length; i++) {
var daydata = arguments[i][2];
applymarkupWData(resultdata.responseJSON.d,i);
};
});

Variable scope or return issue (not sure which)

Using the script below I'm attempting to create an object called temptagarray which gets populated with all the tags on a Tumblr weblog and their frequency. So it should end up looking like this:
{'performance': 10, 'installation': 5}
I know the object is being created and it looks correct (I can print it out in each loop) but I can't figure out how to use it after/outside the function i.e. at the bottom of the script where I attempt to document.write() it out. Is this a global/local variable issue, a return issue or do I need to address it in some way?
<script type="text/javascript">
var temptagarray = {};
var tags;
var tag;
function loadPosts () {
var key = "api_key=9I4rZAYQCbU1o5TSMZuyrlvXiQsNxKBicCJxNK5OKZ6G9pgdim";
var api = "https://api.tumblr.com/v2/blog/garrettlynch.tumblr.com/";
var retrieve_more = function (offset) {
$.getJSON(api + "posts?callback=?&filter=image&limit=20&offset=" + offset + "&" + key,function(data) {
//for each item (post) in the response
$.each(data.response.posts, function(i, item) {
//pull out the posts tags
tags = item['tags'];
//loop through the tags
for (i = 0; i < tags.length; i++)
{
tag = tags[i];
//if the tag already exists in the tag array
if (temptagarray[tag])
{
temptagarray[tag] = temptagarray[tag] + 1;
}
else
{
temptagarray[tag] = 1;
}
}
});
if (data.response.posts.length == 20) {
retrieve_more(offset + 20);
}
});
};
retrieve_more(0);
}
loadPosts();
document.write(JSON.stringify(temptagarray));
</script>
Thanks in advance
Garrett
Replace this:
if (data.response.posts.length == 20) {
retrieve_more(offset + 20);
}
...with this:
if (data.response.posts.length == 20) {
retrieve_more(offset + 20);
} else {
document.write(JSON.stringify(temptagarray));
}
The problem you're having is that, despite your document.write(...) command being located below the ajax call in your code, the ajax call is asynchronous and thus the callback will be invoked asynchronously as well. Basically, document.write(...) is being invoked long before you've had a chance to interact with the temptagarray variable in the ajax callback.
First things first - AJAX is Async Asynchronous.
So the code block does not wait for the previous instruction to be completed before it executes the next line.
So your document.writeline would have already been executed by the time the response comes back.
Try printing that info in the success call back after the if block and you would indeed see the response.
thanks for the replies. Below is what I have now as a workable solution as the result is going to call another function anyway. Reading a little bit more I'm wondering if I should be using a callback - is it better?
<script type="text/javascript">
//load posts from a Tumblr weblog
function loadPosts () {
//api key and weblog address
var key = "api_key=9I4rZAYQCbU1o5TSMZuyrlvXiQsNxKBicCJxNK5OKZ6G9pgdim";
var api = "https://api.tumblr.com/v2/blog/garrettlynch.tumblr.com/";
//tags object
var temptagarray = {};
//all tags and each tag
var tags;
var tag;
//looping function to keep retrieving posts until all are retrieved
var retrieve_more = function (offset) {
$.getJSON(api + "posts?callback=?&filter=image&limit=20&offset=" + offset + "&" + key,function(data) {
//for each item (post) in the response
$.each(data.response.posts, function(i, item) {
//pull out the posts tags
tags = item['tags'];
//loop through the tags
for (i = 0; i < tags.length; i++)
{
//pull out each tag
tag = tags[i];
//if the tag already exists in the tag array
if (temptagarray[tag])
{
//add 1 to its count
temptagarray[tag] = temptagarray[tag] + 1;
}
else
{
//set its count to 1
temptagarray[tag] = 1;
}
}
//to test object as it gets added to
//$("#Posts ul").append('<li>' + JSON.stringify(item, ['tags']) + '</li>')
});
//if the number of posts is more than 20
if (data.response.posts.length == 20)
{
//retrieve the next 20
retrieve_more(offset + 20);
}
else
{
//call the show result function
showresult(temptagarray);
}
});
};
//stop retrieving posts
retrieve_more(0);
}
loadPosts();
function showresult(tagarray)
{
$("#Posts ul").append('<li>' + JSON.stringify(tagarray) + '</li>');
//document.write(JSON.stringify(tagarray));
}
</script>

Using D3 to read data into associative arrays

I'm trying to loop through a set of values and read in separate files (using d3.js) into javascript associate arrays , but the argument is not being passed properly into the internal function. For example:
var reg_list = [];
var all_region_data = [];
reg_list[0]="region1";
reg_list[1]="region2";
// Function to read in data from all regions in reg_list.
function read_regional_data(region_list) {
for (i=0;i<reg_list.length;i++) {
region = region_list[i];
console.log("Read data for " + region) // THIS RETURNS REGION1, THEN
// REGION2
all_region_data[region]=new Array()
d3.csv('filepath' + region + '.csv', function(csv){
console.log('reading for ' + region) // THIS RETURNS REGION2 TWICE
csv.map(function(x) {
all_region_data[region].push(x.cause);
})
console.log("Finished Reading Data for " + region)
})
}
}
When I execute this, I iterate through both regions in the loop, but region1 is never passed into the d3.csv function. This may have something to do with the d3.csv being run asynchronously? Any thoughts on how to avoid or improve this are appreciated.
Use recursion instead of the loop to iterate over the regions --
var reg_list = [],
all_region_data = [];
reg_list[0]="region1";
reg_list[1]="region2";
function read_regional_data(i) {
var region = reg_list[i];
console.log("Read data for " + region);
all_region_data[region]=new Array();
d3.csv('filepath' + region + '.csv', function(csv){
console.log('reading for ' + region);
csv.map(function(x) {
all_region_data[region].push(x.cause);
});
console.log("Finished Reading Data for " + region);
if(i < reg_list.length) {
read_regional_data(i+1);
}
});
}
read_regional_data(0);

Categories

Resources