A blog I read has some annoying commenters. I thought I would try my hand at Greasemonkey to turn them off.
The basic structure of the HTML is simple - a comment looks something like this:
<li>
<cite>user name ...</cite>
comment text
</li>
So with that in mind, I bashed my head against the keyboard for a while until this dropped out:
var killlist = /user1|user2/;
var comments = document.getElementsByTagName('li');
if (comments.length) {
for ( var i = 0; i < comments.length; i ++ ) {
var comment = comments[i];
var cites = comment.getElementsByTagName('cite');
if (cites.length) {
var cite = cites[0];
var title = cite.textContent;
if (killlist.test(title)) {
comment.parentNode.removeChild(comment);
}
}
}
}
window.alert('Done!')
(the window.alert is just so I know if the script runs to completion)
This mostly works. e.g. on one test page, it removed 13 of 16 posts by one of the users. I tried replacing the removeChild line with this:
comment.style.visibility = 'hidden';
That appears to get everything, but at the expense of leaving great empty spaces where the comments would have been.
I'm a complete javascript newbie, so can anyone see anything obvious I'm doing wrong?
The behavior you're seeing is due to comments being a live NodeList. As you remove elements referenced by comments from the DOM, comments is mutated (and its length updated) and your loop goes out the window.
You can loop in reverse order as #Pat suggested, or "unhook" the NodeList from the DOM with a copy operation first:
var comments = Array.slice(document.getElementsByTagName('li'));
Then proceed as before.
You should be able to fix it by reversing the order of your for loop and deleting elements from the end of the comments array first:
for ( var i = comments.length - 1; i >= 0; i-- )
Related
What I'm trying to accomplish is:
have a data input sheet called 'data' (its data is fed by a form)
script moves the information from data to sheet1/sheet2/.../sheetn (according to a string that is to be found in column 3)
script also deletes moved rows
I think the deleteRow command works fine, i suspect the culprit being the detection of the string in the array.
I've already used the search a lot, tried a few codes and I've identified this as the most probable candidate (its by cooper), as it's almost doing what i need it to do.
I tried logging a bit, but unfortunately i dont know too much about coding yet. Currently im learning by trial and error.
If i log for vals[i][2] i only get 1 string, instead of a few from my example input.
When i set only one targetsheet (sh1) and target-term it works. but when i extend it it doesnt work anymore.
{
var ss=SpreadsheetApp.getActive();
var sh0=ss.getSheetByName('Data');
var rg0=sh0.getDataRange();
var sh1=ss.getSheetByName('Applesheet');
var sh2=ss.getSheetByName('Banana');
var sh3=ss.getSheetByName('Cherry');
var vals=rg0.getValues();
Logger.log(vals)
for(var i=vals.length-1;i>0;i--)
{
if(vals[i][2]=='Apple')
Logger.log("PV Abfrage -", vals[i][2])
{
sh1.appendRow(vals[i]);
sh0.deleteRow(i+1);
}
if(vals[i][2]=='Banana') //also tried with else if here
{
sh2.appendRow(vals[i]);
sh0.deleteRow(i+1);
}
if(vals[i][2]=='Cherry')
{
sh3.appendRow(vals[i]);
sh0.deleteRow(i+1);
}
}
}
My code moves rows that dont contain any of the terms.
It's also supposed to only move rows that contain this term, but its doing so super unrealiably.
I think all rows get appended to Applesheet, rows that contain banana are moved to banana but the ones with cherry wont.
Im definitely not experienced enough to judge, but this code seems a bit unreliable, because even my test version with just one if fails to perform the way i want it to.
Issue:
Your first if statement is forced to return true by the Logger.log() you've included between if and {. As soon as you remove it, your code functions exactly as you're expecting.
Example:
If we run the following script:
var check = ['Apple', 'Pear', 'Fruit'];
for (i = 0; i < check.length; i++) {
if (check[i] === 'Apple') {
console.log('Found!');
}
}
We're looping through an array, and logging "Found!" for every time the item in the array is found. This is the same way your script works. It works as expected, "Apple" is only found once in the array, so the log looks like this:
Found!
As soon as we put a log between the if and the {, like so:
var check = ['Apple', 'Pear', 'Fruit'];
for (i = 0; i < check.length; i++) {
if (check[i] === 'Apple')
console.log("Oops!")
{
console.log('Found!');
}
}
We get the following:
Oops!
Found!
Found!
Found!
Summary:
Make sure to only include your conditions for the if statement between your if and {, adding anything else can return false positives like you've experienced today.
Reference:
JavaScript if Statements
I've been trying to learn javascript by refactoring some Jquery examples in a book into javascript. In the following code I add a click listener to a tab and make it change to active when the user clicks on the tab.
var tabs = document.querySelectorAll(".tabs a span");
var content = document.querySelectorAll("main .content li");
for (var tabNumber = 0; tabNumber <= 2; tabNumber++) {
tabs[tabNumber].addEventListener("click", function (event) {
for (var i = 0; i < tabs.length; i++) {
tabs[i].classList.remove("active");
}
tabs[tabNumber].classList.add("active");
for (var i = 0; i < content.length; i++) {
content[i].innerHTML = "";
}
event.preventDefault();
});
}
This returns an undefined error when I run it. However, I tried replacing tabs[tabNumber].classList.add("active") with this.classList.add("active") and it worked.
Why doesn't the previous code work? As far as I can see they are referring to the same thing, and tabs[tabNumber] should work since at that point in the code it is tabs[0].
If use this, I think it's better and a more polished solution. If you still want to use tabNumber, it's probably evaluating to 3 in every click callback, because it's the number after the last iteration, and you don't have a tabs[3] position.
So, you just have to make a closure of the tabNumber variable.
I guess other answers told you why tabs[tabNumber] does not work (because it comes from the score of the for loop and so, is always equal to the greater value of tabNumber).
That's why I would recommend using a .forEach loop. Careful though because it doesn't work on arrays of DOM nodes produced by document.querySelectorAll(), but you can use:
// ES6
Array.from(document.querySelectorAll('...'))
// ES5
[].slice.call(document.querySelectorAll('...'))
Anyway, I made a simplified working demo of your code.
Note that I save the currently active tab in a variable, to prevent another for loop. You could also do:
document.querySelector('.active').classList.remove('active')
But I like to reduce the amount of DOM reading.
Good luck for your apprentissage, re-writing some jQuery code into Vanilla JS seems like a good method, and you might acquire a far better comprehension of JavaScript.
r = r.replace(/<TR><TD><\/TD><\/TR>/gi, rider_html);
...does not work in IE but works in all other browsers.
Any ideas or alternatives?
I've come to the conclusion that the variable r must not have the value in it you expect because the regex replacement should work fine if there is actually a match. You can see in this jsFiddle that the replace works fine if "r" actually has a match in it.
This is the code from fiddle and it shows the proper replacement in IE.
var r = "aa<TR><TD></TD></TR>bb";
var rider_html = " foo ";
r = r.replace(/<TR><TD><\/TD><\/TR>/gi, rider_html);
alert(r);
So, we can't really go further to diagnose without knowing what the value of "r" is and where it came from or knowing something more specific about the version of IE that you're running in (in which case you can just try the fiddle in that version yourself).
If r came from the HTML of the document, then string matching on it is a bad thing because IE does not keep the original HTML around. Instead it reconstitutes it when needed from the parsed page and it puts some things in different order (like attributes), different or no quotes around attributes, different capitalization, different spacing, etc...
You could do something like this:
var rows = document.getElementsByTagName('tr');
for (var i = 0; i < rows.length; i++) {
var children = rows[i].children;
if (children.length === 1 && children[0].nodeName.toLowerCase() === 'td') {
children[0].innerHTML = someHTMLdata
}
}
Note that this sets the value of the table cell, rather than replacing the whole row. If you want to do something other than this, you'll have to use DOM methods rather than innerHTML and specify exactly what you actually want.
While using Firefox, I keep getting the error described in the subject line for this block of code:
for(var i = 0; i < tables.length; i++)
{
var j = rows.length - 1;
while(j--)
{
if(hideDP && tables[i].innerHTML.indexOf(">D<") != -1)
{
if(!platTag && !soulSilverTag && pearlTag)
{
tables[i].deleteRow(j);//ERROR IS ON THIS LINE
}
}
}//end while loop (rows)
}//end for loop (tables)
I suspect that this error is because I'm somewhat new to making reverse loops, but I specifically made a reverse loop in this instance because it made deleting rows from a table easier. Note also that j is something like 24 and i is 0, so they're non-negative. Could someone shed some light on this for me?
EDIT : The full code can be found here.
Strictly working off of the currently posted code, here are the issues I see:
The posted code looks incomplete. Where is rows being initialized? This could cause the stated error.
Given while(j--); The var j = rows.length - 1; line is incorrect. That is, unless you know that the last row will never need deleting. But if that is the case, then comment the code to make it clear.
For example, if there were 4 rows, the current code initializes j to 3, but because of the location of the -- operator, the inside of the loop sees: 2, 1, 0. For the code as shown, use var j = rows.length; or add a comment to show that the logic is deliberate.
The 2 if() statements do not depend on j at all! (At least as the code is posted here.) If this is true, then move the conditionals outside of the j loop.
Consider posting the full, unedited, code. Or linking to it on a site like Pastebin.
Update for full script, now that it's been linked to:
Scanning the complete code, it looks like tables[i].deleteRow(j); can be called multiple times for the same row.
The easy solution, that should be done anyway, is to add a continue statement after each row delete.
For extra credit, reanalyze and simplify the flag and if logic too. :)
Update for target page, now that it's been linked to:
Examining the target page, the tables being looped by this script contain nested tables.
That throws off the row count in this line:
var rows = tables[i].getElementsByTagName("tr");
Sometimes making it seem like table[i] has more rows than it really directly owns.
Solution, use the built in rows array; so the line becomes:
var rows = tables[i].rows;
~~~~
While examining the script relative to the target page, a few other issues seemed apparent:
It's not best to loop through all tables. Target just the ones you need. So this:
tables = document.getElementsByTagName("table");
Should be changed to:
var tables = document.querySelectorAll ("div.KonaBody > table.roundy");
...which will select just the 4 payload tables, and not their subtables or the other tables scattered about.
By fine-tuning the initial table selection, the following, problamatic, test is not needed:
if(tables[i].getAttribute("style").indexOf("border: 3px solid") != -1)
Missing var in front of the majorSections initialization.
That error will also be generated if j is >= to the amount of rows in the table, but I'm not seeing the exact problem.
how can i search an html page for a word fast?
and how can i get the html tag that the word is in? (so i can work with the entire tag)
To find the element that word exists in, you'd have to traverse the entire tree looking in just the text nodes, applying the same test as above. Once you find the word in a text node, return the parent of that node.
var word = "foo",
queue = [document.body],
curr
;
while (curr = queue.pop()) {
if (!curr.textContent.match(word)) continue;
for (var i = 0; i < curr.childNodes.length; ++i) {
switch (curr.childNodes[i].nodeType) {
case Node.TEXT_NODE : // 3
if (curr.childNodes[i].textContent.match(word)) {
console.log("Found!");
console.log(curr);
// you might want to end your search here.
}
break;
case Node.ELEMENT_NODE : // 1
queue.push(curr.childNodes[i]);
break;
}
}
}
this works in Firefox, no promises for IE.
What it does is start with the body element and check to see if the word exists inside that element. If it doesn't, then that's it, and the search stops there. If it is in the body element, then it loops through all the immediate children of the body. If it finds a text node, then see if the word is in that text node. If it finds an element, then push that into the queue. Keep on going until you've either found the word or there's no more elements to search.
You can iterate through DOM elements, looking for a substring within them. Neither fast nor elegant, but for small HTML might work well enough.
I'd try something recursive, like: (code not tested)
findText(node, text) {
if(node.childNodes.length==0) {//leaf node
if(node.textContent.indexOf(text)== -1) return [];
return [node];
}
var matchingNodes = new Array();
for(child in node.childNodes) {
matchingNodes.concat(findText(child, text));
}
return matchingNodes;
}
You can try using XPath, it's fast and accurate
http://www.w3schools.com/Xpath/xpath_examples.asp
Also if XPath is a bit more complicated, then you can try any javascript library like jQuery that hides the boilerplate code and makes it easier to express about what you want found.
Also, as from IE8 and the next Firefox 3.5 , there is also Selectors API implemented. All you need to do is use CSS to express what to search for.
You can probably read the body of the document tree and perform simple string tests on it fast enough without having to go far beyond that - it depends a bit on the HTML you are working with, though - how much control do you have over the pages? If you are working within a site you control, you can probably focus your search on the parts of the page likely to be different page from page, if you are working with other people's pages you've got a tougher job on your hands simply because you don't necessarily know what content you need to test against.
Again, if you are going to search the same page multiple times and your data set is large it may be worth creating some kind of index in memory, whereas if you are only going to search for a few words or use smaller documents its probably not worth the time and complexity to build that.
Probably the best thing to do is to get some sample documents that you feel will be representative and just do a whole lot of prototyping based around the approaches people have offered here.
form.addEventListener("submit", (e) => {
e.preventDefault();
var keyword = document.getElementById("search_input");
let words = keyword.value;
var word = words,
queue = [document.body],
curr;
while (curr = queue.pop()) {
if (!curr.textContent.toUpperCase().match(word.toUpperCase())) continue;
for (var i = 0; i < curr.childNodes.length; ++i) {
switch (curr.childNodes[i].nodeType) {
case Node.TEXT_NODE: // 3
if (curr.childNodes[i].textContent.toUpperCase().match(word.toUpperCase())) {
console.log("Found!");
console.log(curr);
curr.scrollIntoView();
}
break;
case Node.ELEMENT_NODE: // 1
queue.push(curr.childNodes[i]);
break;
}
}
}
});