I am writing some jquery that will grab an array of xml files. It then will loop through these and parse through them to show them on the page. I have this working but one thing I would like to do if before it lists out the contents, I want it to print the name of the file. Here is the code I have.
$.get('inc/getMenuFiles.php', function(data) {
var catSplit = data.split(",");
var menuitems = $('.menuitems');
menuitems.empty();
for (i=2; i<catSplit.length; i++) {
url = "inc/menulists/" +catSplit[i].replace(/"/g, '').replace(/]/g, '');
catName = catSplit[i].replace(/"/g, '').replace(/.xml/g, '').replace(/]/g, '');
menuitems.append(catName);
$.ajax({
type: "GET",
url: url,
dataType: "xml",
success: function(xml) {
$(xml).find('item').each(function(){
var name = $(this).find('name').text();
var price = $(this).find('price').text();
menuitems.append(name + " - $" +price + "<br />");
});
}
});
}
});
This is not valid but if I do this it works
$.ajax({
type: "GET",
url: url,
async: true,
dataType: "xml",
success: function(xml) {
$(xml).find('item').each(function(){
var name = $(this).find('name').text();
var price = $(this).find('price').text();
menuitems.append(name + " - $" +price + "<br />");
});
}
}).delay();
So I guess I just need to properly add a delay, after that ajax call
This is happening because your Ajax callbacks won't execute until the current execution path finishes. To get around this you can wait until inside your ajax callback to append catName. This should work:
$.get('inc/getMenuFiles.php', function(data) {
var catSplit = data.split(",");
var menuitems = $('.menuitems');
menuitems.empty();
for (i=2; i<catSplit.length; i++) {
url = "inc/menulists/" +catSplit[i].replace(/"/g, '').replace(/]/g, '');
catName = catSplit[i].replace(/"/g, '').replace(/.xml/g, '').replace(/]/g, '');
$.ajax({
type: "GET",
url: url,
dataType: "xml",
success: (function(catName){
return function(xml) {
menuitems.append(catName);
$(xml).find('item').each(function(){
var name = $(this).find('name').text();
var price = $(this).find('price').text();
menuitems.append(name + " - $" +price + "<br />");
});
}
})(catName)
});
}
});
You still (just like in your original code) have no guarantee on the order your requests could complete, so sometimes appetizers.xml and beer.xml will load in the reverse order.
This is happening because your for-loop is faster than the AJAX call. Remember that the first A in AJAX stands fro asynchronous. Picture this...
You're at the beginning of your for-loop. You take take the first catName, and append it to menuitems. Then you fire up an AJAX call to fetch the name and price of each item in the catName. Meanwhile, the AJAX request hasn't responded yet, so we move one to the second catName, fire up another AJAX request. The first one isn't done, and we just fired a second request; so now we have 2 AJAX responses pending. In the meantime, we move on the third catName.
And so on, and so forth...
Solutions
Set the async property in your inner AJAX call to false. This will make the for-loop pause until the request is completed.
** PREFERRED ** Make your AJAX calls before the for-loop, store the response somewhere (in an array perhaps), than run your for-loop using this array.
Related
I have the function shown below, it just makes a request to a bunch of endpoints housed in an array. Right now I have the Ajax calls be sync (as opposed to the native async).
This is because although when async it does cycle through the urls and makes the calls correctly, the name of the API, which I set to the variable 'name', will only display the last name in the list:
When it should display like this (which it does only if I set the Ajax call to sync):
I'm pretty sure this is due to the fact that the threads from the ajax calls can take any given time to complete while the for loop has finished its iterations way before, thus having only the las name in the list for display.
How can I keep the calls async while also having the names synched to the response of each call?
Thanks.
function isAlive(service) {
var buildIsAliveBox = function (isAlive, name, xhr) {
var isAliveOuterCnt = $('#isAliveOuterCont' + service);
var applyClass = isAlive ? 'alive' : 'dead';
var status = xhr.status == 0 ? 'Failed' : xhr.status;
var xhrMessage = handleHttpResponse(xhr);
var isAliveBox = $('<div class="' + applyClass + ' isAliveBox" class="btn btn-secondary" data-toggle="tooltip" data-placement="bottom" title="' + xhrMessage + '"><p class="svrName">' + name + '</br>' + status + '</p></div>');
isAliveOuterCnt.append(isAliveBox);
};
var svce = service.toLowerCase();
for (var i = 0; i < environments.qa[svce].healthUrls.length; i++) {
var data = environments[envSwitch][svce].healthUrls[i];
var name = data.split(',')[0];
var url = data.split(',')[1];
$.ajax({
url: url,
type: 'GET',
async: false,
headers: {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json',
},
success: function (data, status, xhr) {
buildIsAliveBox(true, name, xhr);
},
fail: function (xhr, textStatus, errorThrown) {
buildIsAliveBox(false, name, xhr);
},
error: function (xhr, status, err) {
buildIsAliveBox(false, name, xhr);
}
});
}
}
Change var to let, at least for name (but ideally for the other variables as well to avoid stumbling upon this issue again later on).
var is function-scoped and not block-scoped and hence the same data, name and url variables are reused every iteration. And the response from the AJAX call comes back long after your loop finished running, so by that time those variables will have the values they last got assigned (in the last iteration of the loop).
With let you get a new scope for every iteration so each AJAX call will get a different name variable etc. it accesses in the callback.
dI use a kendo tooltip on cells of a column of a kendo grid but the content of the tooltip is empty.
When I use the chrome debugger, values are correctly set but there is nothing in my tooltip.
$("#gri").kendoTooltip({
filter: "span.tooltip",
position: "right",
content: function (e) {
var tooltipHtml;
$.ajax({
url: ".." + appBaseUrl + "api/Infobulle?id=" + $(e.target[0]).attr("id"),
contentType: "application/json",
dataType: "json",
data: {},
type: "GET",
async: false
}).done(function (data) { // data.Result is a JSON object from the server with details for the row
if (!data.HasErrors) {
var result = data.Data;
tooltipHtml = "Identifiant : " + result.identifiant;
} else {
tooltipHtml = "Une erreur est survenue";
}
// set tooltip content here (done callback of the ajax req)
e.sender.content.html(tooltipHtml);
});
}
Any idea ? Why it is empty ?
After looking at the dev's answer on telerik forums, i found out that you need to do something like
content: function(){
var result = "";
$.ajax({url: "https://jsonplaceholder.typicode.com/todos/1", async:false , success: function(response){
result = response.title
}});
return result;
}
changing directly with e.sender.content.html() won't work, instead we have to return the value. And i tried several approach :
i tried mimick ajax call with setTimeOut, returning string inside it or using e.sender.content.html() wont work
i tried to use content.url ( the only minus i still don't know how to modify the response, i display the whole response)
the third one i tried to use the dev's answer from here
AND check my example in dojo for working example, hover over the third try
Currently, need to click two times on the button ".radioButton" to load the two pages in the ajax success function. How can I change my code to have just one click to load the two pages?
$('.select-address').on('click', '.radioButton', function() {
var form = $('#shippingAddress');
var action = form.attr('action'),
method = form.attr('method'),
data = form.serialize();
data += '&' + form.find('button[name$=save]:first')[0].name + '=' + form.find('button[name$=save]:first').val();
$.ajax({
url: action,
type: method,
data: data,
success: function(data) {
$(".mini-billing-address").load(app.urls.miniBillingAddressURL).delay(2000);
$(".billing").load(app.urls.paymentMethodURL).delay(2000);
}
});
});
Below is an Ajax POST variable I use to return some information to an ASP MVC3 View. However, I cannot get the .dialg() pop-up function to work. Right now you click on the icon that calls GetProgramDetails(pgmname), and nothing happens. First time using Ajax, so any suggestions would be appreciated. Thx!
<script src="http://code.jquery.com/jquery-1.8.3.js" type="text/javascript"></script>
<script src="http://code.jquery.com/ui/1.9.2/jquery-ui.js" type="text/javascript"></script>
<script type="text/javascript">
function GetProgramDetails(pgmname) {
var request = $.ajax({
type: 'POST',
url: '/BatchPrograms/PopDetails',
data: { programName: pgmname },
dataType: 'html'
});
request.done(function (data) {
$('#data').dialog();
});
</script>
EDIT
I've updated the request.done function to include a simple alert to see if the code was being called. After stepping through with Chrome's debugger, I saw that the code inside was completely skipped over.
request.done(function (data) {
alert("HERE!");
$('#programExplanation').html(data);
});
SECOND EDIT
Here is the controller code the ajax is returning a value from:
[HttpPost]
public string PopDetails(string programName)
{
BatchPrograms batchprograms = db.BatchPrograms.Find(programName);
if (batchprograms == null) return string.Empty;
StringBuilder s = new StringBuilder();
s.Append(batchprograms.ProgramName + " - " + batchprograms.ShortDescription);
s.Append("<br />Job Names: " + batchprograms.PrdJobName + ", " + batchprograms.QuaJobName );
s.Append("<br /> " + batchprograms.Description);
return s.ToString();
}
You need to use the success method to handle the callback, like so:
var request = $.ajax({
type: 'POST',
url: '/BatchPrograms/PopDetails',
data: { programName: pgmname },
dataType: 'html'
}).success(function(data){ $('#data').dialog()} );
This will launch the dialog for you, but if you want to get the response data to work with it, you can have GetProgramDetails take a second parameter which is a callback for after the data is loaded like so:
function GetProgramDetails(pgmname, callback) {
var request = $.ajax({
type: 'POST',
url: '/BatchPrograms/PopDetails',
data: { programName: pgmname },
dataType: 'html'
}).success(callback);
}
This way after the response is received you can handle what to do with the data in your implementation of the callback, in this case it seems like you will be setting data in the dialog and launching the dialog.
This is my Ajax:
$("form[0] :text").live("keyup", function(event) {
event.preventDefault();
$('.result').remove();
var serchval = $("form[0] :text").val();
if(serchval){
$.ajax({
type: "POST",
url: "<?= site_url('pages/ajax_search') ?>",
data: {company : serchval},
success: function(data) {
var results = (JSON.parse(data));
console.log(results);
if(results[0]){
$.each(results, function(index) {
console.log(results[index].name);
$("#sresults").append("<div class='result'>" + results[index].name + "</div>");
});
}
else {
$("#sresults").append("<div class='result'>לא נמצאו חברות</div>");
}
}
});
}
});
When I type slowly (slower then a letter per second) I get the results correct, when I type faster I get 2 times the same results
example:
slow typing: res1 res2 res3
fast typing: res1 res2 res3 res1 res2 res3
Also, any advice on improving the code would be welcome!
Thats what is happening (pseudocode):
When you're typing slow:
.keyup1
.remove1
//asynchronous ajax1 request takes some time here...
.append1
.keyup2
.remove2
//asynchronous ajax2 request takes some time here...
.append2
When you're typing fast:
.keyup1
.remove1
//asynchronous ajax1 request takes some time here...
//and keyup2 happens before ajax1 is complete
.keyup2
.remove2
.append1
//asynchronous ajax2 request takes some time here...
.append2
//two results were appended _in a row_ - therefore duplicates
To solve duplicates problem, you would want to make your results removing/appending an atomic operation - using .replaceWith.
Build results HTML block first as string and then do the .replaceWith instead of .remove/.append:
var result = '';
for (i in results) {
result += "<div class='result'>" + results[i].name + "</div>";
}
$("#sresults").replaceWith('<div id="sresults">' + result + '</div>');
Another problem (not related to duplicates) may be that older result overwrites newer which arrived earlier (because AJAX is asynchronous and server may issue responses not in the same order it receives requests).
One approach to avoid this is attaching roundtrip marker (kind of "serial number") to each request, and checking it in response:
//this is global counter, you should initialize it on page load, global scope
//it contains latest request "serial number"
var latestRequestNumber = 0;
$.ajax({
type: "POST",
url: "<?= site_url('pages/ajax_search') ?>",
//now we're incrementing latestRequestNumber and sending it along with request
data: {company : serchval, requestNumber: ++latestRequestNumber},
success: function(data) {
var results = (JSON.parse(data));
//server should've put "serial number" from our request to the response (see PHP example below)
//if response is not latest (i.e. other requests were issued already) - drop it
if (results.requestNumber < latestRequestNumber) return;
// ... otherwise, display results from this response ...
}
});
On server side:
function ajax_search() {
$response = array();
//... fill your response with searh results here ...
//and copy request "serial number" into it
$response['requestNumber'] = $_REQUEST['requestNumber'];
echo json_encode($response);
}
Another approach would be to make .ajax() requests synchronous, setting async option to false. However this may temporarily lock the browser while request is active (see docs)
And also you should definitely introduce timeout as algiecas suggests to reduce load on server (this is third issue, not related to duplicates nor to request/response order).
You should involve some timeout before calling ajax. Something like this should work:
var timeoutID;
$("form[0] :text").live("keyup", function(event) {
clearTimeout(timeoutID);
timeoutID = setTimeout(function()
{
$('.result').remove();
var serchval = $("form[0] :text").val();
if(serchval){
$.ajax({
type: "POST",
url: "<?= site_url('pages/ajax_search') ?>",
data: {company : serchval},
success: function(data) {
var results = (JSON.parse(data));
console.log(results);
for (i in results)
{
console.log(results[i].id);
$("#sresults").append("<div class='result'>" + results[i].name + "</div>");
}
}
});
}
}, 1000); //timeout in miliseconds
});
I hope this helps.