I'm trying to make a javascript script running from the urlbar which for each row on a specific page clicks a button (allowing me to change a certain value), then edit that value, then click that button again to confirm. I'm running into a lot of trouble with simultaneity, as it seems to wait to do the first click 5000 ms even tho it's not being told to pause. didn't do the same when i tested the structure replacing things in between pauses with rando alerts
The reason for the pauses is bc there's a short loading animation during which I can't execute code. Anything glaringly wrong in this as-is? I know there's funky stuff w/ closure, but I thought by busywaiting i could get past it but its not.
function pause(milliseconds) {
var dt = new Date();
while ((new Date()) - dt <= milliseconds) { /* Do nothing */ }
}
var q=document.getElementById("MainContent_gvProducts").children[0];
var l=q.children.length;
for(i=1;i<l;i++){
q.children[i].children[0].children[0].click();
pause(5000);
var x=q.children[i].children[7].children[0].value;
pause(1);
x=math.round(x);
pause(5000);
q.children[i].children[0].children[0].click();
pause(5000);
}
Your pause function seems very hacky.
You should use setTimeout() instead, or something similar.
If you can afford to use the latest JS tech, I'd recommand creating a promise with a timeout and using async/await, with something like this :
async function pause(t = 1000) {
return new Promise(resolve => {
setTimeout(() => {
resolve(true)
}, t)
})
}
for(i=1;i<l;i++){
q.children[i].children[0].children[0].click();
await pause(1000)
var x=q.children[i].children[7].children[0].value;
pause(1);
x=math.round(x);
await pause(5000);
q.children[i].children[0].children[0].click();
await pause(5000);
}
Note : your for loop must be placed in an async function.
Related
I am working with a database app and I sometimes I have to click on tens or even hundreds of same buttons on a web page so I thought I save some time and run a script in the Inspect -> Console window of the Crome browser to do the job for me. I found a script which does the job just fine, however the database app hangs up after the first 10-20 clicks so I spoke with our developers and they advised that there should be a small delay between the clicks. They were not sure how much though so I need to experiment with it. So, by now I am trying to run the following script:
javascript:var inputs = document.getElementsByClassName('BUTTON CLASS HERE');
$(function theLoop (i) {
setTimeout(function () {
inputs[i].click();
if (--i) {
theLoop(i);
}
}, 100);
})(inputs.length);
It does nothing, but gives me the following error message:
gE02JAse0g9.js:60 ErrorUtils caught an error: "Cannot read property 'click' of undefined". Subsequent errors won't be logged
There seems to be some problem with calling the inputs[i].click() within the SetTimeout function, because it works just fine if I run it in a simple for loop like this:
javascript:var inputs = document.getElementsByClassName('BUTTON CLASS HERE');
for(var i=0; i<inputs.length;i++) {
inputs[i].click();
}
What am I doing wrong?
Thanks.
inputs[inputs.length] (on your first iteration, i === inputs.length) will always be undefined - array-like objects are zero-indexed in Javascript, so when inputs[i].click(); is encountered, an error is thrown.
Either initialize i to inputs.length - 1 instead, or you might find it easier to use your original code and simply await a Promise that resolves after 100ms or so:
const resolveAfter100MS = () => new Promise(res => setTimeout(res, 100));
(async () => {
var inputs = document.getElementsByClassName('_42ft _4jy0');
for(var i=0; i<inputs.length;i++) {
inputs[i].click();
await resolveAfter100MS();
}
})();
Reference code:
function sleep( sleepDuration ){
var now = new Date().getTime();
while(new Date().getTime() < now + sleepDuration){ /* do nothing */ }
}
function submit_answer(label) {
let image = get_node('img.to_label')
let size = Math.floor(Math.random() * 500)
image.src = `http://via.placeholder.com/${size}x${size}`
setTimeout(sleep.call(this, 1000), 0)
}
submit_answer is called from a click handler.
Desired function: the image is rendered, and the user is forced to wait 1 second before interacting with the page in any way.
Actual function: the user waits 1 second, and the image loads.
I thought setTimeout would put sleep on a queue - I was hoping that the image would be rendered before they were made to wait. How can I force the image to render, and then force the user to wait?
setTimeout() and setInterval() take a function reference or code as a string (but, don't do that) as their first argument. Your code:
setTimeout(sleep.call(this, 1000), 0)
passes an actual function invocation of sleep and that's why you get sleep first (it's being called immediately) and the image load second. The return value from the function invocation is what winds up getting used as the function reference, but sleep doesn't return a value, so undefined winds up being passed to the timer and so nothing happens when the timer expires. The line would need to be:
setTimeout(function(){ sleep.call(this, 1000) }, 0)
so that a function reference would correctly be the first argument and the call to sleep wouldn't happen immediately.
From the docs:
Syntax:
var timeoutID = scope.setTimeout(function[, delay, param1, param2, ...]);
var timeoutID = scope.setTimeout(function[, delay]);
var timeoutID = scope.setTimeout(code[, delay]);
NOTE: code
An alternative syntax that allows you to include a string instead of a function, which is compiled and executed when the timer expires. This syntax is not recommended for the same reasons that make using eval() a security risk.
Also, setting a timer delay of 0 will never happen. The JavaScript runtime is synchronous and will only run the callback function specified in the timer when it has nothing else to do. As a result, you can never really know for absolute certain what the delay will wind up being. Think of the delay as the minimum amount of time you can expect to wait for your function to run. Having said that, I read somewhere that there was an absolute minimum of 16ms due to the latency between the JavaScript runtime and the browser's WebAPI.
Now, you are going to need to be able to trap the moment that the image actually gets rendered and that can be accomplished with .requestAnimationFrame().
Then, what you need to do is much simpler. You set your timer to start as soon as the image has finished loading and that is done by setting up a callback on the image's load event.
But, your code does nothing to prevent the user from interacting with the page, so you'll need to add a "mask" over the page that prevents interaction.
I've made the timer 3 seconds and given the mask a grey color in the snippet below to show the effect better.
var mask = document.getElementById("mask");
function startRender() {
// Rendering started, run callback when next render occurs
requestAnimationFrame(rendered);
}
function rendered() {
sleep(3000); // Render complete
}
// Nothing happens until the image fires off its load event...
document.querySelector("img").addEventListener("load", function(){
// Run callback when next render occurs
requestAnimationFrame(startRender);
});
function preventKeystrokes(evt){
preventDefault();
}
function sleep(duration){
mask.classList.remove("hidden"); // Show mask to prevent interactions
window.addEventListener("keydown", preventKeystrokes); // prevent keystrokes
// Count to three
setTimeout(function(){
mask.classList.add("hidden"); // Remove mask
window.removeEventListener("keydown", preventKeystrokes); // Enable keyboard
}, duration);
}
#mask { position:fixed; top:0; left:0; z-index:99; background-color:rgba(0,0,0,.6); width:100%; height:100%; }
.hidden { display:none; }
<button>Try to click me!</button>
<img src="http://imgsrc.hubblesite.org/hvi/uploads/image_file/image_attachment/30466/STSCI-H-p1801a-m-2000x1692.png" alt="big image">
<div id="mask" class="hidden"></div>
I'm making a quiz-type app in which, when user gets a question, a timer of 10 seconds goes like this:
$scope.timer2 = function() {
setTimeout(function() {
console.log('times up!!!!');
}, 10000)
}
and it is being called when a question arrives like this:
timerHandle = setTimeout($scope.timer2());
And after this timer2 execution another question pops up and so on, another way of a question being popped up is that the user selects an option also then a new question comes up. So far so good but the problem is that if suppose 5 seconds were passed and then user selects an option, the "timer2" still shows "times up!!" after 5 more seconds and another timer for the new question also shows "times up!!" if the user hasn't selected any option.
What I'm trying to say is that I want the timer2 to stop when user selects any option, and then i want again this timer to be called as a new question will arrive.
This is the angular code which executes when user selects an option:-
$scope.checkanswer=function(optionchoosed){
$http.get('/quiz/checkanswer?optionchoosed='+ optionchoosed).then(function(res){
if(res.data=="correct"){
$scope.flag1=true;
$scope.flag2=false;
}else{
$scope.flag2=true;
$scope.flag1=false;
}
$http.get('/quiz/getquestions').then(function(res){
console.log("respo");
$scope.questions=res.data;
clearTimeout($scope.timerHandle); //not working
timerHandle = setTimeout($scope.timer2());
You can try using the service of AngularJS $timeout.
Then do something along these lines:
var myTimer = $timeout(function(){
console.log("hello world")
}, 5000);
....
$timeout.cancel(myTimer);
Take a look at the MDN documentation for setTimeout.
As you can see, that function returns a unique identifier.
At this point, you can call clearTimeout passing that UID as parameter:
let myTimeout = setTimeout(myFunction, millis); //Start a timeout for function myFunction with millis delay.
clearTimeout(myTimeout); //Cancel the timeout before the function myFunction is called.
Since you do not provide working example let me do the best guess. Your function does not return handle from inner setTimeout so it cannot be cancelled. What about such modifications:
$scope.timer2 = function() {
return setTimeout(function() { // added return statement
console.log('times up!!!!');
}, 10000)
}
and then
timerHandle = $scope.timer2(); // simply call timer2 that returns handle to inner setTimeout
In this code, why does the css change not complete until the while loop finishes? I know a loop hangs the browser but I would have thought the css change would be synchronous and therefore finish before the while loop even starts.
Bonus Question: Is there any way for me to get that css change to complete before moving to the while loop without giving up control of the javascript thread?
function run() {
var then = +new Date()
, now
;
$('#mydiv').css('display','block');
now = + new Date();
while (now - then < 5000) {
now = +new Date();
}
}
fiddle: http://jsfiddle.net/ezVZT/2/
Browsers don't always update the page immediately. They'll often wait with updates rendered but not painted while scripts execute and batch all the repainting together.
In your code you're applying a change to the styling, but then executing a 5 second loop which will block everything. The CSS change just has to wait.
If you need to wait five seconds before doing something use a setTimeout() call, or since you're using jQuery, look at .delay().
Would something like $elem.addClass('xyz') solve your problem? you can then assign the display: block to your class in css. Maybe that would be faster?
Try the following code. It might work.
function run() {
var then = +new Date()
, now
;
$('#mydiv').css('display','block');
$("#mydiv", window.parent.document).load($("mydiv").html());
now = + new Date();
while (now - then < 5000) {
now = +new Date();
}
}
function reset() {
$('#mydiv').css('display','none');
}
I am trying to make a simple hidden object game using javascript. When the user finds and clicks an image, I want 3 things to happen in the following order; a sound plays, the image size increases, and the image goes invisible. The problem I am running into is getting the 3 events to happen sequentially, not concurrent. Right now, seems that all three events happen all at the same time.
I've tried using setTimeout(), and while that does create a delay, it still runs all functions at the same time, even if each function is nested in setTimeout.
Example: (all this does is waits 1.5 sec then plays the sound and makes the image invisible):
function FindIt(image, id){
var t = setTimeout('sound()',10);
var b = setTimeout('bigger(' + image + ')',30);
var h = setTimeout('hide(' + image + ')',1500);
}
Below are the functions I am currently using and the actual results are: click the image, nothing happens for 2 seconds, then the sound plays and the image goes invisible.
function FindIt(image, id){
sound();
bigger(image);
hide(image);
}
function sound(){
document.getElementById("sound_element").innerHTML= "<embed src='chime.wav' hidden=true autostart=true loop=false>";
}
function bigger(image){
var img = document.getElementById(image);
img.style.width = 112;
img.style.height = 112;
}
function hide(id){
var ms = 2000;
ms += new Date().getTime();
while (new Date() < ms){} //Create a 2 second delay
var img = document.getElementById(id);
img.style.visibility='hidden';
}
Any guidance would be greatly appreciated!
To trigger things sequentially, you need to execute the second item some amount of time after the first one completes, execute the third item some amount of time after the second one completes, etc...
Only your sound() function actually takes some time, so I'd suggest the following:
function FindIt(image, id){
sound();
// set timer to start next action a certain time after the sound starts
setTimeout(function() {
bigger(image);
// set timer to start next action a certain time after making the image bigger
setTimeout (function() {
hide(image);
}, 1000); // set this time for how long you want to wait after bigger, before hide
}, 1000); // set the time here for how long you want to wait after starting the sound before making it bigger
}
FYI, the animation capabilities in libraries like jQuery or YUI make this sort of thing a lot easier.
Also, please don't use this kind of construct in your JS:
while (new Date() < ms){}
That locks up the browser for that delay and is very unfriendly to the viewer. Use setTimeout to create a delay.
For reference, using the animation libraries in jQuery, the jQuery code to handle a click on the object and then animate it over a 2 second period to a larger size, delay for 1 second, then slideup to disappear is as follows:
$("#rect").click(function() {
$(this).animate({height: 200, width: 400}, 2000).delay(1000).slideUp();
});
jQuery manages an animation queue and handles setting all the timers and doing all the sequencing and animation for you. It's a lot, lot easier to program and gives a very nice result.
You can see it work and play with it here: http://jsfiddle.net/kC4Mz/.
why don't use "event" approach. like onTaskDone();
function task1(arg, onTask1Done){
console.log(arg);
if(onTask1Done)onTask1Done();
}
task1("working", function(){console.log("task2");});
The Frame.js library is designed to elegantly handle situations like this:
function FindIt(image, id){
Frame(10, function(next) { sound(); next(); });
Frame(30, function(next) { bigger(image); next(); });
Frame(1500, function(next) { hide(image); next(); });
Frame.start();
}
Frame.js offers many advantages over using standard timeouts, especially if you are doing a lot of this kind of thing, which for a game, you likely are.
https://github.com/bishopZ/Frame.js