Trying to automate a task in the browser with Greasemonkey.
Requirement: load a web page which contains a table displaying 15 out of 340+ results, with button controls to see the next page worth of results, go back, go to the end and so forth.
Collect the first 15 results --> got this working
Click on the 'Next' button --> got this working
Wait until the page loads the next set of results --> dont know how
Repeat until the next button is disabled --> got this working
Cant show pictures cuz its a corporate app.
So far if I loop with setTimeouts, the timeouts get queued up and executed at the same time I think. Instead, the requirement is for the code to run the info collection, click next, wait, then collect info again.
You could try a utility function waitForKeyElement, a greasemonkey utility script. It is essentially doing what you were trying to do before. It sets a timeout to continuously checks to see if an element is loaded. Executing it would look something like this:
waitForKeyElements (
"div.comments",
commentCallbackFunction
);
Where comment callback function would be the function that collects results.
It would help also to understand why setTimeout in a loop doesn't work. setTimeout does not block execution of a script to run your callback function. It queues up that function to be executed no earlier than the time that you pass in as a second parameter. Because of this, the loop runs very quickly and instead of getting function calls at 3000ms, 6000ms, and 9000ms... as you might expect you get something closer to function calls at 3000ms, 3001ms, 3002ms.
You can execute arbitrary JS logic mixed with API calls, recursion and timeouts sequentially via synchronous executor nsynjs:
function synchronousCode() {
var i=0;
while(true) {
console.log("waiting 1 sec, iteration", i++);
$('#myDiv').toggle();
nsynWait(nsynjsCtx,1000);
};
}
var ctx;
function btnStopClicked() {
ctx.stop();
ctx=null;
}
function btnStartClicked() {
ctx=nsynjs.run(synchronousCode,{},function(){
console.log("Synchronous Code done");
});
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>
<script src="https://rawgit.com/amaksr/nsynjs/master/wrappers/nsynWait.js"></script>
<body>
<button id="buttonStart" onclick="btnStartClicked()">Start</button>
<button id="buttonStop" onclick="btnStopClicked()">Stop</button>
<div id="myDiv">Flashing div</div>
</body>
See more examples here: https://github.com/amaksr/nsynjs/tree/master/examples
Thanks people, found this post to be the best answer
Synchronous setTimeout + Loop
This way the page loads, the first values are read, the next button is clicked, next results appear and are read, until the last page.
Thanks again for the input and ideas
you can use it
for(const elment of arrayElements) {
await yourFunc(elment)
await yourOtherFunc('somePatameter')
}
Related
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 7 years ago.
Improve this question
I have an APEX application with a button that is calling a javascript function. The function is doing some screen scraping and takes a few seconds to complete. I want to display a spinner icon first to provide the sense that something is happening. However, even though I have the spinner displaying with a jquery .show() call prior to the function call, they seem to both get executed at the same time....seconds after the button click. It's as if the jquery .show() won't execute until the function call is complete. How do I get the .show() to execute prior to the call to the function? I even tried adding the .show() to the first line of the function but the result is the same. Spinner doesn't display until after the function has completed.
Show accepts a callback function as a parameter. See example below.
jQuery.show() documentation
function buttonHandler() {
$(".spinner").show(function() {
// this will be executed after show has completed
});
}
How do I get the .show() to execute prior to the call to the function?
You call .show, then yield back to the browser for a tiny bit of time, before calling the function, like this:
function buttonHandler() {
$("...").show();
setTimeout(theFunction, 0);
}
You'll have to experiment with your target browsers to determine whether you need a value larger than 0. The value is in milliseconds, so anything up to 50 or so will be largely-unnoticable to a human.
By not calling the function until after you've yielded back to the browser, you give the browser a chance to update the display.
Note that the spinner may not be animated while the function is tying things up, it depends on the browser. You may prefer to show something static but dynamic-looking (the way "go faster" stripes are) to avoid inconsistency between browsers.
Live example using 50ms:
$("#the-button").on("click", buttonHandler);
function buttonHandler() {
$("#spinner").show();
$("#done").hide();
setTimeout(theFunction, 50);
}
function theFunction() {
var target = Date.now() + 1000;
while (Date.now() < target) {
// Do nothing, we're just simulating a busy function
}
$("#spinner").hide();
$("#done").show();
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<input type="button" id="the-button" value="Click me">
<div id="spinner" style="display:none">We're doing it, one sec!</div>
<div id="done" style="display:none">Function is done now</div>
i want to expand a division with data that should be loaded from another server.
the problem is if i start expanding the division (toggleslide) and the load method finishs the height is jumping and the effect is destroyed.
what i want is that jquery starts expanding AFTER the data-transfer finished but my following code does not work:
// divison name = details
details.load("index.php", expandLastResultDetails3(details));
function expandLastResultDetails3(details) {
$(details).slideToggle('slow', function () {
ready();
});
}
the box is jumping because (i guess) the slidetoggle starts to early.
.load() expects a function callback to be called upon the completion of the request. However, instead of passing a callback, you are executing the function. Try the following:
details.load("index.php", function () {
expandLastResultDetails3(details);
});
I have (basically) this code -
<script type="text/javascript" language="javascript">
$(document).ready(function() {
$("#loadDiv").load("mypage.html", function(){ alert($("#someText").text()) });
});
</script>
<div id="loadDiv"></div>
However the alert will display "undefined" since the html has not been rendered into the div yet.
I've seen this question - How to wait for jQuery's load function to render loaded content before executing callback function - and Jake's answer is the best for my situation. On my actual page I have eight of these and with a two second delay there are still some of them running before the html is rendered.
Does anyone have a better suggestion than increasing the delay to silly times?
According to the documentation, the load method already waits for the content to be placed in the element before calling the function:
"If a "complete" callback is provided, it is executed after
post-processing and HTML insertion has been performed."
I have tested this, and it does find the loaded content in the element when the callback function runs.
If it doesn't work in your code, you are trying to do something other than what you have shown in the question.
You should be able to wrap your code in the callback with a setTimeout() with a timeout of 0 (browsers are meant to clamp at 4 microseconds, but not all do).
This will push the code onto the event queue, giving the browser a chance to render before the code is executed.
You can set the html empty(if you don't have other html element in the div while the request is loading).
After the success (the complete event)you load the content (now I don't know if the response is an html or a text, i've done the example following your code).
You can also set a predefinite text if the request fail.
Check also this link for more information jquery\load, but this should be work
$("#loadDiv").load("mypage.html", function(response, status, xhr) {
if (status == "success") {
$("#loadDiv").html("");
}
else if(status == "complete"){
$("#loadDiv").text();
}
});
Background
I've inherited an ancient web application that has input controls with custom behaviors defined with an old-fashioned HTC (HTML Component) script, e.g.:
<input name="txtFiscalYearEndDay" type="text" value="30"
maxlength="2" size="5" id="txtFiscalYearEndDay" class="Text1"
style="behavior:url(/path/js/InFocus.htc);" />
Here are the relevant parts of this HTC file to illustrate the issue:
<PUBLIC:COMPONENT tagName="InFocus">
<PUBLIC:METHOD NAME="setValid" />
<PUBLIC:ATTACH EVENT="ondocumentready" HANDLER="initialize" />
<SCRIPT LANGUAGE="javascript">
function initialize() {
// attaches events and adds CSS classes, nothing fancy
}
function setValid(bInternal) {
// checks some flags and changes a label
}
</SCRIPT>
</PUBLIC:COMPONENT>
So, nothing out of the ordinary so far. Additionally, I have some JS that runs on DOM-ready:
$(function() {
txtFiscalYearEndDay_Validate(document.getElementById('txtFiscalYearEndDay'));
});
And the validation function:
function txtFiscalYearEndDay_Validate(el) {
...
}
Note: I'm not using $('#txtFiscalYearEndDay') because then I really can't try to call setValid(true); on the element, nor do I want to have to do $('#txtFiscalYearEndDay')[0].setValid(true);.
The problem
At one point in the validation function, I'm attempting to call a method on the element, the one added by the HTC script:
el.setValid(true);
However, the IE debugger gets sad and complains that setValid() is not a function. Inspecting it in the debugger confirms this:
typeof el.setValid // "unknown"
Of course, once the page has completed rendering (or whatever period of time is needed for the document to actually be ready has passed), the validation function works as expected (because I'm calling the same validation function on change and blur events as well). That is, when the function is called outside of jQuery's on-DOM-ready function, it works just fine.
Do any of you have any ideas at to what might be happening here? Is jQuery's "ondomready" being registered before the HTC script's "ondomready"? Can I somehow change that order?
I'm currently seeing this behavior in all versions of IE.
EDIT: WORKAROUND
I discovered a workaround. If you take the function call out of the jQuery ready function and throw it at the end of the page, it works (i.e.:)
...
<script type="text/javascript">
txtFiscalYearEndDay_Validate(document.getElementById('txtFiscalYearEndDay'));
</script>
</body>
</html>
I do not know if HTC counts toward page ready but i suspect they do not.
What you might try is check something that only is tru after the HTC hase finished.
You own script should then start something like this:
function MyFunction() {
if(!HTCIsreadyTest()) {
setTimeout(MyFunction, 100);
return;
}
//the rest of your code
}
This basically makes you function check and restart in 100 milliseconds if conditions are not met untill the test succeds.
You could also ad a counter argument increasing it by one for each attempt to have some timeout code trigger if HTC sciprts has not loaded after 2 seconds
The easiest workaround I could find was to move the validation function call out of the jQuery ready() callback and move it to the end of the page:
...
<script type="text/javascript">
txtFiscalYearEndDay_Validate(document.getElementById('txtFiscalYearEndDay'));
</script>
</body>
</html>
However, I found a more elegant solution. Because I seemingly need to wait for all page resources to be loaded, I simply needed to move the function call out of the jQuery ready() callback and instead put it in a window load() callback:
$(window).load(function() { // instead of $(function() {
txtFiscalYearEndDay_Validate(document.getElementById('txtFiscalYearEndDay'));
});
I'm using the latter so I can keep all of the JS code together.
I've got a long running method -- and I want to indicate to the user that an operation is underway. This is NOT and ajax call, so I can't use the common pattern I've used in the past to display, say a spinner, before the ajax event, then hiding it on success for example.
In my case -- I'm not making an ajax call, I'm instead doing some very heavy DOM manipulation.
I had a test example on jsFiddle.net -- would love to learn how to capture the event. At the moment, my "wait-message" div updates at the same exact time when my operation completes which is much too late :(
Complete sample code is here: http://jsfiddle.net/rsturim/97hrs/6/
Javascript (jQuery)
$(document).ready(function() {
$("#link-action").click(function(e) {
$("#wait-message").text("starting ...");
var count = longRunningMethod(1000000000);
$("#result").text(count);
$("#wait-message").text("completed.");
});
var longRunningMethod = function(countUpTo) {
var i = 0;
while (i <= countUpTo) {
i++;
}
return i;
};
});
HTML:
<div id="wait-message">
push button please
</div>
<hr />
<button id="link-action">Run Operation</button>
<hr />
<h1>Results:</h1>
<div id="result"> </div>
Here is a solution. I'm not sure if it works in all browsers, you may want to test it out in several, but I think it does:
$(document).ready(function() {
$("#link-action").click(function(e) {
$("#wait-message").text("starting ...");
// Stuff to do after a render
setTimeout(function(){
var count = longRunningMethod(1000000000);
$("#result").text(count);
$("#wait-message").text("completed.");
}, 0);
});
var longRunningMethod = function(countUpTo) {
var i = 0;
while (i <= countUpTo) {
i++;
}
return i;
};
});
Basically, the browser won't render any changes until a script finishes executing. That allows you to do things like:
Hide all divs with a certain class
Show one of those divs
In a row and the browser will never render the div that is being shown as hidden, so you won't get weird flickers or things moving around on the page.
Using setTimeout like I did, the anonymous click handler will finish executing, the browser will re-render, the the anonymous function in the setTimeout will run (immediately after the render since there is no actual delay).
Use setTimeout or setInterval instead of your while loop; a sub-second delay like 15ms should be enough to prevent your window freezing / UI locking.