Static String

Let’s look at some non-dynamic uses of templating to learn the basics.

Render a String Literal

Let’s start with the simplest form of templating: just a string, no tags, no attributes: For the purposes of illustration, we do the VDOM in one step and the rendering in a second.

from viewdom import html
from viewdom import render


def main() -> str:
    """Main entry point."""
    vdom = html("Hello World")
    result = render(vdom)
    return result

We start by importing the html function from viewdom. This is, essentially, htm.py in action. It takes a “tagged” template string and returns a VDOM. The render function, imported from vdom, does the rendering.

Simple Render

Same thing, but in a <div>: nothing dynamic, just “template” a string of HTML, but done in one step:

def main() -> str:
    """Main entry point."""
    result = render(html("<div>Hello World</div>"))
    return result

We get back a VDOM – an optimized dataclass – with:

  • The name of the “tag” (<div>)

  • The properties passed to that tag (in this case, an empty dict)

  • The children of this tag (in this case, a text node of Hello World)

The second item in the VDOM tuple – the props dictionary – now has a key of ‘class’ with the assigned class name value.

Show the VDOM Itself

Let’s take a look at that VDOM structure. This time, we’ll return the VDOM rather than rendering it to a string:

def main() -> str:
    """Main entry point."""
    result = html('<div class="container">Hello World</div>')
    return result

In our test we see that we got back a VDOM – an optimized dataclass:

    assert main() == VDOMNode(
        tag="div", props={"class": "container"}, children=["Hello World"]
    )

What does it look like?

  • The name of the “tag” (<div>)

  • The properties passed to that tag (in this case, an empty dict)

  • The children of this tag (in this case, a text node of Hello World)

The second item in the VDOM tuple – the props dictionary – now has a key of ‘class’ with the assigned class name value.

Expressions as Attribute Values

We can go one step further with this and use a little bit of “expressions”. Let’s pass in a Python symbol as part of the template, inside curly braces:

def main() -> str:
    """Main entry point."""
    vdom = html('<div class="container{1}">Hello World</div>')
    result = render(vdom)
    return result

Everything is the same, except the value of the class prop now has a Python int included in the string. If it looks like Python f-strings, well, that’s the point. We did an expression inside that prop value, using a Python expression that evaluated to just the number 1.

Shorthand Syntax

As a shorthand, when the entire attribute value is an expression, you can just use curly braces instead of putting in double quotes:

def main() -> str:
    """Main entry point."""
    vdom = html('<div class={"Container1".lower()}>Hello World</div>')
    result = render(vdom)
    return result

The VDOM’s third item contains the “children”.

Child Nodes in VDOM

Let’s look at what more nesting would look like:

def main() -> VDOMNode:
    """Main entry point."""
    vdom = html("<div>Hello <span>World<em>!</em></span></div>")
    return vdom

Over in the test, we see what this looks like:

    assert main() == VDOMNode(
        tag="div",
        props={},
        children=[
            "Hello ",
            VDOMNode(
                tag="span",
                props={},
                children=["World", VDOMNode(tag="em", props={}, children=["!"])],
            ),
        ],
    )

It’s a nested Python datastructure – pretty simple to look at.

Expressing the Doctype

One last point: the HTML doctype is a tricky one to get into the template itself as its syntax is brackety like tags. Instead, define it as a variable using markupsafe.Markup and insert the variable into the template:

def main() -> str:
    """Main entry point."""
    doctype = Markup("<!DOCTYPE html>\n")
    vdom = html("{doctype}<div>Hello World</div>")

    result = render(vdom)
    return result

Reducing Boolean Attribute Values

The renderer also knows to collapse truthy-y values into simplified HTML attributes. Thus, instead of editable="1" you just get the attribute name without a value:

def main() -> str:
    """Main entry point."""
    vdom = html("<div editable={True}>Hello World</div>")
    result = render(vdom)
    return result