The Importance of our Buffer Objects
The render/unrender/clone functions used in registered tag objects all accept a buffer. Since we are using the same tag objects for both text and HTML, and we absolutely need a buffer to render an HTML template (we'll get to that in a second), a buffer will be both accepted and returned by every render/unrender function. When we render a text-based template, we use dojox.string.Builder to ensure the fastest string concatenation possible. But in the HTML version of the compiler, we have a very special Buffer.
Let's propose a situation that happens all the time when writing a template. It looks something like this:
<h1>Title</h1>
{% if name %}
<div>Hello {{ name }}</div>
{% else %}
<div><a onclick="login">Please log in </a></div>
{% endif %}
< p>Welcome to our wonderful site</ p>
</div>
Now when someone uses this page, we have a situation where, when they first use the application, they are given a link to log in, but once their name is set, they're given a "Hello" message. While we didn't have to write it this way, we've created a situation now where only one of two sibling divs can be shown at a time. For those of you familiar with DOM, you know how complicated it is to insert a node if it's at neither the beginning nor the end of a parent. The HTML buffer allows us to overcome this problem with ease.
As a template is rendered, the setParent function is called on the buffer object, which changes the Node that is considered to be the current parent. Calling concat on the buffer uses this parent to insert the passed node beneath the current parent. Where to place it involves a little bit of magic. As shown in the example above, if we just use appendChild, the second time we render the template, the div in the else tag will end up after the paragraph tag, which is not where it should go.
What we do, then, is check to see if the node we're inserting exists in DOM (by looking at its parentNode). If it doesn't exist in DOM, we check to see if the buffer's currently set parent has any childNodes. If the parent has no child nodes, we can just use appendChild. If there are existing child nodes, we store the child in a cache. When a node arrives that has the same parentNode as the buffer's currently set parent, we take the cache and append it before this node.
Finally, when setParent is called, we get the cache from the old parent, and make sure all the nodes are appended to the end of it, before changing the parent.
The reason this works is that every time we render, every node calls the concat function. While this may seem like a speed hit, it's not only necessary to allow us to fill in those cached nodes, the function does no other work. So in the example above we start out with the opening div set as the current parent, and no value for name. The buffer's concat function is called with the h1, the div in the else block, and finally the p tag. The h1 is added using appendChild, since it's the first node, and the div and p are cached until setParent is called at the end of the render (in code, we actually call setParent twice at the end on the root node so that the cache gets flushed).
But now, if we re-render after giving name a value, the following happens: The h1 is passed to concat and nothing happens since it has a parentNode, and the cache is empty, then the div in the if block gets added to the cache since it has no parentNode, the div in the else block gets removed from the DOM by the unrender function of the if tag, and when the p tag is passed to concat, we see that it has a parentNode, and use insertBefore to place the items in the cache (our div) before the p tag.
Using this method of buffering, we don't need to worry at all about how the DOM is organized. Simply switching the parent and calling concat manages all the complex interactions we would otherwise have to account for.
- Printer-friendly version
- Login or register to post comments
- Unsubscribe post