I’ll be the first to admit that Javascript is quite painful when dealing with it.
The only reason why we use it, is because it is native to most web browsers, however
its most painful feature is that each browser supports it differently. But Javascript
is still the best way to make a web app behave similarly to its cousin, the native
client app.
In trying to make a particular dialog expand and contract with new fields based on
selections by the user, I came up with a series of notes and lessons on making
Javascript work on the latest crop of browsers, i.e. Firefox/Mozilla, IE, Opera
and Safari.
Use getElementById
When hunting through a DOM (Document Object Model) of an HTML tree, the best approach
is to label particular elements with an “ID” attribute. Then you can quickly grab that
element with a call to getElementById(), and this approach works the same on all
browsers.
Another option, getElementsByNames() is only supported by IE, and getElementsByTagNames()
(which works on all browsers), gives you a collection of nodes, which you would then
have to analyze or grab what you want. For instance, the following code grabs all of
the <input> tags, and then hides any buttons it encounters:
var inputs = newCell.getElementsByTagName("input");
for (var i=0; i<inputs.length; i++)
{
var input = inputs[i];
if (input.type == "button" || input.type == "submit" )
input.style.display = "none";
}
Try to be Relative
Don’t get crazy with hard-coding element references, for as soon as you do,
the HTML will change, and then you’ve got to find all of the references in
your Javascript. One approach is to walk the tree from a known starting
point.
For instance, if the click of a button affects the “row” the button is on,
as in:
<input type="button" onclick="addService(this)" …>
Then walk this way to find the row that contains it:
function addService (button)
{
var row=button.parentNode;
while (row.nodeName!="TR") {
row=row.parentNode;
}
…
Don’t use InnerHTML
The innerHTML method seems like a wonderful way of adding dynamic content to
things. The idea is that you could add a slew of buttons to a cell by doing
something like:
var cell = document.getElementById("button-holder");
cell.innerHTML = '<input type="button" name="button1" …';
However, while all the recent browsers support this method, IE does not accept
code in the string that changes the layout. Yes, it is quite frustrating to
create a dynamic form on Mozilla using the easy-to-use, innerHTML function, and
have it completely die on IE.
Don’t despair, if there is a complex table that you want to insert into your
form, there is a solution…
Use Template Code
I found it better to create snippets of code to use as “templates.” Each of these
were marked as “hidden”, and then I would clone the node containing the code to
be copied, and then appended it to its new parent location. Example:
<table style="display: none;">
<tbody id="template-4">
<tr style="padding-top: 12px;">
<td>
<textarea name="targetExp" cols="65"
rows="6" wrap="soft"></textarea>
</td>
</tr>
</tbody>
</table>
Notice that the <table> tag has a style which sets the display to “none” (which
hides its display. I then associate an ID with the subelement that I will be
cloning with this Javascript code:
var template=document.getElementById('template-4');
var newnode = template.cloneNode(true);
newnode.id = "";
// Set the embedded <textarea> with a better default value
newnode.getElementsByTagName("textarea")[0].value = te;
formstart.appendChild(newnode);
Keep in mind that once you clone it, you need to change the
ID, as the cloned node will inherit it on IE.
What TBODY?
The DOM implementation in IE assumes that all tables have a
<tbody> tag… even if it isn’t explicitely written in the
HTML code. The best approach is to actually put it in the HTML
code so that Mozilla and friends will act the same way as IE.
Use deleteRow()
I found the best approach to building a dynamic, changeable form is to make heavy
use of <table>s for holding everything. This means that you will be deleting rows
first, and then adding new rows in order to change the content of certain sections.
The easiest way to delete a row is to use the deleteRow() method on a table
element. It takes a parameter of the index of the row to delete. If you have a
pointer to the row element, this can be done with this code:
try {
table.deleteRow(row.rowIndex);
}
catch (e) {
table.deleteRow(-1);
}
The catch code is there because of an exception that is sometimes thrown when
accessing the row.rowIndex in Firefox. Since in my particular case, the user
is often deleting the last row, I put a catch here and call deleteRow() with
a -1 (to mean, delete the last row).
Turning Things On and Off
More often than adding new elements to a form, you will be hiding and displaying
some fixed code (often based on the value of a checkbox or radio button).
If the element to be toggled has an ID, then it is quite easy to do this:
<input type="radio" name="advform" value="simple"
onclick="document.getElementById('adv').style.display='none'" />
Simple
<input type="radio" name="advform" value="advanced" checked="checked"
onclick="document.getElementById('adv').style.display=''" />
Advanced
...
<td id="adv">
…
</td>
Thought originally posted on Tuesday, 12 April 2005
©
2005, Howard Abrams •
Except where otherwise noted, all original content is licensed under a
Creative Commons License (see
details).