Need help optimizing script that hides table rows - javascript

I made this Greasemonkey script:
var maxpi = 250;
var p1 = "/html/body/div/div[2]/div/div[2]/table[2]/tbody/tr[1]/td[11]";
var p2 = "/html/body/div/div[2]/div/div[2]/table[2]/tbody/tr[2]/td[11]";
..
var p25 = "/html/body/div/div[2]/div/div[2]/table[2]/tbody/tr[25]/td[11]";
var r1 = "/html/body/div/div[2]/div/div[2]/table[2]/tbody/tr[1]";
var r2 = "/html/body/div/div[2]/div/div[2]/table[2]/tbody/tr[2]";
..
var r25 = "/html/body/div/div[2]/div/div[2]/table[2]/tbody/tr[25]";
var xpathPI1 = document.evaluate(p1, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
..
var xpathPI25 = document.evaluate(p25, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
var xpathrow1 = document.evaluate(r1, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
..
var xpathrow25 = document.evaluate(r25, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
if (xpathPI1.singleNodeValue.textContent >maxpi ){
xpathrow1.singleNodeValue.style.display='none';}
..
if (xpathPI25.singleNodeValue.textContent >maxpi ){
xpathrow25.singleNodeValue.style.display='none';}
Basically, it checks a table row's 11th field and if its contents > than 250 it hides the row.
With my limited javascript knowledge took quite some time get this working.
The problem is that I have to rewrite every single line if I want to check-hide another row.
I want to make it more usable so I can use it on similar tables without rewriting the whole thing.
Maybe I need to use a different XPath type or use some kind of changing variable?

Of course, there are more ways to improve your script.
Firstly, you need to thoroughly think through WHAT exactly you want to look for. Is is every row and column? Is it rows/columns with some text, class, any other attribute? You can even select only those nodes that have their text value greater than your maxpi!
Read something about XPath, the possibly best resource is the official one.
Some random thoughts on what could be useful regarding XPath:
//table//tr[5]/td[2] ... the double slash is the deal here
//table//tr/td[number(text()) > 250] ... the number() and text() functions
When talking about JavaScript, that would be a little tougher, because there are so many things you could use!
Just for starters - you can create dynamically changing xpath expressions by String concatenation and For loop, like this:
for (var i = 1; i <= maxNumberOfRows; i++) {
var p1 = "//table/tbody/tr[" + i + "]";
// more work goes here...
}
Also, you could use arrays to store multiple nodes returned by your XPath expressions and work on them all with just a single command.
For more JavaScript, I would recommend the first chapters of some JavaScript tutorial, that will boost your productivity by a lot.

Use a loop and functions. Here's one way:
hideRowsWithLargeCellValue (
"/html/body/div/div[2]/div/div[2]/table[2]/tbody/tr[",
25,
"]/td[11]",
250
);
function hideRowsWithLargeCellValue (xpathPre, maxRows, xpathPost, maxpi) {
for (var J = maxRows; J >= 1; --J) {
var srchRez = document.evaluate (
xpathPre + J + xpathPost,
document,
null,
XPathResult.FIRST_ORDERED_NODE_TYPE,
null
);
if (srchRez.singleNodeValue && srchRez.singleNodeValue.textContent > maxpi) {
var rowToHide = srchRez.singleNodeValue.parentNode;
rowToHide.style.display='none';
}
}
}
Then read "Dont Repeat Yourself" (sic).

Related

Alternatives to XPath $x() in Google Chrome console [duplicate]

I'm using the document.evaluate() JavaScript method to get an element pointed to by an XPath expression:
var element = document.evaluate(
path,
document,
null,
XPathResult.FIRST_ORDERED_NODE_TYPE,
null
).singleNodeValue;
But how do I get a list of elements in case the XPath expression points to more than one element on the page?
I tried the following code, but it is not working:
var element = document.evaluate(
path,
document,
null,
XPathResult.ORDERED_NODE_ITERATOR_TYPE,
null
);
I found the following solution in the book I am currently reading. It says that the code is from the Prototype library.
function getElementsByXPath(xpath, parent)
{
let results = [];
let query = document.evaluate(xpath, parent || document,
null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
for (let i = 0, length = query.snapshotLength; i < length; ++i) {
results.push(query.snapshotItem(i));
}
return results;
}
Use it like this:
let items = getElementsByXPath("//*"); // return all elements on the page
From the documentation
var iterator = document.evaluate('//phoneNumber', documentNode, null, XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null );
try {
var thisNode = iterator.iterateNext();
while (thisNode) {
alert( thisNode.textContent );
thisNode = iterator.iterateNext();
}
}
catch (e) {
dump( 'Error: Document tree modified during iteration ' + e );
}
Try this:
function getListOfElementsByXPath(xpath) {
var result = document.evaluate(xpath, document, null, XPathResult.ANY_TYPE, null);
return result;
}
Then call it:
var results = getListOfElementsByXPath("//YOUR_XPATH");
while (node = results.iterateNext()) {
console.log(node);
}
In Chrome, there is a simpler solution, described in this document, at least in the console:
$x(path)
It does the same as the getElementsByXPath function above, but much easier for debugging.
I was working hard with the same problem some weeks ago. I found out, that the result already represents a list of elements (if any) and one can iterate trough it. I needed to build a jQuery plugin for realize a search of partial or full text strings, which means the inner text of any DOM element like LI or H2. I got the initial understanding on his page : Document.evaluate() | MDN
After some hours I got the plugin running: Search for the word "architecture" only in "p" elements, find partial matching strings ("true" for <p>todays architecture in Europe</p>) instead of matches of entire text (<h2>architecture</h2>).
var found = $('div#pagecontent').findtext('architecture','p',true);
Found results are regular jQuery objects, which can be used as usual.
found.css({ backgroundColor: 'tomato'});
The example of usage above may be altered like this for search trough entire document and all node types like this (partial results)
var found = $('body').findtext('architecture','',true);
or only exact matches
var found = $('div#pagecontent').findtext('architecture');
The plugin itself shows a variable "es" which is the plural of a single "e" for "element". And you can see, how the results are iterated, and collected into a bunch of objects with f = f.add($(e)) (where "f" stands for "found"). The beginning of the function deals with different conditions, like full or partial search ("c" for condition) and the document range for the search ("d").
It may be optimized whereever needed, may not represent the maximum of possibilities, but it represents my best knowledge at the moment, is running without errors and it may answer your question, hopefully. And here is it:
(function($) {
$.fn.findtext = function(s,t,p) {
var c, d;
if (!this[0]) d = document.body;
else d = this[0];
if (!t || typeof t !== 'string' || t == '') t = '*';
if (p === true) c = './/'+t+'[contains(text(), "'+s+'")]';
else c = './/'+t+'[. = "'+s+'"]';
var es = document.evaluate(c, d, null, XPathResult.ANY_TYPE, null);
var e = es.iterateNext();
var f = false;
while (e) {
if (!f) f = $(e);
else f = f.add($(e));
e = es.iterateNext();
}
return f || $();
};
})(jQuery);

search within an array in javascript

I've been trying for a while now to search within an array, I've looked at all the other questions that even somewhat resemble mine and nothing works, so I'm asking for any help you can give now..
I have an array with a more complex insides than a simple string array
var elementDefns = [
{"element":"water", "combos": {"air":"steam", "earth":"sand"} },
{"element":"fire", "combos": {"earth":"lava", "air":"energy"} },
{"element":"air", "combos": {"water":"steam", "earth":"dust"} },
{"element":"earth", "combos": {"water":"swamp", "fire":"lava"} },
];
Two elements are picked (by the users) which are combined to create new elements. I'd like to search through the elements for any combos that can be made. Ideally, I'd want to use Array.prototype.find, although I can't figure out how to use polyfills correctly and i'm unsure if i'm writing it correctly, so it continues to not work
var elementOne = $("#board img:first-child").attr('id');
var elementTwo = $("#board img:last-child").attr('id');
function findElement(element) {
return elementDefns.element === elementOne;
}
board is the id div where the element cards go to once clicked. I also tried a loop
for (var i=0, tot=elementDefns.length; i < tot; i++) {
var indexHelp = elementDefns[i].element;
var find = indexHelp.search(elementOne);
console.log(find);
}
I'm trying to post a question that's not too long, but I'm sure there's lots more about my code i need to adjust in order to do this. I guess I'm just asking if there's something obvious you think i could work on. I've looked at most of the answers on this site to similar problems but its all just going horribly wrong so any other support would be greatly appreciated..
I have an array with a more complex insides than a simple string array
Yes, but why? Get rid of the extra layers and this is trivial
var e1 = "water";
var e2 = "air";
var elementDefns = {
"water": {"combos": {"air":"steam", "earth":"sand"} },
"fire": {"combos": {"earth":"lava", "air":"energy"} },
"air": {"combos": {"water":"steam", "earth":"dust"} },
"earth": {"combos": {"water":"swamp", "fire":"lava"} },
};
elementDefns[e1].combos[e2] = > "steam"
If you want to keep your data-structure, you can filter through it like this:
var matches = elementDefns
.filter(e => e.element == first && e.combos[second] !== null)
.map(e => e.combos[second]);
The first row filters out all matches, and the secon maps it over to the actual match-string (element name). The find() you speak of just returns the first value that matches, and i guess you want all, so that would be the filter() method.

getElementById(...) returns null unexpectedly

EDITED: I pretty much checked dozens of articles here on this issue, but I simply can't find the problem in my code: The line (commented below) returns null, although I expect it to return the particular element I created and added above.
element2 = document.createElement("button");
element2.innerHTML = "-";
element2.text = rowCounter;
element2.style.backgroundColor = 'white';
element2.id = "kuerzer"+rowCounter; //rowCounter is an iterator variable
ell2.appendChild(element2);
console.log(document.getElementById(element2.id)); //returns null
However, I am now trying to find this element in another function like this:
function structureGantData(){
var gantData = [[]];
for(i=0; i<document.getElementById("myTable").rows.length; i++){
console.log("schleife");
gantData[i][0] = document.getElementById("kuerzer",(i+1)).text; //ISSUE
Could anyone help me? :-)
Thanks alot!
Fabian
You have not added the created element to the DOM, so it cannot be found there.
You may use say Node.appendChild (or any other similar) method to append it somewhere first.
I believe you meant to call document.getElementById("kuerzer" + (i + 1)) rather than document.getElementById("kuerzer", i + 1) in structureGantData.

How do you find the (string) length of a starting tag or ending tag?

I'm trying to write a jQuery or pure Javascript function (preferring the more readable solution) that can count the length of a starting tag or ending tag in an HTML document.
For example,
<p>Hello.</p>
would return 3 and 4 for the starting and ending tag lengths. Adding attributes,
<span class="red">Warning!</span>
would return 18 and 7 for the starting and ending tag lengths. Finally,
<img src="foobar.png"/>
would return 23 and 0 (or -1) for the starting and ending tag lengths.
I'm looking for a canonical, guaranteed-to-work-according-to-spec solution, so I'm trying to use DOM methods rather than manual text manipulations. For example, I would like the solution to work even for weird cases like
<p>spaces infiltrating the ending tag</ p >
and
<img alt="unended singleton tags" src="foobar.png">
and such. That is, my hope is that as long as we use proper DOM methods, we should be able to find the number of characters between < and > no matter how weird things get, even
<div data-tag="<div>">HTML-like strings within attributes</div>
I have looked at the jQuery API (especially the Manipulation section, including DOM Insertion and General Attributes subsections), but I don't see anything that would help.
Currently the best idea I have, given an element node is
lengthOfEndTag = node.tagName.length + 3;
lengthOfStartTag = node.outerHTML.length
- node.innerHTML.length
- lengthOfEndTag;
but of course I don't want to make such an assumption for the end tag.
(Finally, I'm familiar with regular expressions—but trying to avoid them if at all possible.)
EDIT
#Pointy and #squint helped me understand that it's not possible to see </ p >, for example, because the HTML is discarded once the DOM is created. That's fine. The objective, adjusted, is to find the length of the start and end tags as would be rendered in outerHTML.
An alternate way to do this could be to use XMLSerializer's serializeToString on a clone copy of the node (with id set) to avoid having to parse innerHTML, then split over "><"
var tags = (function () {
var x = new XMLSerializer(); // scope this so it doesn't need to be remade
return function tags(elm) {
var s, a, id, n, o = {open: null, close: null}; // spell stuff with var
if (elm.nodeType !== 1) throw new TypeError('Expected HTMLElement');
n = elm.cloneNode(); // clone to get rid of innerHTML
id = elm.getAttribute('id'); // re-apply id for clone
if (id !== null) n.setAttribute('id', id); // if it was set
s = x.serializeToString(n); // serialise
a = s.split('><');
if (a.length > 1) { // has close tag
o.close = '<' + a.pop();
o.open = a.join('><') + '>'; // join "just in case"
}
else o.open = a[0]; // no close tag
return o;
}
}()); // self invoke to init
After running this, you can access .length of open and close properties
tags(document.body); // {open: "<body class="question-page">", close: "</body>"}
What if an attribute's value has >< in it? XMLSerializer escapes this to >< so it won't change the .split.
What about no close tag? close will be null.
This answer helped me understand what #Pointy and #squint were trying to say.
The following solution works for me:
$.fn.lengthOfStartTag = function () {
var node = this[0];
if (!node || node.nodeType != 1) {
$.error("Called $.fn.lengthOfStartTag on non-element node.");
}
if (!$(node).is(":empty")) {
return node.outerHTML.indexOf(node.innerHTML);
}
return node.outerHTML.length;
}
$.fn.lengthOfEndTag = function () {
var node = this[0];
if (!node || node.nodeType != 1) {
$.error("Called $.fn.lengthOfEndTag on non-element node.");
}
if (!$(node).is(":empty")) {
var indexOfInnerHTML = node.outerHTML.indexOf(node.innerHTML);
return node.outerHTML.length - (indexOfInnerHTML + node.innerHTML.length);
}
return -1;
}
Sample jsFiddle here.

Why this javascript is not working in IE?

I am using the following javascript to dynamically add rows in a table:-
var trObj = document.createElement('tr');
trObj.setAttribute('name', 'dynamicTR');
var tdObjEmpty = document.createElement('td');
tdObjEmpty.setAttribute('colspan', '2');
tdObjEmpty.innerHTML = ' '
trObj.appendChild ( tdObjEmpty );
var tdObj = document.createElement('td');
tdObj.setAttribute('colspan', '15');
tdObj.innerHTML = postingDivObj.innerHTML; // <-- copy the innerHTML
trObj.appendChild ( tdObj );
parentObj = approvedDisapprovedTableObj.getElementsByTagName('tbody')[0];
targetElementObj = getNthTr ( parentObj, rowIndex1 - extraTr ); // <-- it will just return the trObject,
if ( targetElementObj ){
parentObj.insertBefore(trObj, targetElementObj.nextSibling )
}else{
//alert ( 'targetElementObj is null' );
}
This is working in FF as well as in IE, [ but, i guess, in case of IE name and colspan attribute is not set using setAttribute. but not sure ] .
Now, when i have to remove all rows which are dynamically created i use:-
dynamicTRObjs = document.getElementsByName('dynamicTR');
if ( dynamicTRObjs ){
parentObj = approvedDisapprovedTableObj.getElementsByTagName('tbody')[0];
for ( i = 0 ; i < dynamicTRObjs.length; i++ ){
parentObj.removeChild ( dynamicTRObjs[i] );
extraTr++;
}
}
This code removes all dynamically created TRs. and it works fine in FF, but not in IE.
Also in case of IE dynamicTRObjs.length is always 0,whereas in FF dynamicTRObjs.length it gives correct number of rows. Please tell me what i am missing here.
The HTML4 spec list of attributes lists elements that the name attribute can be set on. Tables and table elements are not on the list. The most obvious option is one of,
Keep references to all TRs you create so you don't have to find them in the DOM
Set a className on your TRs and use selectors to find them
That Firefox uses getElementsByName 'correctly' and IE does not is something others have run into too. I'd just avoid using name here altogether.
http://www.quirksmode.org/dom/w3c_core.html
getElementsByName() is not working well in IE6-8
I would suggest that you some other way of identifying that element if you want cross browser usability.
I know, it's a bit off-topic, but let me give you a small advice on using getElementsByName functionality in a browser. It will not help you to solve the current problem (which is because TR can not have Name attribute ), but it will definitely help you to prevent future problems which you will met.
getElementsByName returns you collection, which always keeps itself up-to-date with the DOM tree. This means, that at the moment when you remove ONE item with removeChild, the SIZE of collection will be decreased. So, if you will removing nodes and keep relying on the length of the collection, not all nodes will be removed.
Check this example of your for loop:
Collection length is 3, Your i var is 0, i < length
You remove child,
collection length is 2, your i var is 1, i < length
you remove child,
collection length is 1 and your i var i 2.
Condition i< length == true that means that for loop will stop, BUT some of the elements will still be presented in the DOM.
Choose any solution you like to fix this, but try to avoid relying on the length of the Collection which is returned by getElementsByTagName.
Good luck
since I'm not the only one to suggest avoidance of low-level DOM manipulation, here's an example: an untested implementation with jquery. not exactly an answer to your question, but a comment would lose the formatting.
var mkTd = function (colspan, html)
{
return $('<td />')
.attr('colspan', colspan)
.html(html)
;
}
var addRow = function (rowNr)
{
var target = $('#approvedDisapprovedTableObj tbody tr:eq('+rowNr+')');
if (!target.length) {
//alert ( 'target is null' );
return;
}
target.after(
$('<tr />')
.addClass('dynamicTR')
.append(mkTd(2, ' ')
.append(mkTd(15, $('#postingDivObj').html()))
);
}
var dropRows = function ()
{
$('.dynamicTR').remove();
}
notice that the expression $('.dynamicTR').remove() achieves the same as your
dynamicTRObjs = document.getElementsByName('dynamicTR');
if ( dynamicTRObjs ){
parentObj = approvedDisapprovedTableObj.getElementsByTagName('tbody')[0];
for ( i = 0 ; i < dynamicTRObjs.length; i++ ){
parentObj.removeChild ( dynamicTRObjs[i] );
extraTr++;
}
}
IMO it's obvious that the benefits are huge.

Categories

Resources