More javascript

Finding the date

d=new Date(); will make d into the current date. Is d a number? A string? Neither. d is a "date object." If you treat it like a string, it will turn itself into one (forms[0].time.value=d;.) There are also some built-in date functions to get out the numbers:

Finding things in other places (getElementById):

Any other item may be located using it's id field (name is an easy shortcut for forms. id can find anything.) Use getElementById(id in quotes) (a reference [near the bottom]):

Please don't hurt me.Or me sabra pablo
<table><tr>
<td id="sabra">Please don't hurt me.</td><td id="pablo">Or me</td>
<td onmouseover="getElementById('sabra').style.fontStyle='italic';"
    onmouseout="getElementById('sabra').style.fontStyle='normal';">sabra</td>
<td onclick="getElementById('pablo').firstChild.nodeValue='Ouch!';">pablo</td>
</tr></table>

This should have document. in front of it, but I can leave it off here and it will guess I mean "the current document." To change the value of any item, note the odd firstChild.nodeValue syntax. This is where the DOM comes into play. The text inside a td isn't a value attribute, as with a form. What is its name? The DOM says the text is the first "node" inside the td (so we use firstChild,) and the contents of a node is referred to using nodeValue. A reference.

getElementsByName

IDs must be unique, making it safe to return the one match. It is also possible to get all elements with a certain name using w=getElementsByName("weaselinfo"). This makes w into an 0-based array of everything with name="weasel". This turns everything yellow with that name:

n=document.getElementsByName("hardware")
index=0
while(index<n.length)
 { n.item(index).style.background="#FFFFA0"
   index++
 }

It might be nice to have getElementsByAttributeName("dept","hardware"), but there isn't one. It you want to search for an attribute that isn't name you need to use a treewalker of some sort to searchyourself (below.) Also, the above is easier to do by inserting the style rule *[name~="hardware"] {background:yellow}

Changing top of page style rules

What if I want to make all of my tables go across the screen? body.style.width="100%" won't do it. I need to add table {width:100%} to my top-of-page style rules. The easy part is: styleSheets[0].insertRule('p {color:red}',0). Write the rule exactly as it would appear in HTML, and use insertRule to add it. There can be several stylesheets, but if you have only one, styleSheets[0] refers to it. Here's the tricky part. Since the last rule always wins in a stylesheet, the order matters. We want our new rule to win, so need to add it last. Unfortunately, insertRule wants to insert it before an existing rule (as you might expect by now, they are numbered, starting from 0.) If you know the color of paragraphs hasn't been set, the above is safe. But, if it has been set in rule 5, that will overrule ours.

one trick is to say our rule is "important" (an obscure stylesheet trick.) A better trick is to add ours to the end. All we need to do is lookup the number of rules, and add ours before one past the end (if there are 10 rules, always numbered 0-9, our new one should go "before" the non-existant rule #10.) You might think to always insert before 9999, but the browser won't let you skip rules. So, to add a rule:
<onclick="styleSheets[0].insertRule('tt,pre {background:red}',styleSheets[0].cssRules.length);">

shortcut for changing body style rules

A useful shortcut is using body (really document.body) instead of locating the body by its id (this works since there is only one body.) <tt onmouseover="body.style.background='green';" onmouseout="body.style.background='white';">

setTimeOut/setInterval

a=setTimeout("thing()",2000) will execute "thing()" in 2 seconds. clearTimeout(a) will cancel it.

a=setInterval("thing()",2000) will do the same thing, every two seconds, with clearInterval(a) getting rid of it.

a is an integer (similar to a process ID using unix ps.) It is not reset on the call to clearTimeout. In other words, if you also want to use a to check whether thing is about to run (maybe to avoid doing it twice,) you should start with a=0 and reset it to 0 after each clearTimeout.

Animation

<span style="position:relative"><span id="X" style="position:absolute;z-index:1">X</span></span>
X will make a floating X. The position:relative establishes the current position as (0,0) for anything inside of it. Without it, position:absolute works just fine, but uses the upper-left of the document. You may move it by changing the left or top style values:
<input type="button" onclick="getElementById('X').style.left=40px"> . Using a global x and y coordinate, it isn't too difficult to move something around, with setInterval.

O This position:relative paragraph around the ball gives it a (0,0) that isn't top of screen.

<script>
var animate=0  // am I running?/handle for clearInterval
var pos=0   // where I will draw ball
var speed=5
function move()
 { pos+=speed
   if(pos>100) { speed=-5; } // make it go backwards if it hit the "edge"
   if(pos<0) { speed=5; pos=5; }
   // Moves the ball, by changing it's { left:20px} stylesheet rule:
   document.getElementById('ball').style.left=pos.toString()+"px";
 }
</script>

<P style="position:relative">
<span id="ball" style="position:absolute; font-size:24pt; z-index:1">O</span>
This position:relative paragraph around the ball gives
it a (0,0) that isn't top of screen.
</P>

<input type="button" value="Start" onclick="if(!animate){animate=setInterval("move()",25)}">
<input type="button" value="Stop" onclick="clearInterval(animate);animate=0">

Instant drop-down menus

There are three tricks to this. First, use position style-sheet rules to make a table, or whatever you want, on top of your "button". Second, use the style sheet visibility:hidden and visible rules to make it appear on a mouseover of your button and vanish on a mouseout of itself. The third trick is to solve a problem with mouseout.

Currently, moving between any two parts of something creates a mouseout for the whole thing. This includes going between rows of a table, but also between two span-tags. The problem is then that moving from the first item to the second in your dropdown will trigger a mouseout for the whole table and will make it vanish. A way to get around it to use setTimeout to give maybe a quarter-second delay (250 milliseconds,) and to use onmouseover to cancel it.

red
green
black
black
yellow
blue
aqua
second menu
<style>
span.ddm { position:absolute; visibility:hidden; z-index:1; left:0; top:0 }
table.ddm { border:2px solid black; background:#A0A0A0; border-collapse:collapse }
table.ddm td { padding:4px; background:#A0A0A0}
table.ddm td:hover { background:white}
</style>

This is a typical use of stylesheets to save typing and would go at the top of the page. The actual pop-up part of the menu will use position absolute, at (0,0) (relative to the position:relative span surrounding, as with the animation trick above.) It will start hidden. The z-index controls what "covers" what. A page starts at 0, so our drop-down will go over the page. In practice, z-index currently isn't needed.

table.ddm, etc... (stands for drop-down menu) is just standard table prettification stuff.

<script>
function startCloseMenu(menuId)
 { var m=document.getElementById(menuId);
   if(m.closeMe=="0")
    { m.closeMe="1"; // a mouseover will reset this to 0
      setTimeout("closeMenu('"+menuId+"')",125);
    }
 }
function closeMenu(menuId)
 { var m=document.getElementById(menuId);
   if(m.closeMe=="1") // no mouseover's reset it? we really are gone
    { m.style.visibility="hidden";
      m.closeMe="0";
    }
 }
</script>

These functions (also should be at top -- one copy will handle any number of menus) solve the "internal mouseout" problem. startCloseMenu sets the closeMe flag and calls closeMenu, after 1/8th second, which hides the menu unless someone (the menu's mouseover) reset the flag. closeMe could have been a javascript variable, but it seemed easier to make it into an attribute of the menu (which is why I write m.closeMe.)

<span style="position:relative">
<span id="menu1" closeMe="0" class="ddm" onmouseout="startCloseMenu(this.id)" onmouseover="this.closeMe='0'">
  <table class="ddm">
    <tr><td onclick="body.style.color='red'">red</td></tr>
    <tr><td onclick="body.style.color='green'">green</td></tr>
    <tr><td onclick="body.style.color='black'">black</td></tr>
  </table>
</span>
  <input type="button" value="Change Color"
   onmouseover="getElementById('menu1').style.visibility='visible'">
</span>

This is the actual trigger for the menu and the menu itself. The whole thing is wrapped in a position:relative span. The dropdown comes first (so the upper-left is exactly lined up with the button) then the button. id="menu1" can use any name, and much match the menu1 in the button. closeMe is the flag for the delay.

The button merely displays the menu. The menu takes cares of closing itself.

<span style="position:relative">
<span id="menu2" closeMe="0" class="ddm" style="margin-top:1em" 
  onmouseout="startCloseMenu(this.id)" onmouseover="this.closeMe='0'">
  <table class="ddm">
    <tr><td onmouseover="body.style.color='black'">black</td></tr>
    <tr><td onmouseover="body.style.color='yellow'">yellow</td></tr>
    <tr><td onmouseover="body.style.color='blue'">blue</td></tr>
    <tr><td onmouseover="body.style.color='aqua'">aqua</td></tr>
  </table>
</span>
  <b onmouseover="getElementById('menu2').style.visibility='visible'">second menu</b>
</span>

The second menu merely shows that it need not be a button, and how to move it down (with style="margin-top:1em".

If I was really cool, I'd make a javascript function to document.write all of this (makeMenu("menu3","Change Color","green","onclick='body.style.color="green"',...)).

Traversing a page and the DOM

The DOM

To move around it a page, you need to know a little more about the tree built by the browser, according to the document object model (w3c DOM, level 2, Node defination). Start with this web page:

<body>
  <p>A really <b>big</b> toad!</p>

  <table><tr><td>A</td><td>B</td></td><td>C</td></tr>
         <tr><td>D</td><td>E</td></td><td>F</td></tr>
  </table>
</body>

It would be parsed into Nodes in this tree-like structure:


             -node(null)
            /
           /        -node(#text)[nodeValue:A really ]
          /        /
node(BODY)--node(P)--node(B)--node(#text)[nodeValue:big]
         \         \
          \         -node(#text)[nodeValue: toad!]
           |
           \-node(null)
           |                                     node(TD)--node(#text)[nodeValue:A]
            \                                   /
             -Node(TABLE)--node(TBODY)--node(TR)-node(TD)--node(#text)[nodeValue:B]
                                     \          \
                                      \          node(TD)--node(#text)[nodeValue:C]
                                      |
                                      |          node(TD)--node(#text)[nodeValue:D]
                                      \         /
                                       -node(TR)-node(TD)--node(#text)[nodeValue:E]
                                                \
                                                 node(TD)--node(#text)[nodeValue:F]

The null nodes are dummy nodes added by the browser, and are a real pain. TD, TR, BODY, ... are the nodeNames. For example, document.body.nodeName is BODY. document.body.childNodes[1].nodeName is P (the P is the second child, because of that pesky dummy node, which is not guaranteed to be there.)

The general rule is that anything which could have children does not have text. A regular P with just text "A really" in it would look like the top branch of the P above (and #text is the NodeName.) "A really" would be the NodeValue: document.body.childNodes[1].firstChild.nodeValue is "A really ". All interior nodes (with children) have no nodeValue (which javascript writes as null.)

Each node also has a nodeType which is 1(ELEMENT_NODE) for interior nodes and 3(TEXT_NODE) for leaves. Note that even a TD with a single letter is an ELEMENT_NODE. All text is stored in these extra TEXT_NODEs. Dummy nodes are TEXT_NODEs and have nodeValue='\n'

Written another way, here is the first TD node:

nodeType:1 (ELEMENT_NODE)         nodeType:3 (TEXT_NODE)
nodeName:"TD"                     nodeName:"#text"
nodeValue:null                    nodeValue:"A"
firstChild:------------------> firstChild:null
lastChild:-/    /                 lastChild:null
childNodes[0]:-/                  childNodes.length:0
childNodes.length:1

childNodes is a standard 0-based array of child nodes. firstChild and lastChild are shortcuts for childNodes[0] and childNodes[childNodes.length-1]

This code, added to a web page, will print out the structure. It is a standard depth-first tree-traversal from cs228:

<script>
function dumptree(N,indent)
 { var i
   var nlist
   if(N.nodeName=="SCRIPT" || N.nodeName=="PRE") return;
   document.write(indent+"("+N.nodeType+")"+N.nodeValue+"["+N.nodeName+"]\n")
   if(N.hasAttributes())
    { for(i=0;<N.attributes.length;i++)
       { document.write(", "+N.attributes.item(i).nodeName+":"+N.attributes.item(i).nodeValue); }
    }
   document.write("\n");

   if(N.hasChildNodes())
    { nlist=N.childNodes
      for(i=0;i<nlist.length;i++) { dumptree(nlist[i],indent+"  ") }
    }
 }
</script>
<body>
...............thing to check structure of here................
<script>
document.write("<pre>");
dumptree(document.body," ")
document.write("</pre>");
</script>

That shows several things. javascript uses the java-style .length (with no ()'s). Arrays are 0-based and may use [i] or .item(i). Attributes are stored as simply an array of nodes: id="trey" makes a node with nodeName:"id", nodeValue:"trey".

Using the firstChild, lastChild, nextSibling shortcuts, I could have written the "visit all children" loop as:

   if(N.childNodes.length!=0)
    { curChild=N.firstChild
      dumptree(curChild,indent+"  ")
      while(curChild!=N.lastChild)
       { curChild=curChild.nextSibling
         dumptree(curChild,indent+"  ")
       }
    }