RetroV Change: Flattening Node Lists

Page created: 2025-03-24

Back to RetroV.

One thing I never managed to simplify in my original slap-dash creation of this library was my handling of nested node (tag) lists.

A node tree is a series of (possibly nested) lists of HTML tags you would like rendered.

Lists can either represent tags, or they can simply be containers. Here are examples of each:

Lists as tags - (single and/or multiple and/or nested)

Here’s a single paragraph (<p>) tag with a text node that says "Hello world!":

["p", "Hello world!"]

Here’s a nested bold (<b>) tag along with some text nodes:

["p", "Hello", ["b", "cool"], "world!"]

Here’s a bunch of bold tags in the paragraph tag:

["p", ["b", "Hello"], ["b", "cool"], ["b", "world!"]]

Here are two paragraph nodes in a div tag:

["div",
    ["p", "Hello"]
    ["p", "world!"]
]

All of these are flat lists of one or more nodes. All of the lists (arrays) represent a tag (with a string such as "p", "b", or "div" to indicate the tag type).

Lists as…​just lists

Here are two paragraph tags in a list:

[
    ["p", "Hello"]
    ["p", "world!"]
]

Note how the top level list is not a node (tag) of it’s own.

Here’s a bunch of bold tags as a series of nested lists in a paragraph tag:

["p", [["b", "Hello"], [["b", "cool"], ["b", "world!"]]]]

The same, formatted to show the nesting:

["p",
    [
        ["b", "Hello"],
        [
            ["b", "cool"],
            ["b", "world!"]
        ]
    ]
]

These lists have lists (arrays) that are not tags. They do not have a tag type as a string ("p", "b", etc.) in the first position. They are simply containers.

The problem and solution

The problem with lists that are just lists is that they have no representation in the DOM.

Every list ([…​]) here is a tag and will become a DOM node in the HTML page:

["p", ["b", "Hello"], ["b", "cool"], ["b", "world!"]]

But some of these lists just contain other lists and have no purpose in the final HTML:

["p", [["b", "Hello"], [["b", "cool"], ["b", "world!"]]]]

These two examples render identically, but one of them has a perfect 1:1 relationship with the final HTML in the DOM.

Keeping the non-tag lists (second example) intact in the virtual-DOM representation made my diffing algorithm was weird and buggy.

So now I’m flattening these non-node (non-tag) lists on input, so the second example becomes the first example with no extra nesting.

And empty arrays get flattened right out of existence.

I was able to delete a fair amount of crufty code. But the most striking example of the improvement was the main rendering call:

Before:

-                 render(dom_container, old, new, 0);

After:

+        render_children(dom_container, old, new);

(The parameters have been renamed in this diff for the purpose of explanation.)

Before, I was rendering the main input node tree as "siblings" inside the DOM container starting with the 0th item. Which is weird.

After, I’m just rendering the children into DOM container as you would expect. No more weirdness.

I think this change improves the understandability of the RetroV source quite a bit.

Visual debugging

Before I settled on flattening the lists, I had a heck of a time figuring out what was going on in my failing tests in test.html (run it live).

So I ended up adding a visual debugging feature.

Now I can just add a call to debug() before any test and it will display the virtual DOM data (JSON) and the actual rendered HTML at each step in the test like so:

Screenshot of the test suite page with visual debugging turned on for a test.

Hopefully I don’t have to use this too much, but it’s nice to have.