Why is this userscript only doing the first if? - javascript

I am working on a User script but it seems to work just for the first comparison:
(function() {
'use strict';
var noError = document.getElementsByClassName("noMistakeButton");
var next = document.getElementsByClassName("nextButton");
var wait = document.getElementsByClassName("understoodButton");
var okko = document.getElementsByClassName("buttonKo");
var exitOkko = document.getElementsByClassName("exitButton");
while(1)
{
if( noError !== null)
{
noError.click();
}
if( next !== null)
{
exit.click();
}
if( wait !== null)
{
wait.click();
}
sleep(2);
if( okko !== null)
{
exit.click();
}
if( exitOkko !== null)
{
exitOkko.click();
}
} })();
My goal is the run this script freely while AFK.
There are, as you can see, many buttons on the web page and each button cant be :visible or :hidden. My goal is just to click on them.
Here is the target page (static URL).
Some buttons only have class and no ID. Others have them both.
The console reports:
ERROR: Execution of script 'AutoVoltair' failed! noError.click is not
a function

It is unclear what you hope to accomplish. If you are trying to step through a sequence of controls, use the approach illustrated in Choosing and activating the right controls on an AJAX-driven site.
The kind of code shown in the question would just play "Whac-A-Mole" with whatever button "popped up" next. (And only if the preceding buttons had been deleted.)
Anyway, to answer the question: "why this code is only doing the first if?".
It's because userscripts (and javascript) stop running at the first critical error (with a few exceptions). Additionally:
noError.click is not a function because noError is a collection of elements, not a button.
All the getElementsByClassName calls are only done once. If it's going to continually loop, you need to recheck inside the loop.
There is no such thing as sleep().
while (1) is a very bad idea and can freeze your browser, or even help crash your PC. Use setInterval for polling the page.
Here's the "Whac-A-Mole" code with those errors corrected:
setInterval ( () => {
var noError = document.getElementsByClassName ("noMistakeButton");
var next = document.getElementsByClassName ("nextButton");
var wait = document.getElementsByClassName ("understoodButton");
var okko = document.getElementsByClassName ("buttonKo");
var exitOkko = document.getElementsByClassName ("exitButton");
if (noError.length) {
noError[0].click ();
}
if (next.length) {
next[0].click ();
}
if (wait.length) {
wait[0].click ();
}
if (okko.length) {
okko[0].click ();
}
if (exitOkko.length) {
exitOkko[0].click ();
}
},
222 // Almost 5 times per second, plenty fast enough
);
If you want to sequence clicks, use chained waitForKeyElements() calls as shown in this answer.

Related

setInterval not stopping?

I thought an interval just delayed the function, but as it turns out it actually loops.
When I include some function that stops the interval after the deletor function ends it doesn't trigger that and I still get Test logged to the console.
document.addEventListener("DOMContentLoaded", function(event) {
let fullURL = window.location.href;
//let fullURL2 = window.location.host + window.location.pathname;
if (fullURL === "https://net.adjara.com/" ||
fullURL === "https://net.adjara.com/Home") {
var timer = setInterval(deletor, 5);
function deletor() {
timer;
var slider = document.querySelector("#slider-con");
var bannerTop = document.querySelector("#MainContent > div:nth-child(2)")
var bannerMiddle = document.querySelector("#MainContent > iframe");
var bannerRandom = document.querySelector("#MainContent > div:nth-child(3)");
if (slider) {
slider.parentNode.removeChild(slider);
}
if (bannerTop) {
bannerTop.parentNode.removeChild(bannerTop);
}
if (bannerMiddle) {
bannerMiddle.parentNode.removeChild(bannerMiddle);
}
if (bannerRandom) {
bannerRandom.parentNode.removeChild(bannerRandom);
}
function stopInterval() {
clearInterval(timer);
}
console.log("Test");
/*if ()
clearInterval(timer);*/
};
} else {
return false;
}
});
What you're looking for is setTimeout. It runs only once.
setTimeout(deletor, 5);
Also, you don't need to write timer variable inside of your closure like you would in Python. Javascript captures everything that's inside of lexical scope.
The code you provided works ¯\_(ツ)_/¯
Maybe the problem is coming from what triggers stopInterval()
But as mentioned in comments / other answers, you might be better off with another method
I wouldn't recommend using setTimeout in your case, because it looks like you are simply waiting for some DOM elements to be loaded. The problem with the timeout is that you can't know for sure how fast is the computer that will run your code. Maybe a bad quality phone with an outdated software that will need way more time to run your code than when you test on your personal computer, and that will not have the elements loaded by the time your function will be executed.
jQuery
For this reason, and since you tagged your question with jQuery I think you could use $(elementYouWaitForTheDOM2beLoaded).ready(function2execute) for each element you are watching instead of a having a loop that waits for the elements to be loaded (documentation for "ready" function)
Vanilla JS
And if you want to do it in pure JS it would be document.querySelector(elementYouWaitForTheDOM2beLoaded).on('load', function2execute))

Code inside setTimeout is not firing in expected order

newby newb here...I don't think this specific issue has been addressed before on this site, I've searched and searched but can't find anything that works. I want to display a loading image. I am hiding it before my setTimeout and then showing right at the end of the setTimeout. Everything I've read said this should work but it doesn't. My image appears and then disappears right away before my script is finished running. Any ideas? Thanks!
function createHTML(data, listName) {
var arr = data.d.results;
$(".save").on("click",function(event){
// HERE IS WHERE I SHOW MY LOADING IMAGE INITIALLY
$('.logoImg').show();
// SET TIMEOUT FUNCTION
setTimeout(function() {
$("input.title").each(function(){
var title = $(this).val() ? $(this).val() : null;
var currentUserRequest = GetCurrentUser();
(function (userData) {
updateListItem(_spPageContextInfo.webAbsoluteUrl,'MyList',id,itemProperties,printInfo,logError);
function printInfo() {
console.log('Item has been UPDATED!');
}
function logError(error){
console.log(JSON.stringify(error));
}
});
});
// THIS IS FIRING BEFORE THE ABOVE EACH STATEMENT
$('.logoImg').hide();
}, 0);
});
}
The callback function passed to each will be called for each element that matches the selector.
It looks like you want to call hide() only after the callback has been called for all the elements.
To accomplish this, you need to call it after the last callback.
Something like this should works:
var titles = $("input.title");
var length = title.length;
titles.each(function(index, element) {
// Do what you need
if(index == (length-1)) {
// We are now on the last one
$('.logoImg').hide();
}
});

My jplayerplaylist remove function isn't working

I'm trying to make a mobile version of my site, and the jplayer functionality closely resembles that of my main site. The playlist is updated depending on which page you are on, and all the songs except the one you are currently listening to are deleted from the playlist, then the page dynamically adds songs from the page you are viewing.
It works fine on my main page, but my mobile version (which is almost the same code), does not work on.
I set jplayer_playlist.option("removeTime", 0); just like the jplayer documentation suggests, but it doesn't work. Here is a bit of my code so you can see exactly what I'm doing.
function reload()
{
var current = jplayer_playlist.current;
for(var i = 0; i < current; i++)
{
deleteSong(0);
}
var length = theTitles.length;
for(var i = 0; i < (length - 1); i++)
{
deleteSong(1);
}
}
function deleteSong(index)
{
if(!jplayer_playlist.remove(index))
{
console.log("Could not delete it.");
}
}
The first delete does not show the error message, but the second (and every delete after) does. It seems as though it is not recognizing that I set the removeTime to 0, even though I did (and before any delete calls were made). Is there anything else that the jplayer.remove function depends on when you are trying to delete something from it besides removeTime?
I had the same problem and found an answer in jPlaylist docs: http://jplayer.org/latest/demo-02-jPlayerPlaylist/
"Because the second command will only work if the remove animation time, removeTime, is zero."
In my case, I coded the following feature to erase all the songs up to the current one:
When creating the jPlaylist:
var playlistOptions = {
playlistOptions: {
enableRemoveControls: true,
autoPlay: false,
removeTime: 0
},
...
};
playlist = new jPlayerPlaylist(.......);
And the function that erases the songs:
function deleteUpToCurrent(e) {
while(playlist.current != 0) {
playlist.remove(0);
}
return false;
}
Hope it helps!
Cheers
On line 355 of jquery.playlist.js in the jplayerPlaylist.remove function, there is a call to JQuery's remove function:
$(this).remove();
If you look in the source of JQuery, this is delegated to the DOM method
elem.parentNode.removeChild( elem );
In modern browsers, this DOM operation is asynchronous, i.e. it returns immediately but actually removing the DOM element takes time. Therefore the jplayerPlaylist.remove() method returns before the node has actually been removed. Unfortunately, there is no callback. So the only workaround is a setTimeout.
var DELAY = 10;
function reload()
{
var current = jplayer_playlist.current;
var length = jplayer_playlist.playlist.length;
if (current > 0)
deleteSong(0);
else if (length > 1)
deleteSong(1);
if (length > 1)
window.setTimeout(reload, DELAY);
}
You may have to adjust DELAY.

Abandon pending/running keyup functions if another key is pressed

I have a search box that hides all lines in a list that don't contain the entered text.
This worked great until the list became 10,000 lines long. One keystroke is fine but if the user types a several letter word, the function is iterated for each keypress.
What I want to do is to abandon any previous execution of the function if a new key is pressed.
The function is very simple, as follows:
$("#search").keyup(function(e) {
goSearch();
});
function goSearch()
{
var searchString = $("#search").val().toLowerCase();
$(".lplist").each(function(index, element) {
var row = "#row-" + element.id.substr(5);
if ($(element).text().toLowerCase().indexOf(searchString,0) != -1)
$(row).show();
else
$(row).hide();
});
}
Thanks
You can't directly. Javascript is not multi-threaded so your function will run and block any key-presses until it is done.
The way this is made tolerable from a user-experience point of view is to not trigger a function immediately on a key event, but to wait a short period of time and then fire the event.
While the user is typing, the timeout function will continually be set and reset and so the gosearch function won't be called, and so the user won't have their typing interrupted.
When the user pauses typing, the timeout will countdown to zero and call the search function, which will run and block typing until it completes. But that's okay (so long as it completes within a second or so) as the user is probably not currently trying to type.
You can also do what you actually asked by breaking up your gosearch function into chunks, where each call to the function: * Reads a counter of the number of lines processed so far, and then processes another 500 lines and increments the counter. * Calls another gosearch using setTimeout with a value of zero for the time. This yields events to other 'threads', and allows for fast changing of search terms.
var goSearchTimeout = null;
var linesSearched = 0;
function keySearch(e){
if(goSearchTimeout != null){
clearTimeout(goSearchTimeout);
linesSearched = 0;
}
goSearchTimeout = setTimeout(goSearch, 500);
}
$("#search").keyup(keySearch);
function highLight(index, element) {
if(index >= linesSearched){
var row = "#row-" + element.id.substr(5);
if ($(element).text().toLowerCase().indexOf(searchString,0) != -1){
$(row).show();
else{
$(row).hide();
}
if(index > linesSearched + 500){
linesSearched = index;
goSearchTimeout = setTimeout(goSearch);
return;
}
}
function goSearch(){
goSearchTimeout = null;
var searchString = $("#search").val().toLowerCase();
$(".lplist").each(highLight);
}
If you're going to use timeout callbacks like this, I'd strongly recommend wrapping your code up into jQuery widgets, so that you can use variables on the object to store the variables goSearchTimeout etc rather than having them float around as global variables.
Introduce a counter var keypressCount that is being increased by your keypress event handler. at the start of goSearch() write its value into a buffer. Then at each run of your $(".lplist").each() you ask if the current keypressCount is the same as the buffered one; if not, you return. I would suggest you use a for() though since it is easier to break; than $.each().
Update:
You will have to make sure that there is time for new keypress events to be fired/received, so wrap the anonymous function of your $.each() inside a timeout.
Reference: http://www.garrickcheung.com/javascript/what-i-learned-about-multi-threading-in-javascript/
You can use a global variable to save search string and stop execution when search string changes.
IMPORTANT: You must set a timeout in each iteration so that function execution is paused and global variables are updated, as JavaScript is single-threaded.
Your code would look like this:
var searchString;
$("#search").keyup(function(e) {
// Update search string
searchString = $("#search").val().toLowerCase();
// Get items to be searched
var items = $(".lplist");
// Start searching!
goSearch(items, searchString, 0);
});
function goSearch(items, filter, iterator)
{
// Exit if search changed
if (searchString != filter) {
return;
}
// Exit if there are no items left
if (iterator >= items.length) {
return;
}
// Main logic goes here
var element = items[iterator];
var row = "#row-" + element.id.substr(5);
if ($(element).text().toLowerCase().indexOf(filter, 0) != -1)
$(row).show();
else
$(row).hide();
// Schedule next iteration in 5 ms (tune time for better performance)
setTimeout(function() {
goSearch(items, filter, iterator + 1);
}, 5);
}

JavaScript - forcing line to execute within a loop in Chrome?

I have a while loop:
x = true;
while (x == true) {
document.images['id'].src = arr[i];
i = i+1;
x = confirm('do you want to see more?')
}
This shows me each image and then asks if I want to see more on firefox and ie, but in chrome and safari, it only displays the image after I leave the loop. I know this is efficient, but I'm wondering if there's a way to force execution of the line within the loop as I go along?
Thanks for input!
You can add a sequence of setTimeout instead of a loop to force the javascript user thread to stop and thus let the browser refresh the drawing.
var i = 0; // define outside showNextImage to be properly captured by it.
var showNextImage = function() {
document.images['id'].src = arr[i];
i = i+1;
x = confirm('do you want to see more?');
if (true) setTimeout(showNextImage, 10);
};
Two answers:
Don't use confirm
If you really want to use confirm, yield to the browser after updating the image but before the confirm
1. Don't use confirm
The best way is to not use confirm at all; it's antiquated and as you've found it behaves slightly differently on different browsers in terms of whether changes to the page are shown.
Instead, I'd use any of the 350,124 "dialog" libraries that are out there (jQuery UI has a nice one, but again, there are a lot of them), which work asynchronously and so you definitely see the page changes. Your loop would become an asynchronous function, but those aren't all that tricky once you're used to them and the benefits are enormous in terms of the user experience.
function chooseImage(arr, completionCallback) {
var i = 0, imgElement = document.images['id'];
ask();
function ask() {
imgElement.src = arr[i];
showDialog(gotAnswer); // the nature of "show dialog" will depend on which one you use
}
function gotAnswer() {
if (userSaidYes) { // Again, depends on the library you're using
completionCallback(i); // Tell the calling code which one they picked
}
else {
// Is there another?
++i;
if (i >= arr.length) {
// No, tell the user
/* left as exercise */
// Tell calling code none was chosen
completionCallback(-1); // Using -1 as a flag for none
}
else {
// Yes, ask about it
ask();
}
}
}
}
2. Use confirm but yield
The issue is that confirm brings things to a screeching halt while the browser asks the user a question. Changes you've made to the page may not show up while the confirm window is active (as you've seen).
If you really want to use confirm, you can still do that, just yield back to the browser briefly first so that it has time to show the page changes. Note, though, that this still may not be a guarantee, if the image takes a long time to download.
function chooseImage(arr, completionCallback) {
var i = 0, imgElement = document.images['id'];
showAndHandOff();
function showAndHandOff() {
imgElement.src = arr[i];
setTimeout(ask, 0);
}
function ask() {
if (confirm('do you want to see more?')) {
++i;
if (i >= arr.length) {
alert("Sorry, there aren't any more.");
completionCallback(-1);
}
else {
showAndHandOff();
}
}
else {
completionCallback(i);
}
}
}
For example:
var x = true,
i = 0,
fn = function() {
document.images['id'].src = arr[i];
x = confirm('do you want to see more?');
if ( x ) {
i = i+1;
setTimeout(fn, 0)
}
};
fn();

Categories