Reduce number of ajax calls in instant search - javascript

I am trying to make an instant search drop down for my site. All work fine, except for this.
var timeOut;
$('#search input[name=\'search\']').on('keyup', function(e) {
// If enter - submit the search field
if (e.keyCode == 13) {
$('header input[name=\'search\']').parent().find('button').trigger('click');
}
// Call only when length is at least 2 and the key pressed is alphanumeric
else if ($('#search input[name=\'search\']').val().length>2 && ((e.keyCode>=65 && e.keyCode<=90) || (e.keyCode>=97 && e.keyCode<=122))) {
timeOut = null;
//alert(timeOut);
if (!timeOut) {
timeOut = setTimeout(function() {
$.ajax({
url: 'ajax.php',
type: 'post',
async: false,
data: 'ACTION=SEARCH&search='+$('#search input[name=\'search\']').val(),
dataType: 'json',
beforeSend: function() {
$('#loader-icon').show();
},
complete: function() {
$('#loader-icon').hide();
},
success: function(json) {
//$('.product-list-row').html(json);
$('#search-listing').html(json['html']);
},
error: function(xhr, ajaxOptions, thrownError) {
alert(thrownError + "\r\n" + xhr.statusText + "\r\n" + xhr.responseText);
}
});
timeOut = null;
}, 500);
}
}
});
Problem 1: My script ends up making too many calls to the server, for some reason setTimeOut isn't working as I expected it to. Ajax call should only be made when the user has done typing or something like that and not at every key press.
Problem 2: For some reason when I type fast the input bar doesn't get edited. The ajax however works, but with the last textual input.

Instead of trying to tackle this with setTimeout, you can abort the previous call using the jqXHR object returned by the AJAX. It is more clean and simple to use this. Remove async: false, too.
var timeOut;
var xhr;
$('#search input[name=\'search\']').on('keyup', function(e) {
// If enter - submit the search field
if (e.keyCode == 13) {
$('header input[name=\'search\']').parent().find('button').trigger('click');
}
// Call only when length is at least 2 and the key pressed is alphanumeric
else if ($('#search input[name=\'search\']').val().length>2 && ((e.keyCode>=65 && e.keyCode<=90) || (e.keyCode>=97 && e.keyCode<=122))) {
if(xhr && xhr.readyState != 4){
xhr.abort();
}
xhr = $.ajax({
url: 'ajax.php',
type: 'post',
data: 'ACTION=SEARCH&search='+$('#search input[name=\'search\']').val(),
dataType: 'json',
beforeSend: function() {
$('#loader-icon').show();
},
complete: function() {
$('#loader-icon').hide();
},
success: function(json) {
//$('.product-list-row').html(json);
$('#search-listing').html(json['html']);
},
error: function(xhr, ajaxOptions, thrownError) {
alert(thrownError + "\r\n" + xhr.statusText + "\r\n" + xhr.responseText);
}
});
}
});

The right solution is combination of both, abort running request, if new one should be made, and also, tackle firing of the new request. You can use underscore library, which has nice function for that, called debounce (see http://underscorejs.org/#debounce) and your code should looks like this:
// This is your xhr, each request save into this
// variable, in order to be able to abort it if needed
var xhr;
// Wrap your event handler using the debounce function
$("#search").on("keyup", _.debounce(function(e) {
// Abort running request
if(xhr) {
xhr.abort();
xhr = null;
}
// Store the new request
xhr = $.ajax({
// Search for the term $(this).val()
});
},500));
There is no need to fire the search for each keyup, but only when user stopped typing - debounce will do it for you. And there is no need to handle previous results if the request should be made.

Related

Abort all remaining AJAX requests

I am running an AJAX request when the user types in an input field and then displaying the result on the page. When the user presses the backspace to delete all of what they've inputted, I use .empty to remove the result from the page.
However, if you press the backspaces really quickly, the result is removed from the page, but then because the last AJAX query hasn't last executed, the result from that query appears!!!
I have looked at Abort Ajax requests using jQuery but that didn't help, and have tried adding return: false; after $("#results").empty(); to no avail.
If there are any remaining AJAX calls when if(this.value.length < 1) { is true, I would like to abort them all inside that function.
$("input#enter").keyup(function() {
if(this.value.length < 1) {
$("#display").empty();
}else{
$.ajax({
type: "POST",
url: "getdata.php",
data: "title=" + this.value,
success: function(data) {
$("#display").empty();
$("#display").html(data);
}
});
}
});
You can use $.active to check if $.ajax() call is active before calling next $.ajax()
$("input#enter").keyup(function() {
if(this.value.length < 1) {
$("#display").empty();
}else{
if (!$.active) {
$.ajax({
type: "POST",
url: "getdata.php",
data: "title=" + this.value,
success: function(data) {
$("#display").empty();
$("#display").html(data);
}
});
}
}
});
You can also include attaching .ajaxComplete() to document to call next $.ajax() call when current call completes
function request(value) {
return $.ajax({
type: "POST",
url: "getdata.php",
data: "title=" + value,
success: function(data) {
$("#display").empty();
$("#display").html(data);
}
});
}
$("input#enter").keyup(function() {
if(this.value.length < 1) {
$("#display").empty();
}else{
if (!$.active) {
request(this.value)
} else {
$(document).one("ajaxComplete", function() {
request(this.value)
})
}
}
});
One approach to abort requests is to use XMLHttpRequest(), push requests to an array, then call .abort() on each element of the array
function request(data) {
let fd = new FormData();
fd.append("html", data);
fd.append("delay", Math.floor(Math.random() * 10));
let xhr = new XMLHttpRequest();
xhr.open("POST", "/echo/html/", true);
xhr.onload = function() {
console.log(xhr.responseText);
}
xhr.onabort = function() {
console.log("request " + requests.indexOf(xhr) + " aborted")
}
xhr.send(fd);
return xhr
}
function abortAllRequests() {
requests.forEach(function(xhr, index) {
xhr.abort()
})
}
var requests = [];
requests.push(request(123), request(456));
abortAllRequests();
jsfiddle https://jsfiddle.net/onguym5y/
You talk about aborting ajax requests. It would be sufficient to wait until the request returns and then simply do nothing. Yes, if you were doing a lot of large requests it might improve performance if you cancelled them. But that means using jqXhr objects, and personally I prefer to stick to jQuery where possible.
You could have a variable telling you how up-to-date the #display is. It would store the time of sending of the last ajax request that was used to update it. If you get a response from an earlier request, ignore it.
var lastUpdateTime = 0;
$("input#enter").keyup(function() {
var now = new Date().getTime();
if(this.value.length < 1) {
$("#display").empty();
lastUpdateTime = now;
}else{
$.ajax({
type: "POST",
url: "getdata.php",
data: "title=" + this.value,
success: function(data) {
if (now < lastUpdateTime) {
return;
}
$("#display").empty();
$("#display").html(data);
lastUpdateTime = now;
}
});
}
});

ajax loading indicator stopped in between

I am saving data on a save button click that calls ajax and passing json data to a controller method but when we save it loading starts and suddenly stop though the data is not saved.
It is not working I have tried it in all way but not working please help me on this.
<button type="button" id="saveDeleg" class="btn_reg_back btnmainsize btnautowidth btngrad btnrds btnbdr btnsavesize " aria-hidden="true" data-icon="">#Resources.Resource.Save</button>
$('#saveDeleg').click(function() {
var response = Validation();
if (!response) {
return false;
}
$("#overlay").show();
$('.loading').show();
if ($('#organName').val() == '') {
$('#validorganisation').show();
return false;
} else {
$('#validorganisation').hide();
}
//Contact name
var SubDelegation = $('#subdelegation').is(':checked');
var CopyNotification = $('#copynotification').is(':checked');
var ArrangementId = $("#ArrangementId").val();
var paramList = {
ArrangementId: ArrangementId,
ArrangementName: $('#arrangName').val(),
OrganisationName: $('#organName').val(),
OrganisationId: $('#OrganisationId').val(),
ContactName: $('#contactName').val(),
ContactId: $('#ContactId').val(),
SubDelegation: $('#subdelegation').is(':checked'),
CopyNotification: $('#copynotification').is(':checked'),
ContactType: $('#ContactType').val(),
SelectedTypeName: $("input[name$=SelectedType]:checked").val()
};
setTimeout(function() {
$.ajax({
async: false,
type: "POST",
url: '#Url.Action("SaveDelegation", "Structures")',
dataType: "json",
contentType: "application/json; charset=utf-8",
data: JSON.stringify(paramList),
processdata: true,
success: function(result) {
//stopAnimation()
paramList = null;
if (result == 0) {
window.location.href = '../Structures/MyDelegationArrangement';
} else if (result == 1) {
window.location.href = '../Structures/CreateDelegation';
} else if (result == 2) {
window.location.href = '../Home/Error';
} else if (result == 3) {
window.location.href = '../Account/Login';
} else {
//validation message
alert('Error');
}
},
error: function() {},
complete: function() {
$("#overlay").hide();
$('.loading').hide();
}
});
}, 500);
});
The problem with the loading indicator is because you used async: false which locks up the UI. Remove that setting.
Also note that if the data is not being saved I would assume that your AJAX call is returning an error. If so, check the console to see the response code. It may also be worth putting some logic in the error callback function to give you some information on whats happened, as well as inform your users about what to do next.

Adding a delay to JQuery keyup() after Ajax call

I'm having a quite tough problem and I'm not sure how to approach it. I have a few textboxes in a row and I need to fill in these textboxes. Every time a textbox is filled, I grab the value and make an Ajax call that uses the value. The response determines whether or not that very textbox is colored red or green(using the Jquery css() function).
Now here's the problem. Let's say I have 5 textboxes in a row. Let's say I type 1-tab, 2-tab, 2-tab, 1-tab, 1-tab. All of this very fast. 1-tab, for example, means I type 1 followed by the Tab button to move to the next textbox. I realized that if I go too fast, some of the textboxes don't get updated and their colors do not change. I assumed this is due to the ajax taking some time to process.
I thought about the problem and came up with an idea that might solve the problem. That is add a delay after each Ajax call and then tab to the next. I search around S.O and found this solution. However, it's not really working for me(basically it breaks and the JS doesn't work at all).
Here's a snippet of my AJAX. I stripped it down and removed the unnecessary pieces of code.
$( ".myTextbox" ).keyup(function() {
//Defining my variables here
$.ajax({
//Perform First Ajax request
$.ajax({
//Perform Second Ajax Request
});
});
});
Here's the solution I tried using what I found from S.O, but it doesn't work.
var timer = null;
$( ".myTextbox" ).keyup(function() {
clearTimeout(timer);
timer = setTimeout(
function(){
.ajax({
//Perform First Ajax request
$.ajax({
//Perform Second Ajax Request
});
});
}, 200);
//Defining my variables here
});
Now, there are 2 options:
My logic is wrong about delaying the tab key. Could there be some better logic to overcome my initial problem?
I'm using the solution posted above wrongly.
Hope to get some constructive answers.
Thanks.
EDIT: Here's the full code, upon request.
$( ".getqty" ).keyup(function() {
var split = this.id.split(":");
var color = split[0];
var size = split[1];
var prodID = split[2];
var $this = $(this);
var value = $this.val();
var stock = 0;
var price = split[3];
var originalProd = split[4];
var dataStock = $this.attr("data-stock");
if(value.length > 0){
value = parseInt(value);
}else{
value = "";
}
$.ajax({ //create an ajax request
type: 'POST',
url: 'includes/add.php',
dataType: 'html', //expect html to be returned
data:'color='+color+'&size='+size+'&prodID='+prodID+'&qty='+value+'&originalProd='+originalProd+'&dataStock='+dataStock,
success: function(response){
if(response == "breakOut"){
$this.css('background-color', '#F87171').css('border', '1px solid #B42C2C');
$("#"+originalProd+"-"+color).text("Not enough in stock.").css('color', '#B42C2C');
$("#"+originalProd+"-totalPrice").text("");
}else{
stock = response;
if((value > 0 && value <= stock) || (value > 0 && dataStock == 'yes')){
$this.css('background-color', '#66CF66').css('border', '1px solid #277230');
}else{
$this.css('background-color', '#fff').css('border', '1px solid #ccc');
}
var count = 0;
$("."+color+"-" + originalProd).each(function(){
if($(this).val() == 0){
count = count + 0;
}else{
count = count + parseFloat($(this).val(), 10);
}
});
//Single Item Total
if(count > 0){
var totalPrice = (price * count).toFixed(2);
$("#"+originalProd+"-"+color).text(count + " - " + totalPrice.toString().replace(/\./g, ',') + " Eur").css('color', '#CCC');
}else{
$("#"+originalProd+"-"+color).text("");
}
$.ajax({ //create an ajax request
type: 'POST',
url: 'includes/cart.php',
dataType: 'html', //expect html to be returned
success: function(response){
if(response > 0){
$("#cart_price").text("Cart: "+response.toString().replace(/\./g, ',')+ " Eur");
}else{
$("#cart_price").text("Cart:0,00 Eur");
}
},
error:function (xhr, ajaxOptions, thrownError){
// alert(thrownError);
}
});
if(pathname == 'mycart.php'){
location.reload();
}
}
},
error:function (xhr, ajaxOptions, thrownError){
//alert(thrownError);
}
});
You should use the change event instead of keyup. From the docs:
The keyup event is sent to an element when the user releases a key on
the keyboard. It can be attached to any element, but the event is only
sent to the element that has the focus.
When you press tab your elements will change focus quickly and maybe the keyup event will not be fired for that input text with the right value content.
So try:
$( ".getqty" ).change(...)
Update:
Since the change event just fires when the input text loses focus, you could write instead:
$( ".getqty" ).on('input', function() {
var $this = $(this);
var value = $this.val();
if (value.length > 0) {
value = parseInt(value);
}
else {
value = "";
}
$.ajax({
type: 'POST',
url: 'data.txt',
dataType: 'text',
success: function(response){
$this.css('background-color', '#66CF66').css('border', '1px solid #277230');
$.ajax({
type: 'POST',
url: 'data.txt',
dataType: 'text',
success: function(response){
$("#cart_price").text("Cart: "+response.toString().replace(/\./g, ',')+ " Eur");
},
error:function (xhr, ajaxOptions, thrownError){
console.log(thrownError);
}
});
},
error: function (xhr, ajaxOptions, thrownError){
console.log(thrownError);
}
});
});
Or with pure javascript event listeners:
var elemList = document.getElementsByClassName('getqty');
for (var i = 0; i < elemList.length; i++) {
elemList[i].addEventListener('input', function(e) {
var $this = $(e.target);
var value = $this.val();
if (value.length > 0) {
value = parseInt(value);
}
else {
value = "";
}
$.ajax({
type: 'POST',
url: 'data.txt',
dataType: 'text',
success: function(response){
$this.css('background-color', '#66CF66').css('border', '1px solid #277230');
$.ajax({
type: 'POST',
url: 'data.txt',
dataType: 'txt',
success: function(response){
$("#cart_price").text("Cart: "+response.toString().replace(/\./g, ',')+ " Eur");
},
error:function (xhr, ajaxOptions, thrownError){
console.log(thrownError);
}
});
},
error: function (xhr, ajaxOptions, thrownError){
console.log(thrownError);
}
});
});
}
You can try this to delay on keyup
$('input').keyup(function() {
delay(function(){
alert('Time elapsed!');
}, 1000 );
});

Synchronize Ajax Calls and executeQueryAsync SharePoint JS CSOM

I have a problem synchronizing calls using Rest Api and JavaScript Object Model.
I'm currently working with Client Side Rendering to customize a view for a Document Library and add some functionalities in this custom UI.
I have a small collection of id's, and I'm looping through this collection and make some ajax calls with each of this items.
The results of this operation is to perform some tasks and to update my UI when all these operations are completed to refresh my UI and display some icons.
What I expect is to have 3 icons displayed only for my three first items.
The problem is that sometimes it displays all the icons, sometimes the two first... randomly.
I know that there is some problems with the synchronization of my executeQueryAsync calls, I've learned about jQuery Deferred object, I've tried to use them but without results.
Below you'll find screenshots of what I expect.
Expected :
https://onedrive.live.com/redir?resid=E2C3CC814469DA54!3070&authkey=!AEf_C0XGDwfuFRY&v=3&ithint=photo%2cpng
What would be the good way of using deferred ? Could anyone help ?
Thanks a lot
Elhmido
This is my main function for overriding the display :
(function () {
var accordionContext = {};
accordionContext.Templates = {};
// Be careful when add the header for the template, because it's will break the default list view render
accordionContext.Templates.Item = itemTemplate;
// Add OnPostRender event handler to add accordion click events and style
accordionContext.OnPreRender = [];
accordionContext.OnPreRender.push(function () {
$(function () {
IsCurrentUserMemberOfGroup("TEST Owners");
**$.when(IsUserApprover(arrayOfIDS).done(function () {
displayIcons();
}));**
});
});
accordionContext.OnPostRender = [];
accordionContext.OnPostRender.push(function () {
$(function () {
accordionOnPostRender();
fixColumns();
audit.relativeUrl = _spPageContextInfo.webAbsoluteUrl;
});
});
SPClientTemplates.TemplateManager.RegisterTemplateOverrides(accordionContext);
})();
The function where I have the problem,
function IsUserApprover(auditTab) {
var dfd = $.Deferred();
audit.tabIcons = new Array();
for (var i = 0; i < auditTab.length; i++) {
var uri = _spPageContextInfo.webAbsoluteUrl + "/_api/web/Lists/GetByTitle('Audit')/items?$select=UserID&$filter=ID eq " + auditTab[i] + "";
var call = $.ajax({
url: uri,
type: "GET",
dataType: "JSON",
async: false,
headers: {
"Accept": "application/json;odata=verbose"
}
});
call.done(function (data, status, jqxhr) {
SP.SOD.executeFunc('sp.js', 'SP.ClientContext', function () {
var userId = data.d.results[0].UserID;
var context = SP.ClientContext.get_current();
var auditor = context.get_web().ensureUser(userId);
context.load(auditor);
//I think the problem is here because I don't know how to handle this call
context.executeQueryAsync(userLoaded, userFailed);
function userLoaded() {
var auditorId = auditor.get_id();
checkAuditorValidator(auditorId);
dfd.resolve();
}
function userFailed(sender, args) {
alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
}
});
});
call.fail(function (jqxhr, status, error) {
alert(JSON.stringify(error))
dfd.reject();
});
}
return dfd.promise();
}
function checkAuditorValidator(auditorId) {
var uri = _spPageContextInfo.webAbsoluteUrl + "/_api/web/Lists/GetByTitle('SecurityMgmt')/items?" +
"$select=Auditeur/ID,Validateur/ID" +
"&$expand=Auditeur/ID,Validateur/ID" +
"&$filter=(Auditeur/ID eq '" + auditorId + "') and (Validateur/ID eq '" + _spPageContextInfo.userId + "')";
var call = $.ajax({
url: uri,
type: "GET",
dataType: "JSON",
async: false,
headers: {
"Accept": "application/json;odata=verbose"
}
});
call.done(function (data, status, jqxhr) {
if (data.d.results.length > 0) {
if (audit.UserAdmin) {
audit.tabIcons.push(true);
}
}
else {
audit.tabIcons.push(false);
}
});
call.fail(function (jqxhr, status, error) {
alert(JSON.stringify(error))
});
}
Starting with Gecko 30.0 (Firefox 30.0 / Thunderbird 30.0 / SeaMonkey 2.27), synchronous requests on the main thread have been deprecated due to the negative effects to the user experience.
You should avoid synchronous ajax calls...
I had the same problem and solved by adding an id during the custom rendering of the fields (items), on the postrender call my service asynchronously and according the result edit the OnPreRender page using the previously added ids.
I also did some hacks...e.g overriding the standard function RenderItemTemplate. Yes I know, it's not very clean but it works like a charm.

Updating jQuery Item during Ajax call

I am making an ajax call to send out a couple of emails when the user clicks a button. I am trying to update a "Please wait..." div before and after the call with the status, as well as report any errors. The problem... is that the div doesn't actually update until the ajax call is complete.
If I comment out the ajax call the status update works fine, but fails if the ajax call takes place. My Windows programming experience tells me the div needs to be refreshed, but I'm not sure how this is done, or if this is the right method.
For example:
$("#msgEmail").show();
$("#msgEmail").html("Preparing to send");
$.ajax({ blah blah blah...
Any pointers in the right direction will be much appreciated!
On nnnnnn's suggestion I started testing on other browsers. The problem occurs on Chrome and Safari, but works as expected on Firefox and SeaMonkey. It sure looks like he's right about this. Now I just need to figure out to implement setTimeout() in this scenario.
Update: Code:
.click(function() {
$('#myTable :checkbox:checked').each(function() {
sId = $(this).attr('cid');
sName = $(this).attr('cname');
ret = true;
$("#msgImage").slideDown();
sUpdate = 'Sending alerts to class: '+ sName;
$("#msgEmail").slideDown();
$("#msgEmail").html(sUpdate);
sSubject = "Notificatiom";
sMessage = $('#message').val();
sData= "cid="+sId+'&sname='+sName+'&subject='+sSubject+'&message='+encodeURIComponent(sMessage);
$.ajax({
url: 'dostuff.php',
data: sData,
dataType: 'text',
type: 'POST',
async: false,
success: function (msg)
{
if(msg >= '1')
{
ret = true;
}
}
});
if(ret)
$("#msgEmail").html('Finished sending alerts');
$("#msgImage").slideUp();
ret = false;
return false;
})
Place your ajax call in the callback for 'slideDown'. Set the message before calling 'slideDown'. This will ensure your message is updated before the ajax call is sent.'
sUpdate = 'Sending alerts to class: ' + sName;
$("#msgEmail").html(sUpdate);
$("#msgEmail").slideDown(function() {
sSubject = "Notificatiom";
sMessage = $('#message').val();
sData = "cid=" + sId + '&sname=' + sName + '&subject=' + sSubject + '&message=' + encodeURIComponent(sMessage);
$.ajax({
url: 'dostuff.php',
data: sData,
dataType: 'text',
type: 'POST',
async: false,
success: function(msg) {
if (msg >= '1') {
ret = true;
}
}
});
});
I ll get you an on hw this stuff is going to be
$(document).ready(function(){
$.ajax({
url :'YOur url',
.....
.....
ajaxSend :function(){
$('#yourdiv').html('please wait...');
}
success:function(msg){
if (msg >= '1') {
$('#yourdiv').html('validated');
}
}
});
}):

Categories

Resources