Section E. Creating and cloning elements


E. Creating and cloning elements

The W3C DOM allows you to create your own elements and text nodes, and add them to the document tree. In theory, you could remove all elements from the tree, create new ones, and repopulate the tree, thus changing the page completely. In practice, this feature is used in a somewhat more restrained way.

The DOM also allows you to clone existing elements, so that you can easily duplicate a certain feature and spread copies through your document.

createElement() and createTextNode()

The createElement() method and the createTextNode() do just what their names say they will:

var x = document.createElement('p'); var y = document.createTextNode('This is a created element'); 


x refers to the newly created <p> element, while y refers to the newly created text node. These nodes aren't inserted in the document yet. You would now use appendChild() or insertBefore() to add them to the document tree. For instance:

x.appendChild(y); document.body.appendChild(x); 


This appends the text node to the <p>, and the <p> to the document. Now the document has one extra <p> tag as its last child.

Createelement('Option'): don't Use

Don't try to create option elements through createElement(), because Explorer doesn't support it. See 8J for the safe way to add options to a document.


Usually you create an element and insert it into the document immediately. But you can also create elements and keep them around for future use. I do this with the extraButton in Sandwich Picker that we already saw in the appendChild() and removeChild() examples.

Right at the start, before I even run my initialization function, I create a few elements I'll need in the rest of the script:

[Sandwich Picker, lines 4-12, condensed]

if (W3CDOM) {     // create more elements        var extraButton = document.createElement('button');     extraButton.className = 'extraButton';     extraButton.appendChild(document.createTextNode('Collect all orders'));     extraButton.onclick = moveAllToOrderTable; } 


Why Create the Button Before Initialization?

Frankly I can't remember why I created this button before the official script initialization. These are real-world example scripts, so occasionally they contain real-world oddities.


If the W3C DOM is supported, the script creates a <button> element, gives it a class name for CSS purposes, appends a text node so that the user understands what the button does, and adds an onclick event handler that calls the right function.

The extra button is now ready for use, but I want it to appear only when the user orders a sandwich by entering an amount in a form field; at that moment I use appendChild() to actually add the button to the document. Until then it's quietly waiting in the DOM hyperspace we'll discuss in 8K; it may not ever be used.

createTextNode() and HTML entities

There's one problem with createTextNode(): it cannot create HTML entities like &copy; or &#8212;. Instead of the symbol you need, it creates the literal text:

var x = document.createTextNode('&copy; Copyrights reserved'); document.getElementById('test').appendChild(x); 


Figure 8.10. createTextNode() cannot create HTML entities.


Use innerHTML instead:

document.getElementById('test').innerHTML = '&copy; Copyrights reserved'; 


Figure 8.11. Solution: use innerHTML.


cloneNode()

The cloneNode() method clones a node; that is, it makes a near-exact copy of the node, which you can subsequently insert in the document tree. For instance:

var x = document.getElementsByTagName('h1')[0]; var y = x.cloneNode(true); document.body.appendChild(y); 


I take the first <h1> in the document and clone it. Then I append the clone to the <body>.

Figure 8.12. The last element on the page is a clone of the <h1>.


cloneNode() has a couple of features you must know about in order to use it properly:

  • cloneNode() expects an argument true or false. true means "Clone the element and all its child nodes." false means "Clone the element but not its child nodes." In practice, you always use true; I have never yet encountered a situation in which you want to clone a node but not its children.

  • cloneNode() does not clone event handlers. This is extremely annoying, but it's how the method was specified. (Why? I have no idea.) So every time you clone a node with event handlers, you have to redefine them on the clone.

Sandwich Picker contains a useful example of all this. Every sandwich should have its own Order and Trash buttons, and of course these dozens of buttons are exactly the same. This is a typical job for cloneNode()!

[Sandwich Picker, lines 21-28]

var trashLink = document.createElement('a'); trashLink.href = '#'; trashLink.innerHTML = 'trash&nbsp;'; trashLink.className = 'trash'; var orderLink = document.createElement('a'); orderLink.href = '#'; orderLink.innerHTML = 'order&nbsp;'; orderLink.className = 'order'; 


At the start of the initialization function, I create one trashLink and one orderLink as template nodes. Note that I use innerHTML to create the link texts: I need a &nbsp; for CSS reasons, and createTextNode() doesn't allow me to insert it.

Later, when I go through all <tr>s in the order table, I clone these two templates, add the correct event handlers, and insert them into the document.

[Sandwich Picker, lines 47-52]

var extraLink = trashLink.cloneNode(true); extraLink.onclick = removeSandwich; var extraLink2 = orderLink.cloneNode(true); extraLink2.onclick = moveToOrderTable; searchField.parentNode.appendChild(extraLink2); searchField.parentNode.appendChild(extraLink); 


Every sandwich <tr> has a searchField form field. Since the buttons should appear underneath this form field, I append them to searchField.parentNode: the <td>.

Note that I assign the event handlers only after the buttons have been cloned. They would not survive the cloning process.

Creating tables and form fields

Creating tables and form fields in the W3C DOM is tricky if you don't know the ins and outs.

Use <tbody>

Generating table elements like <td> or <tr> works, as long as you use a <tbody>, eschew the specialized DOM table methods, and don't use innerHTML too frequently.

Some browsers (Explorer, at the time of writing) require you to append your <tr>s to a <tbody>. If you append the <tr>s directly to the <table>, they simply don't show up.

Sandwich Picker constantly moves <tr>s to other tables. For instance:

[Sandwich Picker, lines 86-89, condensed]

var node = [tr that should move to search results]; document.getElementById('searchResults').appendChild(node); 


I added the ID 'searchResults' not to the <table> tag, but to the <tbody> tag:

<table  >    <tr>            <td colspan="4" >                   <h4>Search for sandwiches</h4>            <input ></td>    </tr>    <tbody >    </tbody> </table> 


Now all <tr>s are appended to the <tbody>, and the script works smoothly in all browsers.

No Cellindex in Safari

At the time of writing, cellIndex is buggy in Safari 2.0; it always returns 0.


W3C DOM table methods and properties

The W3C DOM defines quite a few methods and properties for working with tables. Unfortunately, they suffer from a few browser-incompatibility problems, and even if they work perfectly they're far slower than traditional createElement()/appendChild() scripts. I advise you not to use most of these methods and properties.

The rowIndex and cellIndex properties may come in useful in some situations. They give the index number of a row in its table (rowIndex) or a cell in its row (cellIndex). There's also sectionRowIndex, which gives the index number of a row in its table section (thead, tbody, or tfoot).

innerHTML

innerHTML is badly supported for table elements other than <td>s. At the time of writing, it's not possible to set the innerHTML of the tbody, thead, or tfoot in Explorer or Safari.

Form fields in Explorer

Generating form fields is hard in Explorer. In theory, it seems simple:

   var newField = document.createElement('input');  newField.type = 'checkbox';  newField.name = 'My name';  newField.value = 'My value';  document.getElementById('theForm').appendChild(field); 


Unfortunately, this code gives errors in Explorer, because the browser cannot handle the newField.type. If we use setAttribute(), the error message disappears:

newField.setAttribute('type','checkbox'); 


Another problem is that generated radio buttons don't work well in Explorer. Although it's possible to generate them through "pure" W3C DOM methods, the browser makes a mess of the name attributes of the radio buttons. Therefore, the generated radio buttons will likely not work as you expect them to.

How Radio Buttons Work

Remember: the user is allowed to check only one radio button in a group, and a group is defined as all radio buttons that share a name attribute. If the name attribute doesn't work correctly, the radio buttons won't work, either.


Switching to innerHTML offers a solution:

var newField = '<input type="radio" name="radioGroup" value="first" />'; newField += '<input type="radio" name= "radioGroup" value="second" />'; document.getElementById('theForm').innerHTML += newField; 


Now the radio buttons work as expected.



ppk on JavaScript. Modern, Accessible, Unobtrusive JavaScript Explained by Means of Eight Real-World Example Scripts2006
ppk on JavaScript. Modern, Accessible, Unobtrusive JavaScript Explained by Means of Eight Real-World Example Scripts2006
ISBN: N/A
EAN: N/A
Year: 2005
Pages: 116

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net