I have simple autocomplete input field with Javascript like this:
$('#search').on('keyup', function () {
var query = $(this).val();
$.ajax({
type: "GET",
url: "/search",
data: { query: query }
}).done(function (results) {
showSearchResults(results);
});
});
Sometimes first call takes more time then second or third and results are overridden.
How can I make sure that results only from the latest successful call are displayed?
I mean if I got response from call #3 - I no longer care about calls #1 and #2 and don't want them to override results of call #3.
Ajax function is in default asynchronous it means that many of functions can run on same time. If You wrote 3 letters it will run 3 times, after 3 keyups. If you want to run function in sequence just add setting async: false.
$.ajax({
type: "GET",
url: "/search",
async: false,
data: { query: query }
}).done(function (results) {
showSearchResults(results);
});
But i think You should add some delay, so function will not run immediately after every keyup, just after last one.
I suggest that you bind an incremental id to each ajax request that you send. When you get a response, just check that it carries last given id.
let lastXHRid=0; // tracker of last sent ajax request
$('#search').on('keyup', function () {
let reqXHR = $.ajax({ // create a variable out of $.ajax returned value
type: "GET",
url: "/search",
data: { query: $(this).val() }
});
lastXHRid++; // increment the XHR counter
reqXHR.id = lastXHRid; // attach id to the request
reqXHR.done(function(results, status, respXHR) {
if ( respXHR.id == lastXHRid ){ // compare id of received and last sent requests
showSearchResults(results);
}
});
});
(Edit: I initially suggested tracking the unix timestamp of last sent request, but as #seth-battis suggested in the comments, a sequence number is far enough. As a bonus, I also debugged my sample snippet!)
Related
I'm working with an application that uses DataTables to generate a HTML table which is populated by data from an ajax request.
This is fairly simple:
var substancesTable = $('#substancesTable').DataTable({
"processing": true,
"serverSide": true,
"searching": false,
"ajax": {
"url": "/get-substances.json",
"method": "POST",
"cache": false,
"dataSrc": function (json) {
// Update non-Datatables UI elements and perform other functions based on the ajax response
$('#numSubstances').html(json.recordsTotal);
drawOptionsButton(json.isFiltering);
// Must return data for DataTables to work
return json.data;
}
},
// ...
});
There is a callback which DataTables provides, called rowCallback (https://datatables.net/reference/option/rowCallback) which allows post processing of table rows after the table has been drawn. The key thing here is that it's after the ajax request to /get-substances.json; the table must be populated with data because this callback is used to manipulate data within it at that point.
Within rowCallback I'm providing an array of row ID's in my table - that is to say ID's which correspond to <tr> elements inside #substancesTable - and I go on to expand these rows. I can do this manually by hardcoding in an array of row ID's, e.g.
var substancesTable = $('#substancesTable').DataTable({
// ...
"rowCallback": function(row) {
var id = $(row).find('td:first').text();
var index = $.inArray(id, ['4', '7']); // hardcoded array
if (index !== -1) {
var tr = $(row).closest('tr');
var row = substancesTable.row( tr );
row.child.show();
tr.addClass('active');
}
});
The array I've hardcoded means rows 4 and 7 are expanded after the table has been populated, which is equivalent to the user clicking on them.
The problem I have is that I don't want to hardcode the array. The application stores the equivalent of var index in Redis (cache) meaning that we can grab the data easily even if the user leaves the page. So I have added a second ajax request (outside the var substancesTable... block) to obtain the Redis data. This makes an ajax request to populate an array, activeRows:
var activeRows = [];
$.ajax({
url: '/view-substance/get-active-rows',
method: 'post',
cache: false,
}).done(function(data) {
activeRows = data;
console.log(activeRows);
});
I understand that the nature of ajax means my code is asynchronous. In some cases the ajax request shown above will complete before the DataTable is drawn, so I get the console.log(activeRows) appearing before the table is rendered, and in other cases it happens afterwards.
What is the correct way to make this second ajax request such that the values from it can be used in place of the hardcoded array? I appreciate I will need to convert the response to an array (since it's still JSON in the console.log statement). But my question is focused on where to put this code such that it can be used reliably inside rowCallback?
I have read How do I return the response from an asynchronous call? and understand about the async nature. I can't work out how to structure this to be used in a callback that's already part of an ajax request.
The application uses DataTables version 1.10.16 and jquery 3.2.1
You can actually use the ajax option to:
Make the first AJAX request which retrieves the active rows.
Once the active rows are retrieved, make the second AJAX request which retrieves the table data.
Example: (see full code and demo here)
var activeRows = [];
function getActiveRows() {
return $.ajax({
url: '/view-substance/get-active-rows',
type: 'POST',
dataType: 'json'
...
}).done(function(data){
activeRows = data;
console.log(activeRows);
});
}
function getTableData(data, callback) {
return $.ajax({
url: '/get-substances.json',
type: 'POST',
dataType: 'json',
'data': data // must send the `data`, but can be extended using $.extend()
...
}).done(callback); // and call callback() once we've retrieved the table data
}
$('#example').dataTable({
ajax: function(data, callback){
getActiveRows().always(function(){
getTableData(data, callback);
});
},
rowCallback: function(row, data){
...
}
});
UPDATE
In the above example, I separated the AJAX calls into two different functions mainly to avoid long indentation in the ajax option when you call the $('#example').dataTable(). The code would otherwise look like:
var activeRows = [];
$('#example').dataTable({
ajax: function(data, callback){
// 1. Retrieve the active rows.
$.ajax({
url: '/view-substance/get-active-rows',
type: 'POST',
dataType: 'json'
...
}).done(function(res){
activeRows = res;
console.log(activeRows);
}).always(function(){
// 2. Retrieve the table data.
$.ajax({
url: '/get-substances.json',
type: 'POST',
dataType: 'json',
'data': data // must send the `data`, but can be extended using $.extend()
...
}).done(callback); // and call callback() once we've retrieved the table data
});
},
rowCallback: function(row, data){
...
}
});
I used the .always() so that the table data would still be retrieved in case of failures in retrieving the active rows.
You could solve your problem with Promises. Promises are objects that help you manage and coordinate asynchronous tasks. Your case would look something like this:
var activeRows = [];
var substancesTable = $('#substancesTable').DataTable({
// ...
});
var pDataTable = new Promise(function(resolve, reject){
// you wan't to resolve just once,
// after the table has finished processing the received data.
// (You may want to change draw to an event that's more suitable)
substancesTable.one('draw', resolve);
});
var pOpenRows = new Promise(function( resolve, reject ){
$.ajax({
url: '/view-substance/get-active-rows',
method: 'post',
cache: false,
}).done(function(data) {
// you can either save your rows globaly or give them to the resolve function
// you don't have to do both
activeRows = data;
resolve( data );
});
});
// Here we basically create a third promise, which resolves (or rejects)
// automatically based on the promises in the array.
Promise.all([pDataTable, pOpenRows])
.then(function( values ){
// expand your table rows here
// Note: if you gave your rows to the resolve function you can access
// them here in values[1] (pDataTable's data would be in values[0])
});
In case you want to learn more about Promises:
https://developers.google.com/web/fundamentals/primers/promises
https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Promise
https://scotch.io/tutorials/javascript-promises-for-dummies
For details on browser support you may look here:
https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Promise
https://caniuse.com/#search=Promise
I have a given collection of items to be processed; each item must wait for the completion of the previous one.
By collection of items I mean an array of integer values.
By "processing" I mean make a POST to a HTTP server, passing the integer value of each array element.
I've found something that looks like waht I'm looking for: doSynchronousLoop.js but I wonder if there are alternatives.
If your site may pause rendering while doing the requests, here's a solution with jQuery:
// process 5 items
for (var i = 0; i< 5; i++) {
// ajax request done with jquery
$.ajax({
async: false, /* this makes it execute synchronously */
url: "the url to handle item #i",
type: "POST",
success: function(msg) {
// process data for item #i
}
})
}
Edit: you can solve it asynchronously, too:
items = [put your items here]
current_item = 0
function processItem() {
if (current_item == items.length) {
// list processing finished
return;
}
$.ajax({
async: true,
url: "the url to handle item #current_item",
type: "POST",
success: function(msg) {
// process data for item #current_item
processItem();
current_item++;
}
})
}
Don't miss to put the variables in a scope, I just left them in global scope to make the example easier to understand.
See also the docs: jQuery.ajax
I'm trying to fire an Ajax request with some data being returned by a function call and, as far as I can tell, the Ajax call isn't waiting for my function call to return.
I'm calling getSelectedMessages to get the values of a variable number of checkboxes before firing an Ajax request with an array of the values returned by getSelectedMessages.
getSelectedMessages looks like this:
var getSelectedMessages = function() {
var selected = [];
$('input:checkbox[name=multipleops]:checked').each(function() {
selected.push($(this).attr('value'));
});
return selected;
}
And the Ajax request that's invoking it looks like this:
$.ajax({
type: "POST",
url: "/api/messages/",
data: { ids: getSelectedMessages(), folder: folder },
cache: false,
success: function(){ location.reload() }
});
I've done a little bit of searching around and all I'm turning up are answers on how to return a value from a call and to it.
use
beforeSend attribute with ajax
try
var getSelectedMessages = function() {
var selected = [];
$('input:checkbox[name=multipleops]:checked').each(function() {
selected.push($(this).attr('value'));
});
return selected;
}
$.ajax({
type: "POST",
url: "/api/messages/",
beforeSend : function () { return jQuery.isEmptyObject(getSelectedMessages); }
data: { ids: getSelectedMessages(), folder: folder },
cache: false,
success: function(){ location.reload() }
});
Reference
beforeSend
isEmptyObject
Call getSelectedMessages() outside the ajax function of jquery (?)
The function is executed before the request is sent.
The real problem is that the getSelectedMessaged() returns an array.
This results in undefined=undefined after serialization in jQuery's internals.
And that gets ignored by the $.ajax(), so it looks like it's not sending in your vars but it's ignoring them because they're undefined.
If you concatenate a string with the values and make it a query string parameter yourself it should work.
I assume you want to send something like ?var[]=something&var[]=somethingelse to the server, so it ends up in PHP as an array?
Than you'll have to build up the string yourself in the getSelectedMessages function.
Hope it helps,
PM5544
Here is a function that I have to write to an xml file through an ajax call. The code works fine the first time the ajax call is made. On the second each loop, the ajax call isn't made at all. I don't know why. I specified asyn to false. That did not help. That doesn't seem to be the problem anyway.
$('#'+divid).children('div').children('div').each(function () {
var url = $(this).find('a');
var urlname = url.text();
var urllink = url.attr('href');
var urlid = $(this).attr('id');
alert ("from javascript urlid: "+urlid+" urlname: "+urlname+" urllink: "+urllink);
$.ajax({
url: "add_url.php",
type: "POST",
data: { nodeid: divid, urlid: urlid, urlname: urlname, urllink: urllink },
cache: false,
async: false,
success: function (response) {
if (response != '')
{
alert(response);
}
}
});
});
This really works for me
http://jsfiddle.net/genesis/DTjZQ/4 (3 POST request sent with response status 404)
be sure that your html is good and with same structure as in my fiddle
Instead of making multiple AJAX requests, I suggest appending the data to an array and then sending the entire array of objects.
(Basically the literal object you're using for data would be appended to an array instead of used in a request, and then once the each is done, you would send the array as the data.)
In the web app I am working on there is potential for very long running ajax queries.
I'm using jQuery's $.ajax method to do something like:
this._xhr = jQuery.ajax({
type: "GET",
url: "/path/to/service",
data: "name=value",
success: function(data, message){
// handle a success
},
dataType: "json"
});
Is there a way to modify the success callback after this._xhr.readyState = 2 (loaded) and before this._xhr.readyState = 4 (completed)
I tried modifying this._xhr.onreadystatechange but found that jQuery does not define onreadystatechange.
The abort method sounds like the best option to me.
I don't know much about the ajax method internals, but I can think of a few ways to do what you want. Both involve global state and would break if it's possible for your user to send a second request before the first has finished, so I'm not sure I recommend them.
First, you could keep a reference to the method that does your success work, and change it:
MySuccessMethod = function(d, m) { /* handle a success */ };
this._xhr = jQuery.ajax({
type: "GET",
url: "/path/to/service",
data: "name=value",
success: function(data, message){ MySuccessMethod(data, message); },
dataType: "json"
});
// later...
// user cancels request, so change the success method
MySuccessMethod = function(d, m) { /*print a simple message*/ }
Alternatively, you could just put all the logic in the one success method, and use a global flag to determine what to do:
success: function(data, message){
if (RequestHasBeenCancelled) {
//display a simple message
}
else {
// handle a success
}
},