Multiple inputs looping slows down each() and ajax() - javascript

I have a large number of inputs on a page, each input is disabled (and hidden) by default unless a checkbox is checked. Checking a related checkbox enables the input for a user to type an amount - that works fine.
After I've typed an amount into a given box and I shift my blur focus to something else (indicating I'm done with this input), I'm looping through every visible input to check if it has an amount in it and then sending an ajax request to update the back-end (this also works but maybe approach is wrong?).
What doesn't work is when I loop through more than 5-10 checkboxes, it seems to be extremely slow or simply doesn't send the ajax requests.
Code the listens for an enabled/visible amount box to change:
$(document).on("blur", ".dollar-amount", function(){
MainPage.amountInputListener('add');
});
Here is the foreach loop, which updates each associated user's backend data with the amount in the visible field:
var MainPage = {
amountInputListener: function (type) {
$(".dollar-amount:visible").each(function () {
//Get the employee being updated
var empID = $(this).data('empid');
//get the amount
var amount = $(this).val();
//Send update request to backend
$.ajax({
type: "POST",
url: "update/amount?empid=" + empID + "&amt=" + amount + '&type=' + type,
dataType: "html"
});
});
},
}
The HTML for the input:
<input type="text" name="dollar_hour_amountX" value="0" class="form-control dollar-amount disabled" data-empid="1" tabindex="-1" disabled>
Note: dollar_hour_amountX, X is a dynamic number related to the row's employee ID. data-empid is the same dynamic number, also used in the for loop.
Things I've tried to ensure the loop works properly:
Adding async: false. This allows it to work 100% of the time, but it's extremely slow the more inputs that are added.
Adding a timeout of 100-1000ms around the entire function, this simply delays the round-trip time of the Ajax call.
I'm open to Vanilla JS suggestions if it aids in making the calls to my back-end much faster and consistent.

// capture the passed in event
$(document).on("blur", ".dollar-amount", function(e){
// give the element to the method
MainPage.amountInputListener(e.target, 'add');
});
var MainPage = {
// accept the element on the arguments
amountInputListener: function (element, type) {
// use the element in place of `this`
//Get the employee being updated
var empID = $(element).data('empid');
//get the amount
var amount = $(element).val();
//Send update request to backend
$.ajax({
type: "POST",
url: "update/amount?empid=" + empID + "&amt=" + amount + '&type=' + type,
dataType: "html"
});
},
}

Does not make sense to update everything, just update what changes.
$('.dollar-amount').on("change", function () {
console.log(this.value, $(this).data('empid'))
// make the one Ajax request
})
Or change your backend to be able to handle multiple things being sent up at once so you are not hammering the backend with a bunch of calls.

"I'm looping through every visible input to check if it has an amount in it and then sending an ajax request to update the back-end (this also works but maybe approach is wrong?)."
I would strongly recommend you change this approach. I suspect it will fix your issues. Don't loop through all of these each time. There is no need. Simply, on blur, just check if this specific input has changed, and then send an ajax call ONLY if that specific one was edited.
Just pass "this" into the amountInputListener as an argument, and then get rid of the above "each" function. The rest would be the same. Instead of $(this), just pass the argument value that represents "this" from the event.

The first and foremost thing is using a get http verb request for update should be avoided.
This is not per standard, usually get requests are used to retrieve data.
And the next thing is instead of making an ajax call for each element with callname .dollar-amount and visible, it is better to declare a global variable above the foreach block of type array of objects and then add each item in the block to that global variable and then finally make an ajax request after the for block execution is done
amountInputListener: function (type) {
var objList = [];
$(".dollar-amount:visible").each(function () {
//Get the employee being updated
var empID = $(this).data('empid');
//get the amount
var amount = $(this).val();
//Send update request to backend
objList.push({
'empId':empId,
'amt':amount,
'type': type
});
});
$.ajax({
type: "post",
url: "update/amount"
dataType: "application/json",
data:{'data':objList}
});
},
}
This way you can send all data in one shot to server and it really helps in the performance.
Note: code is just to give you an idea.

Related

Can we split big ajax calls into multiple smaller calls to load data faster?

I used below ajax call to retrieve data from database and show it in my page.
$.ajax({
type: "POST", url: "MyPage.aspx/LoadGrid",
data: "{idyear:'2020'}",
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (response) {
$(".gridBody").html(response.d);
},
failure: function (response) {
alert(response.d);
}
});
Currently this operation returns 1026 records and takes aroud 12 seconds.
since this process is time consuming and records will be more in future I have to find an alternative way to load data faster.
So, I tried another approch. I decided to get total number of records at first. for example now i have 1026 records and if I want to load my data in 100 records boundles, I need to make 11 ajax calls simultanously and combine the results at then end of all ajax calls.
I thought by applying this method I can start all calls together and I don't have to wait for ending a call to start a new one.
var pagesize = 100;
getQty(function () {
var pagesqty = Math.floor(qty / pagesize);
if (qty % pagesize > 0) {
pagesqty += 1;
}
var control = 0;
for (var i = 0; i < pagesqty; i++) {
$.ajax({
type: "POST", url: "MyPage.aspx/LoadGridMtoN",
data: "{idyear:'2020' , page:'" + i + "' , pagesize:'" + pagesize + "'}",
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (response) {
//alert(control+" succeed");
eval("var str" + i + "='" + response.d + "'");
control += 1;
if (control == pagesqty) {
var str = "";
for (var i = 0; i < pagesqty; i++) {
eval("str += str" + i);
}
$(".gridBody").html(str);
}
},
failure: function (response) {
alert(response.d);
}
});
}
});
but now I am getting time out error while executing ajax calls.
does any one knows any bettere way?
P.S: I wanted to try web worker, but It seems that I can't use JQuery in web Workwer.
P.S: I dont want to use paging. I should load all data together.
Please note that simultaneously calling endpoints from client side (instead of a single call) has more overhead and is not a good solution. For example, your server should handle more connections from client, your DB should handle more connections from your ORM in back-end, your table and correspondingly your disk and IO is challenging etc...
By the way, by considering that all the parts of your system are perfectly designed, from UX point of view, and from technical point of view, incrementally loading the data is a good solution. I mean, loading the first 100 records, while user are using them loading the second 100 records and adding them to end of the list (or when the user scroll down).
However finally you have to consider pagination! You can't load 1M records on your webpage! And no one check and use all the records in the webpage. So, you had to limit the number of records fetched and use server side pagination or provide another approach to the users, to submit their request, and then you process the request and create the report and write it in a raw text file or an excel file and email him/her the download link.
The answer you don't want to hear is that you need to fix your server-side. 1026 records really should not be taking 12 seconds, especially for data being retrieved for the client. Consider reducing the number of JOINs in your SQL statement and aggregating the data in server-side logic instead, and try running EXPLAIN against your queries and consider indices were appropriate.
To answer your question about splitting AJAX calls...
It looks like you have implemented pagination, so perhaps create an asynchronous recursive function that obtains 5-10 records at a time, incrementing the pageNum and recursing after each promise response. You can populate the page and the user will be seeing data without waiting so long. However, you must understand that this would increase the volume to your server, and it will probably end up taking longer to obtain all of the data.
I feel the way you are trying to accomplish this goal to be in bad practice.
Assuming you can make changes to the server side,
create a new table with all the fields that you are going to need on the front end
write a stored procedure to update this table on regular basis
use this table in your ajax call to fetch the records.
Use pagination. No one is going to use 1000+ records at a time
give a search option at the top, in case you feel like the user must have access to all the records.
As suggested in other answers, don't create multiple ajax calls. You will only end up regretting and creating a bottleneck for yourself in later stages

jquery ajax: process typeahead events in correct order?

I'm doing a webapp with html+jquery and a java rest-service backend.
I have a textfield, with typeahead suggestions, so every character the user types in the field
will trigger a server-round trip and update the list of typeahead suggestions.
Essential parts of the code:
var showTypeaheadSuggestions = function(data) {
// update ui-element ...
}
var displayFailure = function() {
// update ui-element ...
}
var searchText = $("#searchText");
var searchTextKeyup = function() {
var txt = searchText.val();
$.ajax({
url : typeaheadUrl(txt),
type : 'GET',
dataType : 'json',
}).done(showTypeaheadSuggestions).fail(displayFailure);
}
searchText.on('keyup', searchTextKeyup);
It's basically working.
But I was thinking abt what happens if you type, for example, 2 letters "ab" (that will trigger first a request for "a" and then a request for "ab")...
Then, what happens if the "a" response takes a bit longer to process, and arrives after the "ab" response?
Do I need to detect this in my code, to throw away the "a" response?
In http://api.jquery.com/jquery.ajax/ it does says:
Promise callbacks — .done(), .fail(), .always(), and .then() — are
invoked, in the order they are registered.
What does that mean exactly?
I was hoping this means $.ajax() would automatically handle the above scenario correct.
But when I do a small test (on the server-side I simply injected a 2 secs sleep-delay, only when the search-string is exactly "a"),
it turns out it does not behave as I expected.
The typeahead list will first get updated with the "ab" response, and then when the "a" response
arrives, it also updates, so the typeahead list gets the wrong suggestions.
What is the established way to handle this correctly?
There's another approach if you want to keep the server side code without changes. You can actually wrap the return functions inside a class and create instances for each request, then store the latest instance in a global scoped variable and check if the owner of the invoked method does match the latest instance:
var lastRequest;
var searchText = $("#searchText");
function requestClass()
{
var that = this;
this.showTypeaheadSuggestions = function(data) {
//Update ui-element
if (lastRequest == that)
console.log('suggestions retrieved: ' + data);
else
console.log('this response (' + data + ') is ignored');
};
this.displayFailure = function() {
//Update ui-element
console.log('failure');
};
}
var searchTextKeyup = function() {
var request = new requestClass();
lastRequest = request;
var txt = searchText.val();
$.ajax({
url : typeaheadUrl(txt),
type : 'GET',
dataType : 'json',
}).done(request.showTypeaheadSuggestions).fail(request.displayFailure);
}
searchText.on('keyup', searchTextKeyup);
I have tested this with the small-test you proposed in the question (adding a 2 seconds delay when the search string does match the 'a' character) and the result is the following:
suggestions retrieved: ab
this response (a) is ignored
One of the ways I approached this problem was to assign an ID for each time you call it, and pass it as an ID to server side. When your server is done processing it, it then sends the result back along with it's id.
Then, every time the client side code runs, the ID will increment. For example:
var callback_id = 0;
var searchText = $("#searchText");
var searchTextKeyup = function() {
callback_id ++;
var txt = searchText.val();
$.ajax({
url : typeaheadUrl(txt),
data : callback_id,
type : 'GET',
dataType : 'json',
}).done(showTypeaheadSuggestions).fail(displayFailure);
}
searchText.on('keyup', searchTextKeyup);
Then, when you receive the response back, you check if the id is the current one. In the event that the user fires two events at once, your ajax event will be triggered twice, once with callback_id = 0, and one with callback_id = 1.
The last thing you have to then do is an if statement only updating your TypeaheadSuggestions if the callback_id is the most current one by comparing the id sent back from your server response.
You must compare new input text with text you sent, and if it what user wants to find - you will show it, otherwise do nothing with response.
For example :
var searchText = $("input").val()
$.ajax({
....
data: {searchText : searchText}
success: funtion(){
if($("input").val()==searchText){
//SHOW RESULTS
}
}
})
The Promises interface returns to you a "Promise object" immediately so that you can use a different syntax for the callback.
Instead of:
asyncCall(callback);
You can use:
asyncCall()
.then(callback);
And you can chain these:
authorizeAccount()
.then(getNames)
.then(processNames);
The callbacks will be executed in the order you register them -- processNames will wait for getNames to resolve first.
The "established" way to handle this "correctly" is probably to add some client-side debouncing so that only the whole request ('query' instead of 'q', 'qu' 'que'...) is processed when you are typing the word:
http://underscorejs.org/#debounce with a timeout that expires a response if it takes too long to come back.

Change value of dynamic inputs

I have a modal window that has a lot of new dynamic elements (inputs, buttons, etc.). I want to see if a certain element(or in this case, and input) gets created and if it does, then change its value.
The scenario is that if I make an ajax request for populating data, and as the user browses the modal window I can reuse some of that data. When the input field I'm looking for gets created, I can just put the value of the ajax call I made previously.
I have tried: $("#myinput_id").val(sellerData['id']);
obviously the above wont work because the element doesn't exists, yet. I'm also trying to avoid new ajax calls for the same data :/
Any thoughts?
$( "#add").bind('click', function() {
$.ajax({
url: '/seller/get',
type: 'POST',
success: function(response) {
sellerData = jQuery.parseJSON(response);
//other code here
//this doesn't work
$("#myinput_id").val(sellerData['id']);
}
});
});
Then the above gets triggers. The input field doesn't exist yet. How can I make it "look for it" if in the future the input field gets created?
Try using .length http://api.jquery.com/length/
if($("#myinput_id").length) //There is at least one element selected
//Do something
a bit confusing question you are saying u want to populate the data and your are using POST

How do I trigger events after event handlers are bound in ajax calls when the page loads?

Problem:
I've got 2 drop downs, both populated by information from a database and dependent on some initial information (a json array created in php). The second drop down is also dependent on what was selected in the first dropdown. Once the second dropdown has been selected, the users selections are saved in another database table. If the user leaves and then comes back, I want to be able to reset the drop downs to what they previously selected (but still let them change their selections).
Context:
I have some code that looks like the following:
function eventHandler2(event) {
$.ajax({
url: "someotherurl.php",
data: event.data["context"],
success: function(data) {
// Do some stuff
}
});
}
function eventHandler1(event) {
event.data["context"][$(event.target).id()] = $(event.target).val();
$.ajax({
url: "someurl.php",
data: event.data["context"],
success: function(data) {
$("#element").append(build_input("input2", data));
$("#element input2").change({"context": event.data["context"]}, eventHandler2);
}
});
}
$(document).ready(function() {
var context = // php generated json array.
$("#element").append(build_input("input1", context));
$("#element input1").change({"context": context}, eventHandler1);
});
context includes some data that has to be initialized outside of the event handler and is added to durring events, and build_input just returns an html string of a form input element built with options determined by data or someData. At times the entire html element that includes eventHandler1 (and eventHandler2) will be removed and reinitialized.
My issue is that I want to initialize the two form inputs (built using build_input) with some default values pulled from a database, but once those values have been set I don't want any reinitialization or event handling to use those initial values (which makes adding them to the context object difficult since that object will be used when binding the event handlers).
I would like to set the value for the inputs and then call a change event to simulate a user selecting those values, but because the input elements are built using ajax data, those change events are being getting called before the ajax request have returned and built the input elements.
I could solve this with timeouts, but because the the javascript might run slower on different machines and the ajax calls could take longer than normal, I don't plan on doing it that way. Still, ideally I would like something like this:
$("#element .input1 option[value='" + initial_value + "']").attr('selected', true);
$("#element .input1").change();
to be called when the page loads but only after the elements have been built and bound.
Question:
Is there any way to trigger change events (simulating the user selecting options) only when the page is first loaded and only after the ajax request to build the html input elements have returned?
Is there any way to trigger change events (simulating the user selecting options) only when the
page is first loaded
and only
after the ajax request
to build the html input elements have returned?
These are two separate events. If you need to ensure both events have ocurred use <body onload="loadedBody=true"> and take advantage of the success: attribute of the $.ajax function like this:
function eventHandler1(event) {
event.data["context"][$(event.target).id()] = $(event.target).val();
$.ajax({
url: "someurl.php",
data: event.data["context"],
success: function(data) {
$("#element").append(build_input("input2", data));
$("#element input2").change({"context": event.data["context"]}, eventHandler2);
ajaxComplete();
}
});
}
var ajaxCompleted;
function ajaxComplete() {
if((loadedBody == true) && (ajaxCompleted == false)) {
$("#element .input1 option[value='" + initial_value + "']").attr('selected', true);
$("#element .input1").change();
} else {
setTimeout('ajaxComplete()', 100);
}
}
Don't worry about the setTimeout. It will rarely be used because the page will likely have loaded before the AJAX result arrives.

update javascript variable with ajax in real-time

I have a javascript function which needs to do a numerical calculation. Some of the numbers used in this calculation are stored in a database, and they will differ depending on how a user fills out an online form. Once the user fills out the form, they will click the CALCULATE button. At this time, in the JS function, I would like to use ajax to get values from a database that correspond to some other value chosen by the user.
For a simple example: there are 3 sizes of t-shirts, with different prices based on each size (stored in database). The user chooses the size, and when they click CALCULATE, I use ajax to get the price associated with the size they chose.
The question is, i want to use ajax to update some variables that I will use later on in the script. The way I am trying to do it now doesn't work, the variable in the script doesn't get updated from ajax, I can only access the value from the database inside the success function of the ajax call. I understand this is because ajax in asynchronous by nature, and it takes some time, waiting for the data to be returned from the server, while the function still continues to run
In the following example, the ajax call returns JSON data, and I have a function called isjson() that tests if the returned string is in fact JSON data.
Example code:
function calculate_cost(){
var price = 0;
var size = $('form#tshirt_form [name="size"] option:selected').val();
$.ajax({
url:'my_script.php',
type:'post',
data:'select=price&table=tshirts.prices&where=size = "' + size + '"',
success:function(data){
if(isjson(data)){
data = $.parseJSON(data);
data = data[0];
price = data['price'];
}else{
//display error getting data
}
}
});
// continue code for calculation
// this alert will display "0", but I want the price from the database in there
alert(price);
//perhaps do other ajax calls for other bits of data
//...
return final_price;
}
Does anyone know how I can accomplish this, updating variables with ajax in real-time??
Thanks a lot!
** EDIT **
Thanks everyone for the help, I understand about ajax being asynchronous. I would really like an answer where I don't have to continue the calculation inside the success function, because my actual problem involves many values from quite a few different tables. I would also like to be able to expand on the calculation in the future without it getting too convoluted. If this is not possible, ever, than i will have to live with that.
;-)
** EDIT 2 **
OK, we got the answer: of course it is right near the top on the docs page, :-/ sorry about that. The async property in jQuery ajax call. http://api.jquery.com/jQuery.ajax/
That is because ajax executes the request asynchronously by default and before the control reaches alert(price); the request has not yet executed and price still holds the old value.
If you want to execute it synchronously then you can set async to false.
$.ajax({
async: false,
.....
.....
});
you need to calculate inside the success function
function calculate_cost(){
var price = 0;
var size = $('form#tshirt_form [name="size"] option:selected').val();
$.ajax({
url:'my_script.php',
type:'post',
data:'query=select price from tshirts.prices where size = "' + size + '"',
success:function(data){
if(isjson(data)){
data = $.parseJSON(data);
data = data[0];
price = data['price'];
// continue code for calculation
// this alert will display "0", but I want the price from the database in there
alert(price);
//perhaps do other ajax calls for other bits of data
//...
}else{
//display error getting data
}
}
});
// return final_price; this function wont be able to return a value
}
ajax is asynchronous and for this reason you should refactor your code so that you do what you need to do in the callback
$.ajax({
url:'my_script.php',
type:'post',
data:'query=select price from tshirts.prices where size = "' + size + '"',
success:function(data){
if(isjson(data)){
data = $.parseJSON(data);
data = data[0];
price = data['price'];
//call another function (maybe make another ajax call) from here
dosomethingwithprice(price);
}else{
//display error getting data
}
}
});
Your ajax code takes time to execute (albeit not much); however the code after the ajax call is executed asynchronously, and most likely before the results of the ajax call come in.
Instead, why don't you try moving alert(price) into the body of the if(isjson(data)) region, and then execute a callback function which returns the price to whatever other utility you need it to be used at?
you have to do your calculation inside callback stack. Ajax work async, that means that, codes outsides callback function run before callback function start. So you should do your calculation in callback.
function calculate_cost(){
var price = 0;
var size = $('form#tshirt_form [name="size"] option:selected').val();
$.ajax({
url:'my_script.php',
type:'post',
data:'query=select price from tshirts.prices where size = "' + size + '"',
success:function(data){
if(isjson(data)){
data = $.parseJSON(data);
data = data[0];
price = data['price'];
}else{
//display error getting data
}
// continue code for calculation
// this alert will display "0", but I want the price from the database in there
alert(price);
//perhaps do other ajax calls for other bits of data
//...
return final_price;
}
});
}
I suggest having the ajax call return a deferred object. Then, when the final price is completely calculated, resolve the deferred object with that value.
function calculate_cost() {
var price = 0,
size = $('#tshirt_form [name="size"] option:selected').val(),
def = $.Deferred();
$.ajax({
data: {size:size},
url: 'my_script.php',
type: 'post',
dataType: 'json',
}).done(function(data){
data = data[0];
price = data['price'];
def.resolve(price);
}).fail(function(){
// do on error stuff
});
return def.promise();
}
// ...
calculate_cost("large").done(function(price){
alert(price);
}).fail(function(){
alert("Failed to retrieve price");
});

Categories

Resources