I have a set of dynamic dropdown menus which are populated using JQuery/Javascript via a javascript array. As you select different parent dropdown values the children change. Fairly basic stuff.
We have an issue where a another javascript function needs to use a child dropdownlist value in order to process.
As the scripts run asyncronously there are situations where the value is not populated by the time the second script needs the value.
I was hoping to use setTimeout to 'wait' for the value to be set by the first script before continuing the second script.
I lifted the code below from another thread but it only ever seems to wait 'once', it never waits 50ms and then wait again if the condition is not set.
var waitForEl = function(selector, callback) {
if (($('#'+selector).val())) {
console.log('good to go');
callback();
} else {
setTimeout(function() {
console.log('paused for thought');
waitForEl(selector,callback);
},50);
}
};
$('[id^="currentcontainersizeid"]').each(function() {
thisId = $(this).attr('id').replace(/^\D+/g, "")
if (($('#currentcontainersizeid'+thisId).length > 0) && ($('#currentcontainersizeid'+thisId).val())) {
waitForEl('containersizeid'+thisId, function() {
console.log('FIRE TRIGGER');
$('#containersizeid'+thisId).trigger('change');
});
}
});
I am not sure whether it is my test that is wrong or the timeout logic. Should I be checking whether the element is 'selected' rather than has a 'val'. If so how would I do that?
Does the code above look correct to achive what I want?
Thoughts/help appreciated.
Related
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.
I design a new survey and in one of my multiple choice questions, the alternatives are supposed to be hidden for 1 sec and so that the user is inclined to spend more attention to the question before answering.
So far my code is only able to hide the choices but waiting and showing is still missing. (code below)
Thanks for any help or suggestions on how to solve this issue.
Qualtrics.SurveyEngine.addOnload(function () {
this.hideNextButton();
this.hideChoices();
//HERE I WOULD LIKE THE CODE TO WAIT FOR 1 sec
//HERE I WOULD LIKE THE CHOICES TO REAPPEAR ON THE SCREEN
this.questionclick = function (event, element) {
if (this.getChoiceValue(4) == true) {
this.clickNextButton();
} else if (this.getChoiceValue(5) == true) {
this.clickNextButton();
}
}
});
There are two parts of the problem here.
How to wait one second? That's done with setTimeout() and a callback function.
How to make sure the callback function works on the right object? - That's done by storing the object we want to work on in a variable.
So, without knowing anything about Qualtrics SurveyEngine, it is clear from your code that this inside the onLoad callback refers to your survey object. We will need that object later in the setTimeout callback, so let's store it. The rest is easy:
Qualtrics.SurveyEngine.addOnload(function () {
var survey = this;
// attach click event handler
self.questionclick = function (event, element) {
// using `survey` instead of `this` would work here, too
if (this.getChoiceValue(4) || this.getChoiceValue(5)) {
this.clickNextButton();
}
};
// hide buttons
survey.hideNextButton();
survey.hideChoices();
// after 1000ms, show buttons again
setTimeout(function () {
survey.showNextButton();
survey.showChoices();
}, 1000);
});
Is there a way to pause a function to check the state of an element and only continue when the state has changed?
Here's my function :
JS
download : function() {
var loading = $('#stats-table > div > div.jtable-busy-message').html();
while (loading.length > 0) {
alert("Table still loading, please wait to download report");
loading = $('#stats-table > div > div.jtable-busy-message').html();
}
// continue with the rest of the function here
}
What should I replace the illustrative while loop with in the above function in order to pause the function and check the .length of loading for a change before proceeding with the function.
For information, the loading variable either has a length of 18 and 0 - so simply checking for this change in length.
So until the length changes do not continue with the rest of the method body.
UPDATE
I've found a simple way of doing this, so no longer require any answers - thanks
No, Javascript is (mostly) single threaded and its impossible for the state to change unless some other piece of code changed it. Any such change cannot happen "mid function". Your while loop will just cause the browser to lock up, permanently.
If you're waiting for an asynchronous load operation to complete, then your code needs to continue from within that operation's completion callback.
$.fn.exists = function(callback) {
var args = [].slice.call(arguments, 1);
if (this.length) {
callback.call(this, args);
}
return this;
};
// Usage
$('div.test').exists(function() {
//do your stuff
});
This might helps you :)
I am writing a webpage with the following structure:
One section (table A) depends on another section (table B);
Another section (table B) has elements that require recalculation on each update. The calculation is handled by external tools, and will cause an event when finished.
In order to guarantee correctness, the table need to be updated only after the other table is fully updated (i.e., done with computation). However, I don't know how to effectively achieve this, and I could not find any wait facility within JavaScript.
For now, I am using the following method:
Declare a global variable updated and make it false;
After the first table received input, I make an empty while loop until updated is true;
Add an listener, once the calculation is done and the event received, set updated to true.
This seems unintuitive to me but I cannot think of any other way of doing it. Is there any good ways to do this?
Thanks for any inputs!
In 2022, it's useful to have an event listener that fires off a Promise (which can be used in promise-chains, or async/await code). A clean way to make one:
function getPromiseFromEvent(item, event) {
return new Promise((resolve) => {
const listener = () => {
item.removeEventListener(event, listener);
resolve();
}
item.addEventListener(event, listener);
})
}
async function waitForButtonClick() {
const div = document.querySelector("div")
const button = document.querySelector("button")
div.innerText = "Waiting for you to press the button"
await getPromiseFromEvent(button, "click")
div.innerText = "The button was pressed!"
}
waitForButtonClick()
<button>ClickMe</button>
<div></div>
Add an listener, once the calculation is done and the event received, set updated to true.
Instead of setting updated to true, and then waiting for updated to be true- just do whatever you want to do in the listener.
myEventBus.addListener(function () {
// do whatever
updateTable();
alert('table updated!');
});
Doing empty while loops is a bad idea. Not only do you burn CPU cycles, but Javacript is single threaded so you will loop forever without giving anyone a chance to change the variable.
What you can do is rewrite the table that has other people depending on it to "fire an event itself". There are many ways to do this, but basicaly you just want it to call a "continuation' function instead of blindily returning. This function can be predefined or you can pass it as a parameter somewhere.
//this is just illustrative
//Your actual code will be probably very different from this.
function update_part(){
//do something
signal_finished_part()
}
var parts_done = 0;
function signal_finished_part(){
parts_done ++;
if(parts_done >= 5){
signal_all_parts_done();
}
}
function signal_all_parts_done()
{
//do something to table A
}
You could write a callback function for whatever triggers the update. To avoid messy callbacks, you could use promises too, and update parts of the table depending on the data retrieved in the update operation. Open to suggestions.
I'm creating a content rotator in jQuery. 5 items total. Item 1 fades in, pauses 10 seconds, fades out, then item 2 fades in. Repeat.
Simple enough. Using setTimeout I can call a set of functions that create a loop and will repeat the process indefinitely.
I now want to add the ability to interrupt this rotator at any time by clicking on a navigation element to jump directly to one of the content items.
I originally started going down the path of pinging a variable constantly (say every half second) that would check to see if a navigation element was clicked and, if so, abandon the loop, then restart the loop based on the item that was clicked.
The challenge I ran into was how to actually ping a variable via a timer. The solution is to dive into JavaScript closures...which are a little over my head but definitely something I need to delve into more.
However, in the process of that, I came up with an alternative option that actually seems to be better performance-wise (theoretically, at least). I have a sample running here:
http://jsbin.com/uxupi/14
(It's using console.log so have fireBug running)
Sample script:
$(document).ready(function(){
var loopCount = 0;
$('p#hello').click(function(){
loopCount++;
doThatThing(loopCount);
})
function doThatOtherThing(currentLoopCount) {
console.log('doThatOtherThing-'+currentLoopCount);
if(currentLoopCount==loopCount){
setTimeout(function(){doThatThing(currentLoopCount)},5000)
}
}
function doThatThing(currentLoopCount) {
console.log('doThatThing-'+currentLoopCount);
if(currentLoopCount==loopCount){
setTimeout(function(){doThatOtherThing(currentLoopCount)},5000);
}
}
})
The logic being that every click of the trigger element will kick off the loop passing into itself a variable equal to the current value of the global variable. That variable gets passed back and forth between the functions in the loop.
Each click of the trigger also increments the global variable so that subsequent calls of the loop have a unique local variable.
Then, within the loop, before the next step of each loop is called, it checks to see if the variable it has still matches the global variable. If not, it knows that a new loop has already been activated so it just ends the existing loop.
Thoughts on this? Valid solution? Better options? Caveats? Dangers?
UPDATE:
I'm using John's suggestion below via the clearTimeout option.
However, I can't quite get it to work. The logic is as such:
var slideNumber = 0;
var timeout = null;
function startLoop(slideNumber) {
//... code is here to do stuff here to set up the slide based on slideNumber...
slideFadeIn()
}
function continueCheck() {
if (timeout != null) {
// cancel the scheduled task.
clearTimeout(timeout);
timeout = null;
return false;
} else {
return true;
}
};
function slideFadeIn() {
if (continueCheck){
// a new loop hasn't been called yet so proceed...
$mySlide.fadeIn(fade, function() {
timeout = setTimeout(slideFadeOut,display);
});
}
};
function slideFadeOut() {
if (continueCheck){
// a new loop hasn't been called yet so proceed...
slideNumber=slideNumber+1;
$mySlide.fadeOut(fade, function() {
//... code is here to check if I'm on the last slide and reset to #1...
timeout = setTimeout(function(){startLoop(slideNumber)},100);
});
}
};
startLoop(slideNumber);
The above kicks of the looping.
I then have navigation items that, when clicked, I want the above loop to stop, then restart with a new beginning slide:
$(myNav).click(function(){
clearTimeout(timeout);
timeout = null;
startLoop(thisItem);
})
If I comment out 'startLoop...' from the click event, it, indeed, stops the initial loop. However, if I leave that last line in, it doesn't actually stop the initial loop. Why? What happens is that both loops seem to run in parallel for a period.
So, when I click my navigation, clearTimeout is called, which clears it.
What you should do is save the handle returned by setTimeout and clear it with clearTimeout to interrupt the rotator.
var timeout = null;
function doThatThing() {
/* Do that thing. */
// Schedule next call.
timeout = setTimeout(doThatOtherThing, 5000);
}
function doThatOtherThing() {
/* Do that other thing. */
// Schedule next call.
timeout = setTimeout(doThatThing, 5000);
}
function interruptThings() {
if (timeout != null) {
// Never mind, cancel the scheduled task.
clearTimeout(timeout);
timeout = null;
}
}
When a navigation element is clicked simply call interruptThings(). The nice part is that it will take effect immediately and you don't need to do any polling or anything else complicated.