How to run one function right after another in JavaScript? - javascript

I'm automating a task on Paypal that involves clicking a couple buttons on a page in succession.
Basically, I need to click a radio button and then click a "next" button to advance to the following page. But I can't advance to the next page unless the radio button is clicked first.
I currently have the second function on a timer, but is there a way to start the second function after the first function finishes and the first radio button's pressed?
Here's my code:
// ==UserScript==
// #name PayPal Transfer Radio
// #include https://www.paypal.com/myaccount/money/balances/withdraw/balance/*
// #require http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js
// #grant GM_addStyle
// ==/UserScript==
$(function(){
$('[id="BA-SMSNY4E48EHGC__ACH__STANDARD_FUNDSRadioBtn"]').click()
});
setTimeout(function(){
$('[name="selectFiNext|standard"]').click();
}, 2000);
The setTimeout method works, as is, but it's not optimal.

setTimeout is the simplest, most reliable method. When patching pages you don't own, it's a great approach.
If you want to click the next button as soon as it appears, decrease the timeout, and if the button doesn't exist yet, set a new timeout. There's also no need to rely on a big dependency like jQuery here:
document.querySelector('[id="BA-SMSNY4E48EHGC__ACH__STANDARD_FUNDSRadioBtn"]').click();
const clickNext = () => {
const btn = document.querySelector('[name="selectFiNext|standard"]');
if (btn) btn.click();
else setTimeout(clickNext, 50);
};
setTimeout(clickNext, 50);
If you really want to avoid polling, you can also use a MutationObserver on the parent of the button, but that gets significantly more complicated.
If the button appears after a network request, another option that can work is to watch for when network requests complete, with ajaxComplete if the site is using jQuery, or by monkeypatching window.XMLHttpRequest.

You need to bind an event handler to the "click" JavaScript event, or trigger that event on an element. as follows:
$(function(){
$('[id="BA-SMSNY4E48EHGC__ACH__STANDARD_FUNDSRadioBtn"]').click(function(){
$('[name="selectFiNext|standard"]').click();
});
$('[id="BA-SMSNY4E48EHGC__ACH__STANDARD_FUNDSRadioBtn"]').click();
});
The following code snippet attaches an event handler click to the element with an id of BA-SMSNY4E48EHGC__ACH__STANDARD_FUNDSRadioBtn which will trigger the event handle click for the element with attribute(s) name="selectFiNext|standard".

Just wanted to share a more modern approach that does the same as the accepted answer, but is easier to read in my opinion:
// Converts set timeout from callback approach to promise approach
function AsyncTimeout(delay = 0) {
return new Promise(function (resolve, reject) {
setTimeout(resolve, delay);
});
}
(async () => {
// possibly, you might or might not want to awit a bit before you start
// await AsyncTimeout(100);
document.querySelector('[id="BA-SMSNY4E48EHGC__ACH__STANDARD_FUNDSRadioBtn"]').click();
let btn = null;
// safeguard to prevent endless loop if something goes wrong
let iterations = 0;
while (btn == null) {
await AsyncTimeout(50);
// try to assign the btn
btn = document.querySelector('[name="selectFiNext|standard"]');
// remember the iteration count
++iterations;
// if we tried too many times, just give up
if (iterations > 100) {
throw new Error("Couldn't find the button to click");
}
}
btn.click();
})();

Related

JS Foreach Alternative (timeout) for Queue

I'm working on the "Approve All" button. The process here is when I click "Approve All," each individual "Approve" button will be triggered as "click" all at once, and then it will send POST requests to the controller. However, when I clicked Approve All button, there was a race condition causing the controller returns Error 500: Internal server error. I have tried using JS setTimeout() with value 1500*iter, but when the iterator gets higher, for example at i = 100, then it would take 1500*100 => 150000ms (150s). I hope that explains the problem clearly. Is there a way to prevent such a case?
Here is my code, I'm using JQuery:
let inspection = $this.parents("li").find("ul button.approve"); // this will get all 'approve' button to be clicked at once
inspection.each((i,e)=>{
(function () {
setTimeout(function () {
$(e).data("note",r);
$(e).click();
}, 1500 * i); // this acts like a queue, but when i > 100, it takes even longer to send POST requests.
})(this,i,e,r);
});
// then, each iteration will send a POST request to the controller.
$("#data-inspection ul button.approve").on("click", function() {
// send POST requests
});
Any help would be much appreciated. Thank you.
That 500 error may also be the server crashing from being unable to process all the requests simultaneously.
What I'd recommend is using an event-driven approach instead of setTimeout. Your 1500ms is basically a guess - you don't know whether clicks will happen too quickly, or if you'll leave users waiting unnecessarily.
I'll demonstrate without jQuery how to do it, and leave the jQuery implementation up to you:
// use a .js- class to target buttons your buttons directly,
// simplifying your selectors, and making them DOM agnostic
const buttonEls = document.querySelectorAll('.js-my-button');
const buttonsContainer = document.querySelector('.js-buttons-container');
const startRequestsEvent = new CustomEvent('customrequestsuccess');
// convert the DOMCollection to an array when passing it in
const handleRequestSuccess = dispatchNextClickFactory([...buttonEls]);
buttonsContainer.addEventListener('click', handleButtonClick);
buttonsContainer.addEventListener(
'customrequestsuccess',
handleRequestSuccess
);
// start the requests by dispatching the event buttonsContainer
// is listening for
buttonsContainer.dispatchEvent(startRequestsEvent);
// This function is a closure:
// - it accepts an argument
// - it returns a new function (the actual event listener)
// - the returned function has access to the variables defined
// in its outer scope
// Note that we don't care what elements are passed in - all we
// know is that we have a list of elements
function dispatchNextClickFactory(elements) {
let pendingElements = [...elements];
function dispatchNextClick() {
// get the first element that hasn't been clicked
const element = pendingElements.find(Boolean);
if (element) {
const clickEvent = new MouseEvent('click', {bubbles: true});
// dispatch a click on the element
element.dispatchEvent(clickEvent);
// remove the element from the pending elements
pendingElements = pendingElements.filter((_, i) => i > 0);
}
}
return dispatchNextClick;
}
// use event delegation to mitigate adding n number of listeners to
// n number of buttons - attach to a common parent
function handleButtonClick(event => {
const {target} = event
if (target.classList.contains('js-my-button')) {
fetch(myUrl)
.then(() => {
// dispatch event to DOM indicating request is complete when the
// request succeeds
const completeEvent = new CustomEvent('customrequestsuccess');
target.dispatchEvent(completeEvent);
})
}
})
There are a number of improvements that can be made here, but the main ideas here are that:
one should avoid magic numbers - we don't know how slowly or quickly requests are going to be processed
requests are asynchronous - we can determine explicitly when they succeed or fail
DOM events are powerful
when a DOM event is handled, we do something with the event
when some event happens that we want other things to know about, we can dispatch custom events. We can attach as many handlers to as many elements as we want for each event we dispatch - it's just an event, and any element may do anything with that event. e.g. we could make every element in the DOM flash if we wanted to by attaching a listener to every element for a specific event
Note: this code is untested

Qualtrics, Javascript: how to implement a pause and show previously hidden choices?

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);
});

Execute a second .click() event after the first is done

I would like to "chain" two .click() calls but cannot get it to work.
The code does work when I debug the JS code in my browser (so with a delay it seems to work)
I somehow need the first .click() to load the page (that's what the first event does) and only if that is done, I want the second .click() to execute.
My Code:
$.post("settings?type=mail&nr=" + nr, function(data){
if(data != ""){
alert(unescape(data));
// First click event -> realoads the page
$("#change_settings").click();
// Second click event -> navigates to a tab
// inside the page loaded by the first click event
$("#tab_mail_header" + nr + "").click();
}
});
EDIT: More Code
function load_change_settings_view(e){
e.preventDefault();
$("#content").empty();
// load settings.jsp in a div (test) inside the mainpage
$("#content").load("settings.jsp #test", function(){
// In here are a couple of .click() & .submit() functions but nothing else
});
});
$("#change_settings").click(function(e){
load_change_settings_view(e);
});
EDIT: I currently have this code:
$("#change_settings").click();
window.setTimeout(function () {
$("#tab_mail_header" + nr + "").click();
}, 1000);
I dont really like it though, as it is a timed delay and it may be the case (on a slow client) that that 1 second timeout will not be enough. I don't want to set the timeout too high as this slows down the workflow for users with a faster client...
I looked though a couple of post like these:
JQuery .done on a click event
Wait for a user event
How to wait till click inside function?
Wait for click event to complete
Does anyone have an idea on how to get this to work?
after a few more attempts I ended up with the following solution:
Code Snippet#1
$.post("settings?type=mail&nr=" + nr, function(data){
if(data != ""){
alert(unescape(data));
// create event object
var evt = document.createEvent('MouseEvents');
evt.initEvent('click', true, false);
// call method manually (not called by actual button click like its supposed to be)
// - pass event object
// - additional parameter to specify the tab the user is viewing
load_change_settings_view(evt, "tab_mail_header" + nr);
}
});
Code Snippet#2
function load_change_settings_view(e, p_tab){
e.preventDefault();
$("#content").empty();
// load settings.jsp in a div (test) inside the mainpage
$("#content").load("settings.jsp #test", function(){
// Go to previous tab (if one was selected)
var prev_tab = p_tab;
if(typeof prev_tab != 'undefined'){
$("#" + prev_tab).click();
}
// In here are a couple of .click() & .submit() functions but nothing else
});
});
feel free to comment if you have a better idea on how to solve this problem or if you have any other suggestions

How to let JavaScript wait until certain event happens?

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.

dojo mouseover with delay

I wish to do something like as follows:
When the mouse goes over to some element, record it
If the mouse stays there for 3 seconds, then execute some action f() for that element
If the mouse leaves that element before 3 seconds, then the action should not be executed.
How can I implement this delayed execution with possible cancellation? An answer using DOJO library would be nicer since I am using DOJO toolkit in my project.
Try the following:
var delay = 3000;
dojo.forEach(dojo.query(".some-element-set"), function(element) {
dojo.connect(element, "onmouseover", function() {
// dojo.partial(f, this.id) will apply `this.id` to `f`, but it
// will not execute it and will only a new function
this._timer = setTimeout(dojo.partial(f, this.id), delay);
});
dojo.connect(element, "onmouseout", function() {
// this._timer was set as an element attribute in the above event
// handler so we don't have to keep track of them separately in
// some silly array/object
clearTimeout(this._timer);
});
});
See the query, forEach, connect and partial documentation for more information.
Edit: I've update my answer per the OP's comment

Categories