Tutorial - Introduction to Backdraft Elements

Source Code

The source code for this example is included in the tree hello-world-static in the tutorial repository.

Static Hello World

We’ll start with the pervasive example that causes the document to contain “hello world”; our example even colors the text. A stunning bit of software engineering to be sure! Here's how to do it in Backdraft:


import {render, e} from "./backdraft.js"

	e("div", {className: "hello-world"},
		e("p", "hello world")

After importing the Backdraft functions render and e, render renders a tree of elements and then appends those rendered elements to the node in the document with the id===”root”. In this case, “render” means to instantiate the tree of DOM nodes implied by

<div class=”hello-world”>
    <p>hello world</p>

The factory function e creates Backdraft Element objects. Each Element instance describes either a DOM node or a class derived from Backdraft’s Component class, and, in either case, can contain zero, one, or many children. The children may be other Element instances or strings. Children that are strings describe a DOM textNode value that should be placed within its parent’s children.

Since Element instances can contain other Element instances as children, an Element instance describes a tree. An Element instance need not contain children, in which case it’s a leaf in a tree; strings are always leafs since they can’t have children. So long as you only use e() to create Element instances, it is impossible to create cycles.

e() has the signature (type, props, …children):

type (required) is either a string that names an HTML tag name or a JavaScript class derived from Backdraft’s Component class.

props (optional) is a JavaScript Object. When type gives an HTML tag, props gives the list of properties and/or attributes to initialize on the DOM node implied by type when that node is instantiated. When type is a Component-derived class, props gives the single constructor argument to provide when an instance of that class is instantiated. In both cases, props may also include additional processing instructions--for example, connecting events--to execute after the element is created.

children (optional) is zero or more Elements, strings, falsey values, or arrays of zero or more Elements, strings, or falsey values. The e() helper function flattens children into a single array and filters out any falsey arguments. So children is ultimately a single, perhaps empty array of Element objects and/or strings.

Here’s a slightly more-complicated example:

e("div", {className:"welcome-message"},
    e("p", "Welcome" + fname + " " + lname),
        customerProps.important && e("p", {className:"important"}, "We're very happy to see you again."),
        e("div", {className:"suggestion-list"},
        e("p", "Please consider buying the following stuff!"),
        e("ul", customerProps.suggestions.map((item)=>e("li", item)))

Notice that JavaScript is used to describe Element trees—not a templating language, and certainly not markup! Indeed, if a templating language were to be used, that language would have to be as powerful as JavaScript in order to have the capabilities of JavaScript. So, why not just use JavaScript and eliminate the complexity cost of yet another templating language. To use markup is equivalent to using a templating language—a very poor templating language.

Of course many templating systems include ways to jump back and forth between the templating language and JavaScript to add power to the templating system. JSX is a good example. But this begs the question: what benefit is gained by using the templating system to begin with? We think none. And what is the cost of using a templating system? We think quite a lot. Since a templating system increases complexity, yet another language must be mastered by the programmer and yet another build step must be employed to process the templates.

Finally, notice that the example above is as declarative as any so-called reactive system. The idea of a reactive system built on top of virtual DOM is completely independent of the idea of declarative composition.

Initializing Property Names and Attributes While Rendering DOM Nodes

When an Element instance that gives an HTML tag name for type is rendered, props gives as a hash of property/attribute names to values. These properties/attributes are set to the given values immediately after the DOM node is created. If the particular DOM node defines a particular name in the hash, then that property is set on the node. Otherwise, the DOM API setAttribute() is used to set an attribute value on the node.

Notice that there is no effort to "improve" or "normalize" the native DOM interface. Any such system must be kept current both with new/changing DOM APIs and the browsers that implement them. And the programmer must learn the new/improved API and understand how it maps to the DOM API it purports to fix. This indirection adds a layer of complexity to any library that is simply not worth the cost in our opinion. When it is necessary to control DOM nodes directly through their properties or attributes, Backdraft gives you direct access to those properties. Zero performance cost; and since there is no intermediate system: zero bugs.

Events on DOM nodes are similarly connected; we'll cover that in the next section.

Running the Example

All browser-hosted applications start by loading an HTML file. Typically there will be exactly one HTML file for each application, and they all look about the same. Here's the version we'll use for our "hello world" example.


<!DOCTYPE html>
<html lang="en">
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <link rel="stylesheet" href="./main.css">
    <title>Backdraft Hello World Example Application</title>
<noscript id="noScript">
    You need to enable JavaScript to run this app.
<div id="root"></div>
<script type="module" src="./src/main.js"></script>

The file accomplishes three things:

  1. It loads a style sheet
  2. It provides a node upon which to hang the application--the node with id="root"
  3. It loads the JavaScript that expresses the application. Notice that we are using ES6 modules.

./src/main.js includes the source code we saw at the beginning of this section.