I am using jQuery UI autocomplete for a city search on my site. It starts to search after the user has entered 3 characters.
I'm wondering how to change this script to abort the last query if the user continues typing.
function enableCitiesAutocomplete() {
var url = LIST.urls.api_cities_search;
$('#txt_search_city').autocomplete({
source: url,
minLength: 3,
select: function( event, ui ) {
$(this).val( ui.item.value );
$( "#id_city" ).val( ui.item.id );
$(this).closest('form').submit();
}
});
}
I don't know if you can "abort" a completion that has already begun. But you may be able to get what you want by interrupting the render part of the autocomplete. This might work if the autocomplete consists of a query and a render, and the query involves a network transaction.
If there's a query and a render, and the query is just a search through a locally-stored list, then this probably won't work, because the latency is too low.
This SO question describes how to monkey-patch the renderMenu and renderItem fns in the jQuery autocomplete package. I'm thinking you can use the same monkey-patch approach.
You'd need to specify, as the source, a function, not a URL. This SO question describes how. Then, in that function, increment a count variable that says "a search is in process". Then perform the search by doing an "ajax get". Also patch the renderMenu to (a) only render if a single search is in process, and (b) decrement the search count.
It will probably look something like this:
var queriesInProcess = 0;
var ac = $('#txt_search_city').autocomplete({
minLength: 3,
select : .... ,
// The source option can be a function that performs the search,
// and calls a response function with the matched entries.
source: function(req, responseFn) {
var url = baseUrl + req.term;
queriesInProcess++;
$.ajax({
url: url,
//data: data,
success: function(json,xhr) {
queriesInProcess--;
var a = ....retrieve from the json ....
responseFn( a );
},
errpr : function () { queriesInProcess--; },
dataType: 'json'
});
}
});
ac.data( "autocomplete" )._renderMenu = function( ul, items ) {
if (queriesInProcess > 0) return;
var self = this;
$.each( items, function( index, item ) {
self._renderItem( ul, item );
});
};
If you want to get more sophisticated, you can also try to abort the pending ajax request.
This would need to happen in the source function; you'd need to cache/stash the XHR returned from the $.ajax() call, and call abort() when a new one comes through.
Related
For example, say a page returns search results based on debounced user text.
How do you account for the case where an endpoint has a highly variable latency in which the second call can return before the first call.
E.g.
User is typing "books and movies" with a keyup debounce of 500ms
The user slightly pauses in the middle so the string is "books", this triggers a search call.
The user continues typing and finishes, triggering the second call with "books and movies".
The second call returns first, populating the list based on "books and movies".
Then the first call, which was delayed comes back and re-renders the list based on "books".
The user sees only "books" and is confused.
The surefire way to solve this is with a button to manually trigger the call. I'd like to avoid this though so I've increased the debounce but I'm wondering if there's a better way.
We suppose that you use jQuery to make ajax calls.
One solution is to use a pooling system: basically an array containing ajax requests.
Each time, a new request is emitted, you abort all request in the pool.
So you ensure that the last request made will be the only one that will end.
Here is the implementation of the pool:
jQuery.xhrPool = [];
jQuery.xhrPool.abortAll = function () {
jQuery(this).each(function (idx, jqXHR) {
jqXHR.abort();
});
jQuery.xhrPool.length = 0;
};
Here is an example on how to use it with the "search repository API" from GitHub (https://developer.github.com/v3/search/#search-repositories):
jQuery.xhrPool = [];
jQuery.xhrPool.abortAll = function () {
jQuery(this).each(function (idx, jqXHR) {
jqXHR.abort();
});
jQuery.xhrPool.length = 0;
};
$(document).ready(function(){
$("#SearchField").autocomplete({
source: function( request, response ) {
// First we abort all other request
jQuery.xhrPool.abortAll();
$.ajax({
url: "https://api.github.com/search/repositories",
method: "get",
dataType: "jsonp",
data: {
q: request.term
},
beforeSend: function (jqXHR) {
// Before sending the request we add it to the pool.
jQuery.xhrPool.push(jqXHR);
},
success: function(data) {
var items = new Array();
for(var i=0;i<data.data.items.length;i++)
{
items.push(data.data.items[i].name);
}
response(items);
}
});
},
minLength: 3,
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/jquery-ui.css" >
<input type="text" id="SearchField" />
Javascript works synchronously so there is no possibility of race conditions if you write your code correctly.
I guess you are using ajax (ajax is supposed to be async, don't use sync ever, once you go sync, you can't go back) to get the query result. You are probably using some code like this:
var req=new XMLHttpRequest();
req.onreadystatechange=function(){
if (req.readyState==4){
if (req.status==200){
// Your callback here which shows autocomplete suggestions maybe?
}
}
}
Hold on to that req variable. So once you do a new request, you can simply discard the old request like:
req.onreadystatechange=null;
You can also abort the ajax request like:
req.abort();
I have an autocomplete feature in my application which makes an ajax request to server.
However, once I get data from server, I want to use the look up feature instead of using the service url(to minimize calls to server).
Here is what my js looks like
$('#country').autocomplete({
serviceUrl : './countryCache?',
paramName : 'countryName',
transformResult : function(response) {
return {
// must convert json to javascript object before process
suggestions : $.map($.parseJSON(response), function(item) {
return {
data : item.name
};
})
};
},
showNoSuggestionNotice:true,
onSelect: function (value, data) {
$('#countryId').val(value.data);
}
});
Here is a sample from my ajax call to countryCache - "India, Iceland, Indonesia".
If the user has typed I, the server returns back the result as above.
Now when the user types in n after I, I dont want to make a call to server again.
Can someone help me achieve it.
There is a simple solution for this in the jQuery UI Autocomplete documentation. There you'll find a section titled Remote with caching that shows how to implement what you are looking for.
I adapted the code from that site to this question, and added some comments for clarification:
var cache = {};
$( "#country" ).autocomplete({
source: function( request, response ) {
// If the term is in the cache, use the already existing values (no server call)
var term = request.term;
if ( term in cache ) {
response( cache[ term ] );
return;
}
// Add countryName with the same value as the term (particular to this question)
// If the service reads the parameter "term" instead, this line can be deleted.
request.countryName = request.term;
// Call the server only if the value was not in the cache
$.getJSON( "./countryCache", request, function( data, status, xhr ) {
cache[ term ] = data;
response( data );
});
},
select: function (event, data) {
$('#countryId').val(data.item.value);
}
});
As I didn't know exaclty the format of the JSON, I just used a basic one that for the text "In" returned: ["India","Indonesia","Spain"] (without ids, just a plain array).
If what you are using is the Ajax AutoComplete plugin for jQuery (the code above looks like it, although the question was tagged with jquery-ui-autocomplete), then you don't have to worry about caching, because the plugin does it automatically for you.
From the plugin's documentation:
noCache: Boolean value indicating whether to cache suggestion results. Default false.
As you didn't specify any value for nocache, then it will take the default value that is false, and it will perform caching directly.
I ended up not using this method at all and going with fast, quick searches with a limit of 100. But since I asked, here is how I sent requests using only the first character:
// global variables: models [], result {}
lookup: function(query, done) {
var mdl = $("#autocomplete").val();
if (mdl.length == 0) {
names = [];
result.suggestions = models;
done(result);
return;
} else if (mdl.length != 1) {
result.suggestions = names;
console.log(result);
done(result);
return;
}
var jqHXR = $.ajax({url: "search.php",
data: {"q": mdl},
dataType: "json",
method: "GET" }
)
.done(function(data, status, jqXHR){
models = [];
$.each( data, function( key, val) {
names.push({ value: val.u, data: { category: genders[val.g] } });
});
result.suggestions = names;
done(result);
})
.fail(function (data, status, errorThrown) {
console.log("failed: "+status+"| error: "+errorThrown);
console.log(data);
});
},
A colleague of mine used devbridge and my research seems to verify that there's an attribute for the devbridgeAutocomplete object for minChars and lookupLimit. Maybe there are different instances of devbridgeAutocomplete, but I thought it was worth posting just in case they're similar, though I should assume you would have seen them already :).
Here's the code:
var a = $('#<%= txtFindName.ClientID %>').devbridgeAutocomplete({
minChars: 3,
lookupLimit: 20,
serviceUrl: 'AutoComplete/ADUsers.aspx',
onSelect: function (suggestion) {
$('#<%= txtTo.ClientID %>').val( $('#<%= txtTo.ClientID %>').val() + ',' + suggestion.data);
$('#<%= txtFindName.ClientID %>').val('');
}
});
I know my questions is marked as duplicate. But the given answer is using async:false. I don't want to force synchronous requests. How do maintain async ajax call sequence ???
I don't need to replace the content. I need to append svg one after another in a sequence.
I am appending 5 svg elements in a div. All svgs are coming by ajax call. The issue is the order of those svgs. Every time they appended in different order. I want to maintain their order. Please find below my code:
FlagRow.DEFAULTS = {
flagOrder: [
Enums.flagType.INDIA,
Enums.flagType.USA,
Enums.flagType.UK,
Enums.flagType.FRANCE,
Enums.flagType.GERMANY
]
}
var container = $(document.createElement("div"));
var topic = new Array();
for (var key in this.options.flagOrder) {
topic.push(this.options.flagOrder[key]);
}
var appendFlag = function (flag) {
console.log(flag);
var svgDiv = $(document.createElement("div"));
$(svgDiv).addClass('svgDiv');
var importedSVGRootElement = document.importNode(flag.documentElement, true);
$(importedSVGRootElement).attr('viewBox', '0 0 100 125');
svgDiv.append(importedSVGRootElement)
container.append(svgDiv);
}
$.each(topic, function (i, val) {
$.when(//ajax call to get flag svg).done(function (flag ) { appendFlag(flag ); });
});
// api call to get flag svg
var deferred = $.Deferred();
$.ajax({
url: url,
type: 'get',
data: '',
dataType: 'xml',
timeout: 300000,
success: function (data) {
deferred.resolve(data);
},
error: function (e) {
console.log(':::error in flag:::', e);
},
beforeSend: function (xhr) {
xhr.setRequestHeader("Authorization", 'myapikey');
}
});
Here every time flag svg comes in different order. I want it to display it in an order of enum. And so I tried it with $.when().done(). But it's working as per my requirement.
How do I maintain order of appended svgs coming via ajax call ???
You can use async: false to mimic what you tried to do with Deferred. Since you know the order at the moment of calling your ajax requests, using placeholders as the duplicate question (for some reason they re-opened this...) suggests is your best bet.
function getAllTheFlags() {
for( var i = 0; i < 5; i++ ) {
insertPlaceHolder( i ); //inserts <div id="placeholder-i"></div> at desired location
insertFlag( i );
}
}
function insertFlag( i ) {
$.ajax( { ... } ).success( function( data ) {
var svgDiv = $(document.createElement("div"));
$(svgDiv).addClass('svgDiv');
var importedSVGRootElement = document.importNode(flag.documentElement, true);
$(importedSVGRootElement).attr('viewBox', '0 0 100 125');
svgDiv.append(importedSVGRootElement)
$( '#placeholder-' + i ).replaceWith( svgDiv );
} );
}
The function insertFlag(..) is mandatory, as you need to copy the value of i.
You can not expect async ajax call to end in order of call. But you could wrap it in a function that takes the element as parameter that you can acces in your ajax callback.
function fetchContent(element, url){
$.ajax({
url: url,
success: function(data) {
element.whatever(...);
}
});
}
In your code you then create a div or search for an existent one. And call your fetchContent by passing that element as a parameter. Even if your ajax calls don't end in the order of call the content should be added to the good element.
I think it should work.
Hello i have sequence like this
$(document).ready(function() {
$("#ctl00_txtsearch").autocomplete({
source: function(request, response) {
$.ajax({
// Here is the code of autocomplete which is requesting
// data and binding as autocomplete
});
});
});
var aa=bindonload();
});
Here is the another function which i want to call on page load
function bindonload() {
$.get( "minicart.aspx#mydatacontent", function( data ) {
var resourceContent = data;
var mini=$(resourceContent).find('div#pnlminicart');
$('#smallcart').html(mini);
});
return false;
}
So , my actual problem is when page is loaded
first of all
bindonload()
called and then autocomplete if textbox have some values right?
But when page is loaded and suddenly i started to write into autocomplete textbox then untill bindlonload function gets executed autocomplete will not work.
I don't have idea how to handle it i have used async:true but its not working i don't want to wait for second process
Thanks in advance....
Well..what i guess..should be..you loaddata() should not be taking too much of time to load.
If there is any way to optimize , look of that.
If you ajax request has a dependency on the other, then you can not make it parallel
If you really intend to make parallel ajax requests, you have to make use of the following:
$.when($.ajax("URL1"), $.ajax("URL2"))
.then(myFunc, myFailure);
Hope it helps..
Note : The Ajax calls should not be dependent
Updated:
$.when( $.ajax( "/page1.php" ), $.ajax( "/page2.php" ) ).done(function( a1, a2 )
{
// a1 and a2 are arguments resolved for the page1 and page2 ajax requests, respectively.
// Each argument is an array with the following structure:
[ data, statusText, jqXHR ]
var data = a1[ 0 ] + a2[ 0 ]; // a1[ 0 ] = "Whip", a2[ 0 ] = " It" if ( /Whip It/.test( data ) ) { alert( "We got what we came for!" );
}});
In the above example , you can see two ajax requests executes parallel
After the two requests are done, i.e on sucess of two functions , the add operation is being performed
Similarly , you can replace $.ajax("/page1.php") with your loaddata() and then
$.ajax("page2.php") with your Auto Complete request.
Both of them will execute Parallely
I have a tightly coupled javascript, where in there are series of if-else checks and multiple ajax calls are made. The ajax calls are nested type. My problem is I am in a deep nested ajax callable function and I want to get out from there gracefully.
The snippet of the code is .
function showSubscriptionLightBox() {
$.get("/ajax/get_subscription_lightbox_content.php?feed_id=" + feedid, function(data) {
//Work on the data we receive... and check whether user is logged in.
if(userLoggedIn) {
//Make one more ajax call
$.get("/ajax/is_user_subscribed.php?feed_id=" + feedid, function(data) {
//Work on data again.... and check if user is subscribed.
if(userSubscribed) {
//Then there is popup which comes up, a part of same page and it has a button name "task".
document.getElementById('task').onclick = function() {
if(document.getElementById('email_mode').checked) {
$.ajax({
url : "ajax/is_user_email_verified.php?user_id="+userID,
success : function(data) {
if(!data)
return;
var response;
response = eval("response = " + data);
if(!response)
return;
if(response['email_status'] == 0) {
//Exit from here
}}}
......
other part of code..
I want to exit gracefully from javascript, when the response['email_status'] == 0
Please tell me, how to do this??
I tried the return statement, but it took me to the enclosing function and not outside the script.
Thanks,
Amit
For what it is worth, here is some code from one of my applications. It syncs records using JSONP and AJAX. It first gets an array of object ids from a remote server. It then fetches the record for the object id at the zero index from the host server. Then it sends the record it receives to the remote server. At that point, it continues the process by starting the process with an incremented index into the array of ids. It terminates when the index reaches the end of the array.
(function( $ ) {
$.getJSON( 'http://remote.com/admin/record_ids.js?callback=?', function( data ) {
var set_record = function( index ) {
if ( index < data.length ) {
$.get( 'record_get.json', { contact_id: data[ index ] }, function( data ) {
$.getJSON( 'http://remote.com/admin/record_save.js?callback=?', data, function() {
set_record( index + 1 );
});
}, 'json');
}
};
set_record( 0 );
});
})( jQuery );
As you can see, when you want to get out gracefully, you just don't call. I can't imagine why you can't just return to stop your code.
There's a funny trick you can always use in JavaScript to escape the call stack: setTimeout(). It's useful in many situations, not just this, it is often used to work around DOM event related bugs in browsers as well.
$.ajax(
{
url: 'lol.php',
success: function(data)
{
setTimeOut(function()
{
// Your code comes here
}, 0); // 0 will ensure that it gets executed immediately
}
});
I know that with Prototype you could do this with try/catch blocks. You could throw an object from within one of the inner functions and it will travel up the call stack for other functions to intercept.