PPK on JavaScript: The DOM - Part 2/Page 6 | 2
[previous]
PPK on JavaScript: The DOM - Part 3
K: DOM hyperspace
Now that we've discussed the most important parts of the W3C DOM and the Level 0 DOM, it's time to look at a few related subjects. The first is what I call the "DOM hyperspace." It plays a vital role in Usable Forms.
Take this code:
The paragraph is removed from the document. But it's not gone. Where is it? I say that it floats in the DOM hyperspace. Since variable x
still refers to it, it's still accessible. For instance, this works fine:
The text node is appended to the paragraph, and the revised paragraph is reinserted into the document.
It's important to realize that the element remains available only as long as a pointer to it exists; in that respect it's similar to cross-window communication, as we discussed in Section 6B. If I wrote the following, the connection would be lost, and could not be re-established:
The last line doesn't work, since the element with ID="test" cannot be found in the document. The element is lost forever.
Every new element you create starts in the DOM hyperspace:
The new <div>
element is created in the hyperspace, more elements and text nodes are added to it, and only when it's ready do we insert it into the document.
Storing elements in hyperspace
It's possible to store large numbers of elements in hyperspace, and Usable Forms does so. Taking a closer look at this part of the script is instructive.
In Section 8I, we saw that the script first gathers pointers to the <tr>
s that should be removed. After that's done, the script goes through the hiddenFields
array and moves the <tr>
s to the DOM hyperspace.
However, moving them to hyperspace is not enough. The <tr>
s should remain available for reinsertion into the document, and as we just saw, that requires that a variable or an array element continues to point to the element so that it isn't lost.
This part of Usable Forms takes care of that:
The script creates an object—hiddenFormFieldPointers
—that functions as an associative array (see 5K). As the name suggests, this associative array contains pointers to the <tr>
s that aren't currently displayed (the ones that are floating in hyperspace.) In addition to providing a lookup table (see below), it also makes sure that pointers to all hidden form fields continue to exist, so that they're not lost.
hiddenFields[0]
is the <tr>
that's currently being removed from the document. The script takes the value of its rel
attribute and checks if hiddenFormFieldPointer
already has a key with this value. If not, it creates this key and assigns an empty array as its value:
It's possible to have several <tr>
s with the same value of the rel
attribute; they should all be shown or hidden when the user selects the appropriate form fields. Therefore, the script has to find out how many <tr>
s with this rel
attribute are already in hyperspace. The array already points to all of them, so its length
gives this number. I add a pointer to the <tr>
as the last element of the array:
Now I can safely remove it from the document and send it to hyperspace; a pointer exists, so it is not lost.
When the time comes to reinsert form fields into the document, I use hiddenFormFieldPointers
as a lookup table for quick and easy access to the relevant <tr>
s. Suppose the user clicks on this checkbox:
Now the <tr>
s with rel="othercountry" should be reinserted into the document. That's easy: hiddenFormFieldsPointers['othercounty']
contains the correct <tr>
s:
This script uses the rel
value as a key to hiddenFormFieldsPointers
, and finds an array. It goes through this array and reinserts all the <tr>
s in the page. (We'll get back to finding the insert point in Section 8L.)
Hyperspace and innerHTML
WARNING Browser incompatibilities ahead
The same principles should apply when you work with innerHTML
:
In most browsers, the changed paragraph is reinserted into the document. Unfortunately, in Explorer the original text of the paragraph is lost, and only the newly appended text node is visible. Therefore, these techniques are not usable in combination with innerHTML
.
Is an element in hyperspace?
Occasionally you're not sure if a certain element is in hyperspace or not. You can check its position by seeing if the element has a parentNode
. Generally, elements in hyperspace don't have one, since they're currently "free-floating." On the other hand, every HTML element in the page has a parentNode
.
I use this trick in Sandwich Picker:
This function removes the extra button 'Collect all orders', and as we saw in 8D, removeChild()
requires us to go to the element's parentNode
first. However, if the element is already in hyperspace, it doesn't have a parentNode
, so executing extraButton.parentNode.removeChild
would give an error. That's why the function first checks if extraButton
has a parentNode
.
L: Markers
There is one more aspect of Usable Forms I'd like to present: markers. Unlike all other topics in this chapter, markers are not "native" W3C DOM features, but rather a structuring trick I use in Usable Forms and Sandwich Picker.
First, consider the problem. Usable Forms hides form fields until the user takes a certain action: checks a radio button, selects a new option, whatever. Then the script retrieves the correct
But where in the document? Correct placement is absolutely vital to Usable Forms' user experience. When creating the HTML, we put the form fields in an order logical to the user (we hope), and when form fields return from hyperspace they should once again take up this logical place in the form.
To a lesser degree, Sandwich Picker has the same problem. If a user discards a sandwich, that sandwich should return to the start table at the bottom of the page. Although its exact placement is not as vital as in Usable Forms, it should at least go under the right price header.
The solution to this placement problem is to use markers. The principle is very simple: I create one new, empty, hidden <tr>
for each <tr>
that will be hidden or moved, give it a unique ID, and insert it just before or after the <tr>
I'm going to remove. When the <tr>
has to return to its original position, I insert it before the marker.
Thus I keep the placement information where it belongs: in the HTML structural layer. I could also use a complicated JavaScript object, array, or hash to hold this information, but I find relying on the document structure much safer and more elegant.
Choosing a simple ID-naming scheme is important in using markers:
The script first creates a template marker and stores it in variable marker. Then, while the <tr>
s with a rel
attribute are removed from the document, the script replaces with a marker with a unique ID. This unique ID consists of the value of the rel
attribute plus the index number of the <tr>
in the hiddenFormFieldPointers
array.
Of course it uses the same information when retrieving the <tr>
to get the correct marker. It inserts the <tr>
before its marker:
Because the ID scheme is so simple, it's easy to find the correct marker and insert the returning <tr>
just before it.
Excerpted from ppk on JavaScript by Peter-Paul Koch. Copyright © 2007. Used with permission of Pearson Education, Inc. and New Riders.
[previous]
URL: