Dom walking for dummies

B

Ben Hallert

Hi guys!

I'm working on a little javascriptlet/greasemonkey script, and I've run
into a challenge that I think can be solved with walking the DOM, but I
feel like I'm kludging my way through and wondering if there might be a
better answer out there.

Here's the problem: I want to insertCell(0) into an existing table, but
the table doesn't have a NAME or ID. I don't have the option of adding
these (it's for Wikipedia) and would like to programatically navigate
my way to the table. The table is inside a FORM element that _does_
have an ID, so it's pretty close to a known entry point. I was
originally planning on using .firstChild a few times in a nested
sequence to work my way down to the table, but it's proving trickier
than I thought it would, hence my question here.

Here's the relevant snippet of HTML I'm working with:

---------
<form id="blockip" method="post"
action="/w/index.php?title=Special:Blockip&amp;action=submit">
<table border='0'>
<tr>
<td align="right">User:</td>
<td align="left">
---------

I want to insertCell(0) a new cell into the first TR in the table, then
setAttribute it to a rowspan of 8. My original foray into walking (or
perhaps stumbling is a better choice) the DOM was something like this:
var parent_form = document.getElementById('blockip');
var spectable = parent_form.firstChild;
var spectr = spectable.firstChild;
var newcell = spectr.firstChild.insertCell(0);

It matches the nesting shown above, but it feels kludgy and has the
added benefit of not working.

For you folks out there that might find themselves in the same sort of
situation (you can't add the ID to the existing table, but you need to
interact with it), how would you resolve this? Am I on the right path,
or is there a smarter way to get from point A to point B?

The end result is that I want to add a cell to the far left of the
table then insert a select element I'm currently jamming underneath
into it for better presentation.

Thanks!

Ben
 
P

pangea33

Ben said:
Hi guys!

I'm working on a little javascriptlet/greasemonkey script, and I've run
into a challenge that I think can be solved with walking the DOM, but I
feel like I'm kludging my way through and wondering if there might be a
better answer out there.

Here's the problem: I want to insertCell(0) into an existing table, but
the table doesn't have a NAME or ID. I don't have the option of adding
these (it's for Wikipedia) and would like to programatically navigate
my way to the table. The table is inside a FORM element that _does_
have an ID, so it's pretty close to a known entry point. I was
originally planning on using .firstChild a few times in a nested
sequence to work my way down to the table, but it's proving trickier
than I thought it would, hence my question here.

Here's the relevant snippet of HTML I'm working with:

---------
<form id="blockip" method="post"
action="/w/index.php?title=Special:Blockip&amp;action=submit">
<table border='0'>
<tr>
<td align="right">User:</td>
<td align="left">
---------

I want to insertCell(0) a new cell into the first TR in the table, then
setAttribute it to a rowspan of 8. My original foray into walking (or
perhaps stumbling is a better choice) the DOM was something like this:
var parent_form = document.getElementById('blockip');
var spectable = parent_form.firstChild;
var spectr = spectable.firstChild;
var newcell = spectr.firstChild.insertCell(0);

It matches the nesting shown above, but it feels kludgy and has the
added benefit of not working.

For you folks out there that might find themselves in the same sort of
situation (you can't add the ID to the existing table, but you need to
interact with it), how would you resolve this? Am I on the right path,
or is there a smarter way to get from point A to point B?

The end result is that I want to add a cell to the far left of the
table then insert a select element I'm currently jamming underneath
into it for better presentation.

Thanks!

Ben

You're on the right track it looks like. Traverse the DOM while looking
for entities with an ID that matches the one you know for the form.
Then traverse down while looking for the nested tags you know will be
there. e.g. tr and td. Here's a snippet I have written that won't apply
directly, but I think it will give you some insight.

function changeLinks(selAttributes, assignVal){
var aAttribNames = selAttributes.split(',');
var alltags=document.getElementsByTagName("a") ?
document.getElementsByTagName("a") : document.all;

for (i=0; i<alltags.length; i++){
// this modifies every link
for (j=0; j<aAttribNames.length; j++){
if (alltags.href && alltags.href.length) {
eval("alltags.style."+aAttribNames[j]+"='"+assignVal+"'");
}
}

//changeLinks('linkColor', '#FF0000');
//only modify links with an id set

if(alltags.id) {
if (alltags.id.substr(0,alltags.id.length)=='IDOFTHEFORM'){
if (alltags.innerHTML && alltags.innerHTML.length) {
//document.getElementById('statusDiv').innerHTML+=alltags.innerText;
if (alltags.canHaveChildren) {
for (j=0; j<alltags.childNodes.length; j++){
if(alltags.childNodes[j].nodeName == 'IMG') {
//alert(alltags.childNodes[j].getAttribute('src'));
alltags.childNodes[j].src =
'http://127.0.0.1/myspace/images/767235372_s.jpg';
var thingie = "alltags";
while (eval(thingie+".parentElement.tagName") != "HTML" ) {
thingie+='.parentElement';
document.getElementById('dumper').innerText+=eval(thingie+".parentNode.style.borderColor");
document.getElementById('dumper').innerHtml+='<br>';
//alert(alltags.parentElement.tagName);
}
}
}
}
}
}
}
}
}
 
O

[on]

Hi guys!

I'm working on a little javascriptlet/greasemonkey script, and I've run
into a challenge that I think can be solved with walking the DOM, but I
feel like I'm kludging my way through and wondering if there might be a
better answer out there.

Here's the problem: I want to insertCell(0) into an existing table, but
the table doesn't have a NAME or ID. I don't have the option of adding
these (it's for Wikipedia) and would like to programatically navigate
my way to the table. The table is inside a FORM element that _does_
have an ID, so it's pretty close to a known entry point. I was
originally planning on using .firstChild a few times in a nested
sequence to work my way down to the table, but it's proving trickier
than I thought it would, hence my question here.

Here's the relevant snippet of HTML I'm working with:

---------
<form id="blockip" method="post"
action="/w/index.php?title=Special:Blockip&amp;action=submit">
<table border='0'>
<tr>
<td align="right">User:</td>
<td align="left">
---------
I want to insertCell(0) a new cell into the first TR in the table, then
setAttribute it to a rowspan of 8. My original foray into walking (or
perhaps stumbling is a better choice) the DOM was something like this:
var parent_form = document.getElementById('blockip');
var spectable = parent_form.firstChild;
var spectr = spectable.firstChild;
var newcell = spectr.firstChild.insertCell(0);

You said you have;
<form>
<table>
<tr>
<td></td>
<td></td>
....
</tr>
....
</table>
....
</form>

You first select the Form = <form>
Then you take the firstChild = <table>
Then you take the firstChild again = <tr>
Then you take the firstChild yet again = <td>

To my knowledge "insertCell" works on Table-Rows (TR) and not
Table-Cells (TD).
 
R

Richard Cornford

[on] wrote:
You said you have;
<form>
<table>
<tr>
<td></td>
<td></td>
....
</tr>
....
</table>
....
</form>

You first select the Form = <form>
Then you take the firstChild = <table>
Then you take the firstChild again = <tr>
<snip>

What happened to the TBODY implied in HTML (but not in XHTML, where it
is optional)?

Richard.
 
M

Martin Honnen

Ben said:
<form id="blockip" method="post"
action="/w/index.php?title=Special:Blockip&amp;action=submit">
<table border='0'>


Grag the form with
var form = document.getElementById('blockip');
then use getElementsByTagName on the element e.g.
if (form) {
var firstTable = form.getElementsByTagName('table')[0];
if (firstTable) {
// now you can look for firstTables.rows[0]
}
}

Other ways to access stuff in a Greasemonkey script are XPath (over
HTML) using document.evaluate as in a Greasemonkey script you know you
have support for that in Mozilla (or Opera 9).
 
R

Richard Cornford

pangea33 wrote:
function changeLinks(selAttributes, assignVal){
var aAttribNames = selAttributes.split(',');

It is recommended that code not be posted indented with tabs. Sequences
of spaces should be used instead (preferably not that many per tab). The
width of a tab on the viewer's set-up is capable of varying
considerably, from zero to equivalents of 8 or more spaces (which is far
too many for each level of indentation when posted to news, where line
wrapping at 80ish characters is not unusual).
var alltags=document.getElementsByTagName("a") ?
document.getElementsByTagName("a") : document.all;

That is a very bizarre line of code. Instead of making a decision based
upon whether or not the browser environment supports a -
document.getElementsByTagName - it is making its decision on whether or
not a call to - document.getElementsByTagName('a') - returns an object
or not. But - document.getElementsByTagName - is specified as always
returning a NodeList, even if it may be an empty NodeList. Thus the -
document.all - alternative will never be taken, and if an environment
does not provide - document.getElementsByTagName - the attempt to call
that method will have the4 code error-out at that point ensuring that -
document.all - cannot serve as an alternative for older browsers.
for (i=0; i<alltags.length; i++){

Using a loop counter that has not been declared as a function local
variable is a dangerous habit. The veritable effectively becomes global
and when that error is made in one location it is often repeated in
another, with the consequence that if code in a loop calls another
function that runs another loop using the same global counter variable
the counter in the 'outer' loop will have the wrong value when the
called function returns. The general programming axiom that no variable
should ever be given more scope than it absolutely needs applies to
javascript as much as it does to any other language (even if the
scopeing units in javascript are as coarse as being at the function
definition level).
// this modifies every link
for (j=0; j<aAttribNames.length; j++){
if (alltags.href && alltags.href.length) {


Given the number of times - alltags - is to be resolved in the
following code it would be a good idea to assigned a reference to the
element to a local variable. That is, with:-

var link;

- declared at the top of the function (at the top purely for style
reasons), here inside the loop the assignment:-

link = alltags;

- would avoid having to resolve the - i - property of the - alltags -
collection repeatedly.

When you test - alltags.href - the value of that property is
type-converted to boolean. If the element has no - href - property that
value is Undefined, which type-converts to boolean false. However, when
the element has an - href - property that property is a string
primitive. Empty string primitives (those with a length of zero)
type-convert to boolean false, while non-empty strings type-convert to
boolean true. This mans that the following - alltags.href.length - is
utterly redundant as its value is numeric and the number zero
type-converts to false while all non-zero (and non-NaN, which string
lengths cannot be) type-convert to true. Whenever the second test would
evaluate to false the first test had already evaluated to false,
short-circuiting the logical AND expression and preventing the
evaluation of the second expression.
eval("alltags.style."+aAttribNames[j]+"='"+assignVal+"'");


There is almost no need to ever use - eval - in javascript, particularly
with property accessors. Generally the use of - eval - with dot-notation
property accessors follows from an ignorance of bracket notation
property accessors. See:-

<URL: http://jibbering.com/faq/faq_notes/square_brackets.html >

The equivalent (but shorter and faster) code without the - eval - would
be:-

alltags.style[aAttribNames[j]] =' assignVal;

(or, given the proposed use of a local reference to - alltags -:-

link.style[aAttribNames[j]] =' assignVal;

)
}
}

//changeLinks('linkColor', '#FF0000');
//only modify links with an id set

if(alltags.id) {
if (alltags.id.substr(0,alltags.id.length)=='IDOFTHEFORM'){
if (alltags.innerHTML && alltags.innerHTML.length) {
//document.getElementById('statusDiv').innerHTML+=alltags.innerText;
if (alltags.canHaveChildren) {
for (j=0; j<alltags.childNodes.length; j++){


Another undeclared loop counter.
if(alltags.childNodes[j].nodeName == 'IMG') {
//alert(alltags.childNodes[j].getAttribute('src'));
alltags.childNodes[j].src =
'http://127.0.0.1/myspace/images/767235372_s.jpg';
var thingie = "alltags";


This is another odd thing to be doing as it was always possible to
assign the value of - alltags - to a variable.
while (eval(thingie+".parentElement.tagName") != "HTML" ) {

But that was being done to allow this second needless use of - eval -.
If the - link = alltags; - assignment had been made then the - eval -
could have been - eval('link.parentElement.tagName') -, which instantly
reveals why it is pointless, as it can be directly replaced with -
link.parentElement.tagName - to the same effect (but again shorter and
faster), though - alltags.parentElement.tagName - would also have
worked without the - eval -.

Incidentally, - parentElement - is a proprietary Microsoft property. The
DOM standard equivalent is - parentNode -. The most recent Microsoft
browser that did not support - paraentNdoe - was IE 4, and IE 4
errored-out at the - document.getElementsByTagName("a") - call because
it has no - document.getElementsByTagName - method.
thingie+='.parentElement';
document.getElementById('dumper').innerText+=eval(thingie+".parentNode.s
tyle.borderColor");
document.getElementById('dumper').innerHtml+='<br>';
//alert(alltags.parentElement.tagName);
}

<snip>

Without the - eval - above the loop logic here becomes:-

while(link.parentNode.tagName != "HTML" ) {
link = link.parentNode;
document.getElementById('dumper').innerText +=
link.parentNode.style.borderColor;
document.getElementById('dumper').innerHtml += '<br>';
}

- and another unnecessary - eval - is avoided.

Richard.
 
B

Ben Hallert

Great responses! I'll try out some of the techniques suggested and
report back. Very promising looking stuff, thanks!

Ben
 
B

Ben Hallert

All of the responses were very helpful! I ended up using Mr. Honnen's
method and it worked right out of the gate. I ran into something
interesting that I wanted to share. When I did a view-source on the
page, it gave me one structure (the one I posted above), but when I
used the Firebug extension to view the table directly, it indicated a
TBODY element was parent to the TRs. So I wrote it to use the
getElementsByName method to walk from the known form element that had
the ID down to the table, tbody, then TR. Once there, I created a
newcell object with insertCell then appendChild'd my click menu into
it. It worked perfectly.

I've put the full code of my script at
http://en.wikipedia.org/wiki/User:Chairboy/blockhelper.greasemonkey.js

Thanks a bunch!

Ben
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,770
Messages
2,569,584
Members
45,075
Latest member
MakersCBDBloodSupport

Latest Threads

Top