Using jQuery's load in successive functions - javascript

I am trying to populate different cells in a table, named as #RECALRow1, #RECALCol1, #RECALBodySum. Each is populated from a database. I am using AJAX and, specifically, jQuery's load.
Originally I used a number of functions - see version 1 below - which worked (the code in these functions in effectively in version 2). This worked.
I belatedly realised how similar the code in these functions was. Version 2 shows the code without functions, illustrating the similarity. This worked too. (valTable is defined earlier - the definition is not shown below).
It then seemed "obvious" that I should write a generic function which just takes two parameters. Making three calls to this function, with different parameters, should surely work(!) In fact only the third function seems to have been called; the first two do not even succeed in generating a console message.
I wondered if I am missing something on callbacks - and I read How do I return the response from an asynchronous call? - but I cannot see that I need them. Perhaps I am about to learn something very basic on practical AJAX.
Version 1
Individual functions, each of which uses jQuery load. THIS WORKS.
[Aside - ASP sets the default selected value]
UpdateCol1Possibilities(); // sets content for #RECALCol1
UpdateRow1Possibilities();
UpdateBodySumPossibilities();
Version 2
Direct call to jQuery load, without encapsulation. THIS WORKS.
[Aside - we need to tweak the default selected value]
$('#RECALRow1').load(
"/_RECAL/AJAX/AJAXSetGroupbyList.asp", // URL
{ // data - no need for callback
"RECALtable":valTable,
"RECALCol1Row1Id":'RECALRow1'
}); // close load
$('#RECALCol1').load(
"/_RECAL/AJAX/AJAXSetGroupbyList.asp", // URL
{ // data - no need for callback
"RECALtable":valTable,
"RECALCol1Row1Id":'RECALCol1'
}); // close load
$('#RECALBodySum').load(
"/_RECAL/AJAX/AJAXSetGroupbyList.asp", // URL
{ // data - no need for callback
"RECALtable":valTable,
"RECALCol1Row1Id":'RECALBodySum'
}); // close load
Version 3
Generic function, which uses jQuery load. THIS DOESN'T WORK.
var RealSelect = _.debounce(function(IdToChange) {
console.log('Calling RealSelect changing Id: ' + IdToChange);
$('#' + IdToChange).load(
"/_RECAL/AJAX/AJAXSetGroupbyList.asp", // URL
{ // data
"RECALtable":$('#RECALtable').children().val(),
"RECALCol1Row1Id":IdToChange
}
, // callback
function() { // callback function - success
// alert('successful callback!');
} // close callback function, close load
) // close load
}
,50); // end RealSelect function
RealSelect('RECALCol1'); // sets content for #RECALCol1
RealSelect('RECALRow1');
RealSelect('RECALBodySum');
// Only #RECALBodySum is populated
}

I'm not sure why you felt you should use _.debounce, but that is precisely your problem, as far as I can see.
Remove the debounce wrapper in your function declaration and all should work:
var RealSelect = function(IdToChange) {
console.log('Calling RealSelect changing Id: ' + IdToChange);
$('#' + IdToChange).load(
"/_RECAL/AJAX/AJAXSetGroupbyList.asp", // URL
{ // data
"RECALtable":$('#RECALtable').children().val(),
"RECALCol1Row1Id":IdToChange
}
, // callback
function() { // callback function - success
// alert('successful callback!');
} // close callback function, close load
) // close load
}; // end RealSelect function
Debounce means precisely that your function will NOT be called until a certain amount of time has passed where no new calls were made.
This is the expected behaviour, for example, of an autocomplete widget. You don't want to process every keystroke the user does immediately. Instead, you wait for the user to stop typing for, say, 500 millis, and then start fetching the data from the server.
In your case, you are creating a debounced function and then calling it three times in a row.
Debounce is working as expected - only the last call runs, and only after 50 millis.
From the underscorejs docs:
debounce_.debounce(function, wait, [immediate])
Creates and returns a
new debounced version of the passed function which will postpone its
execution until after wait milliseconds have elapsed since the last
time it was invoked. Useful for implementing behavior that should only
happen after the input has stopped arriving. For example: rendering a
preview of a Markdown comment, recalculating a layout after the window
has stopped being resized, and so on.
Pass true for the immediate parameter to cause debounce to trigger the
function on the leading instead of the trailing edge of the wait
interval. Useful in circumstances like preventing accidental
double-clicks on a "submit" button from firing a second time.
var lazyLayout = _.debounce(calculateLayout, 300);
$(window).resize(lazyLayout);

Related

waitForKeyElement.js But with a max wait time

When using waitForKeyElement.js I was wondering if there is a good solution to implementing a max time to wait? Sometimes I have elements that are supposed to be there, but sometimes on a poor ajax response they do not appear, or they may take a while to appear. In cases like that I simply want it to move on and execute a separate function.
Edit: For example, when trying to "buy" an item it typically always responds with a "success" box which is what the program is waiting for, however sometimes this box does not happen if the server has an error or if the item is no longer available to "buy" in which case a different error element may be inserted instead, however this is a different element and as such it continues to wait instead of continuing on to process the next item.
The question does not have an example use case, nor MCVE. There's a good chance that it is an XY Problem.
That said, there's no elegant way to set a max wait time (because there's never been neither a need, nor a demand for one before).
You can get the effect with code like:
const maxTime = 5; // seconds
var watchdogTimer = setTimeout (fallBackFunc, maxTime * 1000);
waitForKeyElements ("#foo", desiredFunc, true);
function desiredFunc (jNode) {
console.log ("Foo found!");
clearTimeout (watchdogTimer);
}
function fallBackFunc () {
console.log ("Oh, poo. No foo.");
}
Unloading the waitForKeyElements timer in such a scenario should not be necessary, but you can also do that by adding the following code at the end of fallBackFunc():
function fallBackFunc () {
console.log ("Oh, poo. No foo.");
/*-- Optional body double to take fire from waitForKeyElements that the
has `true` parameter set. This is almost never needed.
*/
var nonce = "IdeallySomeGloballyUniqueStringThatIs_a_ValidCssClass";
//-- Added node should have properties that match the WFKE selector
$("body").append (`<span id="foo" class="${nonce}" style="display:none;">blah</span>`);
setTimeout (function () {$(`.${nonce}`).remove(); }, 333); // time must be > 300
}
Regarding the question edit:
... (the page) responds with a "success" box which is what the program is waiting for, however sometimes this box does not happen... in which case a different error element may be inserted instead...
The usual practice for that is to set waitForKeyElements to listen for both the success node and the error node. (See jQuery selectors doc.)
The waitForKeyElementscallback would then take the appropriate action for the type of node passed to it.
No watchdog timer is needed (unless the page can stay active and yet not return either node type -- which is almost never the case).
For example:
waitForKeyElements ("#goodNode, #errorNode", completeTransaction, true);
function completeTransaction (jNode) {
if (jNode.is ("#goodNode") ) { // Match one of the WFKE selectores
console.log ("Transaction success!");
}
else {
console.log ("Transaction error.");
}
}
As a bonus, this approach: (A) doesn't have to wait for a max timer to run out and (B) is not subject to breaking if max timer is not set long enough for every circumstance.

Stopping synchronous function after 2 seconds

I'm using the npm library jsdiff, which has a function that determines the difference between two strings. This is a synchronous function, but given two large, very different strings, it will take extremely long periods of time to compute.
diff = jsdiff.diffWords(article[revision_comparison.field], content[revision_comparison.comparison]);
This function is called in a stack that handles an request through Express. How can I, for the sake of the user, make the experience more bearable? I think my two options are:
Cancelling the synchronous function somehow.
Cancelling the user request somehow. (But would this keep the function still running?)
Edit: I should note that given two very large and different strings, I want a different logic to take place in the code. Therefore, simply waiting for the process to finish is unnecessary and cumbersome on the load - I definitely don't want it to run for any long period of time.
fork a child process for that specific task, you can even create a queu to limit the number of child process that can be running in a given moment.
Here you have a basic example of a worker that sends the original express req and res to a child that performs heavy sync. operations without blocking the main (master) thread, and once it has finished returns back to the master the outcome.
Worker (Fork Example) :
process.on('message', function(req,res) {
/* > Your jsdiff logic goes here */
//change this for your heavy synchronous :
var input = req.params.input;
var outcome = false;
if(input=='testlongerstring'){outcome = true;}
// Pass results back to parent process :
process.send(req,res,outcome);
});
And from your Master :
var cp = require('child_process');
var child = cp.fork(__dirname+'/worker.js');
child.on('message', function(req,res,outcome) {
// Receive results from child process
console.log('received: ' + outcome);
res.send(outcome); // end response with data
});
You can perfectly send some work to the child along with the req and res like this (from the Master): (imagine app = express)
app.get('/stringCheck/:input',function(req,res){
child.send(req,res);
});
I found this on jsdiff's repository:
All methods above which accept the optional callback method will run in sync mode when that parameter is omitted and in async mode when supplied. This allows for larger diffs without blocking the event loop. This may be passed either directly as the final parameter or as the callback field in the options object.
This means that you should be able to add a callback as the last parameter, making the function asynchronous. It will look something like this:
jsdiff.diffWords(article[x], content[y], function(err, diff) {
//add whatever you need
});
Now, you have several choices:
Return directly to the user and keep the function running in the background.
Set a 2 second timeout (or whatever limit fits your application) using setTimeout as outlined in this
answer.
If you go with option 2, your code should look something like this
jsdiff.diffWords(article[x], content[y], function(err, diff) {
//add whatever you need
return callback(err, diff);
});
//if this was called, it means that the above operation took more than 2000ms (2 seconds)
setTimeout(function() { return callback(); }, 2000);

Forcing code to run sequentially in JavaScript

In a Angular.js and Socket.io App, I want to show a loading before sending an image via Socket.io. I write this code:
When a button clicked this code runs, first i want to run startLoading function and after that I want to send image:
$scope.startLoading(function(){
$scope.socket.emit('sendImg', {
data: $scope.newImg,
});
});
and this is my startLoading function:
$scope.startLoading = function (callback) {
//DO SOME STUFF LIKE ENABLING LOADING ANIMATION
$scope.animation =true; //binded to UI
$scope.errors = false; //binded to UI
callback(); //it seems this code runs before above lines
};
But it seems callback() line runs before first two lines and because of that, my loading appears after sending of image to the server! why? i change callback line to a timeout like this and it works fine but is this a good solution? i dont think! what i have to do for a standard code?
$scope.startLoading = function (callback) {
//DO SOME STUFF LIKE ENABLING LOADING ANIMATION
$scope.animation =true; //binded to UI
$scope.errors = false; //binded to UI
$timeout(function(){callback();}, 1000);
};
Actually, code runs sequentially but calling callback and sending image freezes page and because of that, my loading appears after freezing ends. but i need before freezing, loading starts
Your code really does run sequentially, but the first two lines don't change the UI immediately.
When you assign some value to a scope variable, it's just that, a variable assignment. It doesn't trigger any events. Angular will only update the UI later, when it evaluates the bindings and finds the change. So here is what happens:
$scope.startLoading = function (callback) {
// Presumably this is called from some event from Angular,
// so all this is run in an $apply, in an Angular "context".
// But this is still "plain" javascript, so the next two lines
// are just plain variable assignments.
$scope.animation =true;
$scope.errors = false;
callback(); // This function does its thing, then returns
// When this function returns, Angular will evaluate all of its
// bindings, will find that the above values have changed,
// and will update the DOM.
};
For details, see the "Integration with the browser event loop" section in the dev guide.
What you want is to ensure that the DOM is updated before your callback runs. I think there is nothing wrong with using $timeout for this.
There might be a better/nicer way, but I haven't found it yet...
So it would become something like this:
$scope.startLoading = function (callback) {
$scope.animation =true; // at this point, these are just
$scope.errors = false; // plain variable assignments
$timeout(callback); // schedule callback to run later
// After this returns, Angular will evaluate its bindings,
// and update the DOM, so if $scope.animation and $scope.errors
// are bound to something, they can trigger some visible change.
// callback will be called in the next $digest cycle, so _after_
// the DOM has been updated
};
(There is no need to specify a value for the timeout if you only want to run it in the next "tick". Also, there is no need to wrap callback, it can be directly passed to $timeout.)
Hope this helps!

How to run javascript function in "background" / without freezing UI

I've done an HTML form which has a lot of questions (coming from a database) in many different tabs. User then gives answers in those questions. Each time a user changes a tab my Javascript creates a save. The problem is that I have to loop through all questions each time the tab is changed and it freezes the form for about 5 seconds every time.
I've been searching for an answer how I can run my save function in the background. Apparently there is no real way to run something in the background and many recommend using setTimeout(); For example this one How to get a group of js function running in background
But none of these examples does explain or take into consideration that even if I use something like setTimeout(saveFunction, 2000); it doesn't solve my problem. It only postpones it by 2 seconds in this case.
Is there a way to solve this problem?
You can use web workers. Some of the older answers here say that they're not widely supported (which I guess they weren't when those answers were written), but today they're supported by all major browsers.
To run a web worker, you need to create an instance of the built-in Worker class. The constructor takes one argument which is the URI of the javascript file containing the code you want to run in the background. For example:
let worker = new Worker("/path/to/script.js");
Web workers are subject to the same origin policy so if you pass a path like this the target script must be on the same domain as the page calling it.
If you don't want to create an new Javascript file just for this, you can also use a data URI:
let worker = new Worker(
`data:text/javascript,
//Enter Javascript code here
`
);
Because of the same origin policy, you can't send an AJAX request from a data URI, so if you need to send an AJAX request in the web worker, you must use a separate Javascript file.
The code that you specify (either in a separate file or in a data URI) will be run as soon as you call the Worker constructor.
Unfortunately, web workers don't have access to neither outside Javascript variables, functions or classes, nor the DOM, but you can get around this by using the postMessage method and the onmessage event. In the outside code, these are members of the worker object (worker in the example above), and inside the worker, these are members of the global context (so they can be called either by using this or just like that with nothing in front).
postMessage and onmessage work both ways, so when worker.postMessage is called in the outside code, onmessage is fired in the worker, and when postMessage is called in the worker, worker.onmessage is fired in the outside code.
postMessage takes one argument, which is the variable you want to pass (but you can pass several variables by passing an array). Unfortunately, functions and DOM elements can't be passed, and when you try to pass an object, only its attributes will be passed, not its methods.
onmessage takes one argument, which is a MessageEvent object. The MessageEvent object has a data attribute, which contains the data sent using the first argument of postMessage.
Here is an example using web workers. In this example, we have a function, functionThatTakesLongTime, which takes one argument and returns a value depending on that argument, and we want to use web workers in order to find functionThatTakesLongTime(foo) without freezing the UI, where foo is some variable in the outside code.
let worker = new Worker(
`data:text/javascript,
function functionThatTakesLongTime(someArgument){
//There are obviously faster ways to do this, I made this function slow on purpose just for the example.
for(let i = 0; i < 1000000000; i++){
someArgument++;
}
return someArgument;
}
onmessage = function(event){ //This will be called when worker.postMessage is called in the outside code.
let foo = event.data; //Get the argument that was passed from the outside code, in this case foo.
let result = functionThatTakesLongTime(foo); //Find the result. This will take long time but it doesn't matter since it's called in the worker.
postMessage(result); //Send the result to the outside code.
};
`
);
worker.onmessage = function(event){ //Get the result from the worker. This code will be called when postMessage is called in the worker.
alert("The result is " + event.data);
}
worker.postMessage(foo); //Send foo to the worker (here foo is just some variable that was defined somewhere previously).
Apparently there is no real way to run something on background...
There is on most modern browsers (but not IE9 and earlier): Web Workers.
But I think you're trying to solve the problem at the wrong level: 1. It should be possible to loop through all of your controls in a lot less than five seconds, and 2. It shouldn't be necessary to loop through all controls when only one of them has changed.
I suggest looking to those problems before trying to offload that processing to the background.
For instance, you could have an object that contains the current value of each item, and then have the UI for each item update that object when the value changes. Then you'd have all the values in that object, without having to loop through all the controls again.
You could take a look at HTML5 web workers, they're not all that widely supported though.
This works in background:
setInterval(function(){ d=new Date();console.log(d.getTime()); }, 500);
If you can't use web workers because you need to access the DOM, you can also use async functions. The idea is to create an async refreshUI function that refreshes the UI, and then call that function regularly in your function that takes long time.
The refreshUI function would look like this:
async function refreshUI(){
await new Promise(r => setTimeout(r, 0));
}
In general, if you put await new Promise(r => setTimeout(r, ms)); in an async function, it will run all the code before that line, then wait for ms milliseconds without freezing the UI, then continues running the code after that line. See this answer for more information.
The refreshUI function above does the same thing except that it waits zero milliseconds without freezing the UI before continuing, which in practice means that it refreshes the UI and then continues.
If you use this function to refresh the UI often enough, the user won't notice the UI freezing.
Refreshing the UI takes time though (not enough time for you to notice if you just do it once, but enough time for you to notice if you do it at every iteration of a long for loop). So if you want the function to run as fast as possible while still not freezing the UI, you need to make sure not to refresh the UI too often. So you need to find a balance between refreshing the UI often enough for the UI not to freeze, but not so often that it makes your code significantly slower. In my use case I found that refreshing the UI every 20 milliseconds is a good balance.
You can rewrite the refreshUI function from above using performance.now() so that it only refreshes the UI once every 20 milliseconds (you can adjust that number in your own code if you want) no matter how often you call it:
let startTime = performance.now();
async function refreshUI(){
if(performance.now() > startTime + 20){ //You can change the 20 to how often you want to refresh the UI in milliseconds
startTime = performance.now();
await new Promise(r => setTimeout(r, 0));
}
}
If you do this, you don't need to worry about calling refreshUI to often (but you still need to make sure to call it often enough).
Since refreshUI is an async function, you need to call it using await refreshUI() and the function calling it must also be an async function.
Here is an example that does the same thing as the example at the end of my other answer, but using this method instead:
let startTime = performance.now();
async function refreshUI(){
if(performance.now() > startTime + 20){ //You can change the 20 to how often you want to refresh the UI in milliseconds
startTime = performance.now();
await new Promise(r => setTimeout(r, 0));
}
}
async function functionThatTakesLongTime(someArgument){
//There are obviously faster ways to do this, I made this function slow on purpose just for the example.
for(let i = 0; i < 1000000000; i++){
someArgument++;
await refreshUI(); //Refresh the UI if needed
}
return someArgument;
}
alert("The result is " + await functionThatTakesLongTime(3));
This library helped me out a lot for a very similar problem that you describe: https://github.com/kmalakoff/background
It basically a sequential background queue based on the WorkerQueue library.
Just create a hidden button. pass the function to its onclick event.
Whenever you want to call that function (in background), call the button's click event.
<html>
<body>
<button id="bgfoo" style="display:none;"></button>
<script>
function bgfoo()
{
var params = JSON.parse(event.target.innerHTML);
}
var params = {"params":"in JSON format"};
$("#bgfoo").html(JSON.stringify(params));
$("#bgfoo").click(bgfoo);
$("#bgfoo").click(bgfoo);
$("#bgfoo").click(bgfoo);
</script>
</body>
</html>

function not working correctly unless there is an alert

The problem
I am having is the following code will not update the tow select boxes "select1" and "select2", unless I have an alert immediately preceding the code.
Background info -
I am posting two variables to this page, "sel1val" and "sel2val", and I am assigning them to hidden input boxes with id's "sel1valtry" and "sel2valtry" respectively. I am assigning the values of these input boxes to the variables in the function and have named them "sel1val" and "sel2val". I can post more code if need be, but it is what I call "franken code"...haha! because I am a novice, it is assembled from many different styles of code.
Objective - what I am trying to achieve is to set two select boxes based upon the value of "sel1val" and "sel2val". The correct functionality is only obtained when I have an alert prior to the code.
Methods I have tried - I have left in some commented out code, to illustrate my attempts. I have been through many forums and that is where I got these ideas from. I suspect that the alert "reloads" the javascript part of the page, but I have no real basis for this. I have tried "document ready", variations of "window load", and even tried slowing things down with a delay, in case it was a timing issue. Each of the methods I have tried have resulted in the same outcome, whereby it only works with an alert.
I have achieved much success with my web based projects, and this would not have been possible without the invaluable resource in forums such as this one. I would like to thank anyone that has ever provided input/solutions, as without this I would never have been able to progress.
$(document).ready(function(){
// $(document).ajaxSuccess(function(){
// alert("AJAX request successfully completed");
// });
//$(window).load(function()
//window.onload = function()
//$(function ()
//Code goes here
//alert("got here");
//{
var sel1val = $("#sel1valtry").val()
var sel2val = $("#sel2valtry").val()
if (sel2val)
{
//alert("will not work without this alert"+sel1val);
//$("#select1").delay(2000).val(sel1val);
//$("#select1").val(sel1val);
$("#select1").prop('value',sel1val);
// var element = document.getElementById('select1');
// element.value = sel1val;
dochange('selection2', sel1val)
//var element = document.getElementById('select2');
// element.value = sel2val;
alert("will not work without this alert"+sel2val);
$("#select2").val(sel2val);
}
//}
});
//}
It seems like the dochange function is using some asynchronous API (like an AJAX call for example) which is setting value to the sel2val variable in its success callback. But since AJAX is asynchronous, this function returns immediately, not waiting for the server to return a response. By putting an alert you are blocking the execution of the next line of code and leaving enough time for the AJAX call to complete and assign a value to the sel2val variable.
The proper way to fix your code is to provide a callback to this function where you will perform the necessary actions:
dochange('selection2', sel1val, function(result) {
$("#select2").val(result);
});
And in your dochange function you will invoke this callback in the success event of your AJAX call.

Categories