$.each in $.getJSON race condition? - javascript

I have a problem when using the jquery.each() function within a jquery.getJSON.
There's 2 types of element's I'm dealing with in my code in this case. "Sources" and "Streams"
I want to use getJSON to first get the sources, iterate over them and generate an accordion header out of those. Then, for each of those "sources" I again use getJSON with that source's id to get it's corresponding "streams". Then I append those streams to it's sources accordion body, to get a list of all the streams, sorted by their sources.
But it seems while I'm getting the JSON, the next statements in my procedure are already executed. As I'm basically dynamically building a large HTML String and adding it to an element using jQuery, the String doesn't get all the data it needs.
The code looks as follows:
var html1 = "<div class='panel-group' id='manageAccordion'>";
$.getJSON(url, function(data){
$.each(data, function(i, json){
html1 += getAccordionPart(...); //creates the accordion for the given source
});
}).done(function(){
html1 += "</div>";
$('#elementList').html(html);
});
function getAccordionPart(id, parent, count, json){
//new string html2 is created and a bunch of stuff added
//...
html2 += "....";
html2 += getAccordionBody(json);
html2 += "</div></div></div></div>";
return html2
}
function getAccordionBody(json){
//new string "html3" gets created
//...
var url = standardUrl + "sources/" + encodeURIComponent(json.elementId) + "/streams";
$.getJSON(url, function(data) {
$.each(data, function(i, json) {
html3 += "<li class='list-group-item'>";
html3 += json.name;
html3 += "</li>";
});
}).done(function(){
html3 += "</ul>";
return html3;
});
}
What I specifically end up with is an accordion header that has "undefined" in it's body,
because it seems the getAccordionBody() function doesn't return before the html string gets added to the DOM.
I already tried changing the $.getJSON to $.ajax with async = false, on both of my $.getJSON calls, this seems to fix the problem that the statements don't execute in the order I want them to, but it's horribly slow and returns undefined anyway for some reason..
Any suggestions?
Am I missing something really stupid?

it seems the getAccordionBody() function doesn't return before the html string gets added to the DOM
That's correct. Your way of returning the response (html3) from ajax is flawed - you cannot return from a done handler.
You can however fix this by using promises. Every function returns a promise so that they are easily chained, and then is used to transform the data and get a new promise for it:
$.getJSON(url).then(function(data){
return $.when.apply($, $.map(data, function(i, json){
return getAccordionPart(…); //creates the accordion for the given source
}).then(function() {
var html1 = "<div class='panel-group' id='manageAccordion'>";
html1 += Array.prototype.join.call(arguments, "\n");
html1 += "</div>";
return html1;
});
}).done(function(html){
$('#elementList').html(html);
});
function getAccordionPart(id, parent, count, json){
return getAccordionBody(json).then(function(result) {
var html2 = "…"; //new string html2 is created and a bunch of stuff added
html2 += result;
html2 += "</div></div></div></div>";
return html2;
});
}
function getAccordionBody(json) {
var url = standardUrl + "sources/" + encodeURIComponent(json.elementId) + "/streams";
return $.getJSON(url).then(function(data) {
var html3 = "…"; //new string "html3" gets created
$.each(data, function(i, json) {
html3 += "<li class='list-group-item'>";
html3 += json.name;
html3 += "</li>";
});
html3 += "</ul>";
return html3;
});
}

Rather than using sync calls you can use the Deferred / promise pattern - http://api.jquery.com/deferred.promise/.
You will need to return promise from generate accordion function.
You will need to queue up your generate accordion function calls.
And then do the appending business sequentially as and when promises are resolved.

You have to first collect everything which is required for "getAccordionPart" and "getAccordionBody" (eg: encodeURIComponent(json.elementId)) in a global array. like:
var arrIds = [];
$.getJSON(url, function(data){
$.each(data, function(i, json){
arrIds.push(encodeURIComponent(json.elementId))//collect everything which is required for "getAccordionPart" and "getAccordionBody"
});
then Iterate on this collection and collect all the data for accordian body of these ids in any global variable.
var bodyData = {};
$.each(arrIds, function(){
var url = standardUrl + "sources/" + this + "/streams";
$.getJSON(url, function(data) {
bodyData[this] = data;//collect all the Data for accordian body of these ids
}});
Also do a litle change in getAccordionPart like:
function getAccordionPart(id, parent, count, /*json*/ elementId){
//new string html2 is created and a bunch of stuff added
//...
html2 += "....";
html2 += getAccordionBody(elementId);
html2 += "</div></div></div></div>";
return html2
}
also change getAccordionBody like:
function getAccordionBody(/*json*/ elementId){
//new string "html3" gets created
//...
var data = bodyData[elementId];
$.each(data, function(i, json) {
html3 += "<li class='list-group-item'>";
html3 += json.name;
html3 += "</li>";
});
html3 += "</ul>";
return html3;
}
Your Calling will also be changed:
$.each(arrIds , function(){
html1 += getAccordionPart(,,,this); //creates the accordion for the given source
});
You Final code will be:
var arrIds = [];
$.getJSON(url, function(data){
$.each(data, function(i, json){
arrIds.push(encodeURIComponent(json.elementId))//collect everything which is required for "getAccordionPart" and "getAccordionBody"
});
var bodyData = {};
$.each(arrIds, function(){
var url = standardUrl + "sources/" + this + "/streams";
$.getJSON(url, function(data) {
bodyData[this] = data;//collect all the Data for accordian body of these ids
}});
function getAccordionPart(id, parent, count, /*json*/ elementId){
//new string html2 is created and a bunch of stuff added
//...
html2 += "....";
html2 += getAccordionBody(elementId);
html2 += "</div></div></div></div>";
return html2
}
function getAccordionBody(/*json*/ elementId){
//new string "html3" gets created
//...
var data = bodyData[elementId];
$.each(data, function(i, json) {
html3 += "<li class='list-group-item'>";
html3 += json.name;
html3 += "</li>";
});
html3 += "</ul>";
return html3;
}
$.each(arrIds , function(){
html1 += getAccordionPart(,,,this); //creates the accordion for the given source
});

Related

Why always getting last row from json array result when iterate through with Jquery

I have json data in this structure
here is my json code
https://jsonblob.com/309e8861-7f26-11e7-9e0d-cf3972f3635e
and here is my jquery code example
$("#getJsonData").click(function(){
$.get(base_url + 'roles/index', function(data) {
var data = $.parseJSON(data);
$.each(data, function(index, val) {
$("#result").html("<p>" + val.name + "</p>");
});
});
});
HTML file
<button id="getJsonData">Load role list</button>
<div id="result"></div>
I don't get this, I get every value from json in console when do console.log, but when use html method to display result as html in screen it just showing last value from ending last json array. What is wrong here?
jquery .html always overrite previous html so we cant use html in loop section instead of that we can use append it simply append your p tag in result div
Instead of using this:-
$("#result").html("<p>" + val.name + "</p>");
use like this:-
$("#result").append("<p>" + val.name + "</p>");
You are overwriting the html with the code
$("#result").html("<p>" + val.name + "</p>");
Instead you can store the result in variable and then you write it to the element.Like this
$("#getJsonData").click(function(){
$.get(base_url + 'roles/index', function(data) {
var data = $.parseJSON(data);
var html = '';
$.each(data, function(index, val) {
html +="<p>" + val.name + "</p>";
});
$("#result").html(html);
});
});
Your code should be :
$("#getJsonData").click(function(){
$.get(base_url + 'roles/index', function(data) {
var data = $.parseJSON(data);
var listHtml="";
$.each(data, function(index, val) {
listHtml=listHtml+ "<p>" + val.name + "</p>";
});
$("#result").html(listHtml);
});
});
And Html should be same as your
<button id="getJsonData">Load role list</button>
<div id="result"></div>
Working example is here

JQuery PHP & Contenteditible

I am having a problem updating a database field via jquery and php with contenteditible. The ajax.php file just takes my input from the form and updates the database when the user clicks away but for some reason I can never hit the $("td[contenteditable=true]").blur(function(). Is it because I am displaying the html from the js file? If so how can I get the .blur() to run this way?
Here is a snip of my php.
<div id="status"></div>
<div class="row">
<div id="html"></div>
</div>
My js file
$(function(){
var message_status = $("#status");
$("td[contenteditable=true]").blur(function(){
var field_userid = $(this).attr("id") ;
var value = $(this).text() ;
$.post('ajax.php' , field_userid + "=" + value, function(data){
if(data != '')
{
message_status.show();
message_status.text(data);
//hide the message
setTimeout(function(){message_status.hide()},3000);
}
});
});
//snip
});
function updateTable() {
//snip
$.post("controller.php", payload, displayTable);
}
function displayTable(data){
var obj_defect = $.parseJSON(data);
//snip
html += '<thead><tbody>';
for(i=0; i<obj.length; i++) {
html +='<tr>';
html +='<td width="10%">' + obj[i].row1 + '</td>';
html +='<td width="10%">' + obj[i].row2 + '</td>';
html +='<td width="10%">' + obj[i].row3 + '</td>';
html +='<td width="10%">' + obj[i].row4 + '</td>';
html +='<td id="row0:'+obj[i].id+'" contenteditable="true"> ' + obj[i].row5 + '</td>';
html +='</tr>';
}
html += '</tbody></table>';
} else {
html = '<h4>No results found</h4>';
}
}
$("#html").html(html);
}
Your listener must run AFTER the element exists. You currently create the contenteditble td row after you receive the data from your post, which means when you go to assign a listener at page load there is no object in existence yet.
Something like this should work:
$.post("controller.php", payload, displayTable).done(function() {
// now create listener on td[contenteditable=true]
});

Extracting user playcount from Last.fm API using JSON

In a nutshell, I'd like to display the Last.fm user playcount figure on my site (see 'playcount' on http://www.last.fm/api/show/user.getInfo). I've tried the following, but confess I have no idea if I'm on the right lines here.
$(document).ready(function() {
$.getJSON("http://ws.audioscrobbler.com/2.0/?method=user.getinfo&user=XXX&api_key=XXX&limit=5&format=json&callback=?", function(data) {
var html = '';
$.each(data.user.playcount, function(i, item) {
html += "<p>" + item.playcount + "</p>";
});
$('#count').append(html);
});
});
The inner foreach loop is unnecessary, user.getInfo returns only one user's information after all. The following should work:
$(document).ready(function() {
$.getJSON("http://ws.audioscrobbler.com/2.0/?method=user.getInfo&user=XXX&api_key=XXX&format=json", function(data) {
var html = "<p>" + data.user.playcount + "</p>";
$('#count').append(html);
});
});
I have also removed the limit and callback parameters from the URL for brevity.

Use $.each and if with $.getJSON

Me and $.each aren't good friends, I can't seem to figure out how I should use this statement to print all my data in my JSON file. Another problem is the if statement, I tried it in many ways, but whatever I do, no data is being printed.
My JSON file
[
{
"expo":"pit",
"datum":"05.06.2011 - 05.06.2016",
"img":"images/pit_home.jpg",
"link":"exp1_index.html"
},
{
"expo":"Space Odessy 2.0",
"datum":"17.02 - 19.05.2013",
"img":"images/so_home.jpg",
"link":"exp2_index.html"
}
]
My $.getJSON script
<script type="text/javascript">
function read_json() {
$.getJSON("home.json", function(data) {
var el = document.getElementById("kome");
el.innerHTML = "<td><div class='dsc'>" + data.expo + "<br><em>" + data.datum + "</em></div></td>";
});
}
</script>
So how would I integrate the $.each statement and seperate from that, the if statement?
Try this
$.getJSON("home.json", function (data) {
var html = '',
el = document.getElementById("kome");
$.each(data, function (key, val) {
html += "<td><div class='dsc'>" + val.expo + "<br><em>" + val.datum + "</em></div></td>";
});
el.innerHTML = html;
});
Something like this?
<script type="text/javascript">
function read_json() {
$.getJSON("home.json", function(data) {
var html = '';
$.each(data, function(i, record) {
html += '' +
'<td>' +
'<a href="' + record.link + '" data-ajax="false">' +
'<img src="' + record.img + '">' +
'<div class="dsc">' +
record.expo + '<br>' +
'<em>' + record.datum + '</em>' +
'</div>' +
'</a>' +
'</td>';
});
$('#kome').html(html);
});
}
</script>
Note: I haven't tested this, so there may be a few syntax errors (mostly concerned about the quotes in the string concatenation).
I will note that you don't need jQuery's $.each for this; you can use a basic for-loop:
for (var i = 0; i < data.length; i++) {
var record = data[i];
... stuff...
}
jQuery's .each() is really useful when iterating over elements in the DOM, though. For example, if you wanted to iterate over the elements with class dsc, you could do:
$('.dsc').each(function(i, dsc) {
// Do stuff to the $(dsc) element.
});
Also, you might as well use jQuery's selectors (the $('#kome')) if you're going to use jQuery.
jQuery's API usually has solid examples for stuff; this is one such case.
$.each can iterate arrays [] or objects {}, your json contains both, so first you need to tackle array, that gives you an object on each iteration. Then you can access its properties.
jQuery each documentation
Second thing: to append to innerHTML use "+=" not "=" or you will reset html on each iteration.
var el = document.getElementById("kome");
$.getJSON('ajax/test.json', function(data) {
$.each(data, function(n, linkData) {
el.innerHTML += '' + linkData.expo + '';
});
}

Local JSON data to getJSON on URL

I am using this script which reads some internal json data.
The data is currently in the actual page.
I need to change the code to use getJSON? so that it reads the json from an external page/url
Here is the current full code:
<script>
var data = [{"id": "1","title": "mytitle"}];
var output = '';
$.each(data, function(index, value){
output += '<li data-icon="false">' + value.title + '</li>';
});
$('#listview').append(output).listview('refresh');
</script>
How do I change the code so I can use external json code instead of this way for example adding getJSON to it?
$.getJSON('url_to_script', function(data) {
var output = '';
$.each(data, function(index, value){
output += '<li data-icon="false">' + value.title + '</li>';
});
$('#listview').append(output).listview('refresh');
});
But if you're trying to get data from different domain then you need something following:
$.getJSON("url_to_script?jsoncallback=?",, function(data) {
var output = '';
$.each(data, function(index, value){
output += '<li data-icon="false">' + value.title + '</li>';
});
$('#listview').append(output).listview('refresh');
});
Read more about .getJSON().

Categories

Resources