DEV Community

Cover image for Webson: a new DOM markup
Graham Trott
Graham Trott

Posted on • Updated on

Webson: a new DOM markup

Photo by Hal Gatewood on Unsplash

Synopsis

This article introduces Webson, an easy-to-use syntax with its own run-time rendering engine, that turns JSON into DOM markup and adds features way beyond those of HTML.

Introduction

A web page is the visual representation of a Document Object Model, or DOM, the data structure maintained internally by all browsers. Traditionally, the DOM is constructed by the browser from HTML scripts, but as pages get bigger and more complex HTML becomes ever more cumbersome. In recent years it has become increasingly common to create the DOM using JavaScript, with no HTML ever being seen, but while this suits programmers well it requires a different skill set from that needed to build pages the traditional way.

Today's Web pages may have hundreds or thousands of elements, all carefully positioned to create the desired result. There's no way to hide this complexity, whether it's done with HTML/CSS, JavaScript or some kind of no-code visual builder. In the end it's a human brain that's doing the real work of translating the customer's requirements - a mental picture - into something the browser can use to create the DOM.

HTML is not program code; it's a form of "markup", the ultimate expression of which came in the form of XML, able to represent not only visual structures but a wide range of other data too. Unfortunately, XML is wordy and hard to read and is not greatly loved. In 2001, Douglas Crockford invented (he would say "discovered") a simpler syntax for representing data structures, as a means of transferring data in and out of JavaScript programs in the form of plain text. The syntax is JavaScript Object Notation, or JSON, and in the past 2 decades it has widely supplanted XML. Virtually every programming language has the ability to read and write JSON and it's now the most common way to transfer data across the Web.

Since HTML shares many of the disadvantages of XML, the question might be asked, Can JSON also replace HTML?. If the answer is "yes", a couple of supplementary questions might be

Can we have user-defined variables and reusable blocks?
How about conditional structures?

which would greatly reduce the amount of markup needed to describe a complex web page, where items are commonly repeated with only minor differences.

Webson

Webson is a markup syntax that allows JSON to be used to describe a DOM, together with a JavaScript rendering engine that can be embedded in any web page to process scripts at runtime. The system is immediately usable by HTML/CSS coders and no JavaScript experience is required. It's aimed at simplifying the design and implementation of highly complex layouts, where precise positioning of large numbers of elements is hard to achieve manually, and it achieves this with JSON markup rather than with code.

Getting started

Let's start with a simple example; a layout commonly found in online magazines and social media. At the top there's a full-width header; under this a central panel with 2 sidebars and at the bottom a footer. As this is only an example I've given each of the component divs its own background color so it stands out clearly. It looks like this:

Alt Text

Here's the HTML that will create this screen. It uses inline styles to avoid the need to present a separate CSS file:

    <div style="width:50%;height:50%;display:flex;flex-direction:column">
        <div id="top" style="height:20%;background:cyan">
        </div>
        <div style="width:100%;flex:1;display:flex;flex-direction:row">
            <div id="left" style="display:inline-block;width:25%;height:100%;background:green">
            </div>
            <div id="center" style="display:inline-block;height:100%;background:yellow;flex:1">
            </div>
            <div id="right" style="display:inline-block;width:15%;height:100%;background:blue">
            </div>
        </div>
        <div id="bottom" style="height:10%;background:magenta">
        </div>
    </div>
Enter fullscreen mode Exit fullscreen mode

This is a total of 655 characters. The corresponding Webson script to create the same screen is 1172 characters, nearly twice as many, and occupies 61 lines rather than 14, but before you dismiss Webson as being too wordy I must say in its defence that this is a very basic example which doesn't make use of any of the more advanced features of the system. More complex scripts tend to be far smaller than their HTML equivalents, as we'll see later.

The reason for the extra size in this example is partly that every item is named and partly because JSON itself is fairly bulky (lots of double-quotes), while the increase in lines is mainly because it's a lot more spaced out. This helps readability; high information density makes code hard to read at a glance as the eye has to pick out specific details from a dense surrounding mass. With Webson, the CSS properties are separated out, one per line, rather than all being crammed onto a single line. This can of course be done with HTML too, but because there's no agreed way to present it the result is usually an unstructured mess, so most coders just put everything on the same line.

Here's the script. It just uses a basic feature set; I'll get on to some of the advanced features later.

{
    "width": "50%",
    "height": "50%",
    "display": "flex",
    "flex-direction": "column",
    "#": ["$Top", "$Middle", "$Bottom"],

    "$Top": {
        "#element": "div",
        "height": "20%",
        "background": "cyan"
    },

    "$Middle": {
        "#element": "div",
        "width": "100%",
        "flex": 1,
        "display": "flex",
        "flex-direction": "row",
        "#": ["$Left", "$Center", "$Right"]
    },

    "$Bottom": {
        "#element": "div",
        "height": "10%",
        "background": "magenta"
    },

    "$Left": {
        "#element": "div",
        "display": "inline-block",
        "width": "25%",
        "height": "100%",
        "background": "green"
    },

    "$Center": {
        "#element": "div",
        "display": "inline-block",
        "flex": 1,
        "height": "100%",
        "background": "yellow"
    },

    "$Right": {
        "#element": "div",
        "display": "inline-block",
        "width": "15%",
        "height": "100%",
        "background": "blue"
    }
}
Enter fullscreen mode Exit fullscreen mode

How it works

Running through the script, you will see that every DOM element has its own named block of JSON data. User-defined names all start with $. There are also directives and other system items; the names of these start with #. Everything else in the script above is a CSS style to be applied to the current element.

In the above, most of the blocks include a #element directive, which names the DOM element type. If this is missing, everything in the block applies to the current element (the one defined in the block that calls this one). Here the only block that lacks an #element is the very first one, so its styles all apply to the parent container that was created outside Webson and passed to its renderer as a parameter.

The symbol # by itself signals that child elements are to be added. This directive takes either a single name or an array of names.

Attributes

The structure we've built here isn't much use unless we can add further items to the various divs. Some of this can be done with further Webson code but ultimately you'll either use an onClick="<something>" callout or a JavaScript function that populates or interacts with the DOM. For the latter to work, elements must have unique ids to allow JavaScript to find them. Here's the $Left block again, with an id and a couple of other additions:

    "$Left": {
        "#debug": 2,
        "#doc": "The left-hand sidebar",
        "#element": "div",
        "@id": "left",
        "display": "inline-block",
        "width": "25%",
        "height": "100%",
        "background": "green"
    },
Enter fullscreen mode Exit fullscreen mode

Here we have another new symbol, @, which (appropriately) signifies an attribute. Various HTML elements require special attributes such as @id, @class, @type, @href, @src, etc. In each case the name is that of the HTML attribute prefixed by @.

Another feature above reveals a built-in debugging capability. When hand-building HTML, errors are common, often resulting in strange layouts that are not at all as intended. Webson allows you to specify 3 different debug levels:

"#debug": 0 - no debugging output
"#debug": 1 - Show all #doc properties
"#debug": 2 - Show every item

which enables you to see what is happening. The output for the above is

Build $Left
The left-hand sidebar
#element: div
Attribute id: "left" -> left
Style display: "inline-block" -> inline-block
Style width: "25%" -> 25%
Style height: "100%" -> 100%
Style background: "green" -> green
Enter fullscreen mode Exit fullscreen mode

This is a simple example where all values are constants. The values appear to be repeated but this will not always be the case. In more complex scripts you will often see the results of expressions being evaluated.

#doc items can be either single lines of text or arrays of lines. They are just there for the benefit of the programmer and have no effect on the screen being constructed.

A #debug directive affects its own block and those below it (defined using #).

Nested bocks

Webson implements nesting, whereby items declared at one level apply to all those in lower (contained) levels. Changing a value at one level only affects those at that level and beneath it; those above are unaffected.

For example, let's suppose the two sidebars share a common feature; they each have an inner div and padding to produce a border. Here's what it should look like:

Alt Text

To achieve this we can rewrite the last part of the script as follows:

    "$Left": {
        "#doc": "The left column",
        "$ID": "left",
        "$Width": "25%",
        "$Color": "green",
        "#": "$LRPanel"
    },

    "$Right": {
        "#doc": "The right column",
        "$ID": "right",
        "$Width": "15%",
        "$Color": "blue",
        "#": "$LRPanel"
    },

    "$LRPanel": {
        "#element": "div",
        "display": "inline-block",
        "width": "calc($Width - 2em)",
        "height": "calc(100% - 2em)",
        "padding": "1em",
        "#": "$LRSubPanel"
    },

    "$LRSubPanel": {
        "#element": "div",
        "@id": "$ID",
        "width": "100%",
        "height": "100%",
        "background": "$Color"
    }
Enter fullscreen mode Exit fullscreen mode

Here I've left out the block for $Center as it's unchanged. Both $Left and $Right now no longer declare their own #element; instead they set up user-defined variables $ID, $Width and $Color and invoke $LRPanel to construct the element. I suggest using an initial capital letter for each user-defined name, to make them easier to spot, but it's not mandatory. Any variable declared or modified at a given level in the structure will be visible at all points beneath that one, but changes do not propagate upwards.

$LRPanel creates a div, applies padding to it and creates an inner div called $LRSubPanel. Note how the $Color variable is passed down and used here, resulting in a colored panel with a white border. Note also the use of calc() in $LRPanel to allow for the padding, which in a conformant browser adds to the width or height of the element.

How to run it

To view this demo on a PC, place the following HTML file on your server:

<html>
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Webson demo</title>
    <script type='text/javascript' src='resources/plugins/webson.js'></script>
  </head>

  <body>
    <div id="main" style="width:640px"></div>
    <script>
        window.onload = function() {
            render(`resources/json/simple.json`);

            async function render(file) {
                const response = await fetch(file);
                const script = await response.text();
                Webson.render(document.getElementById("main"), `keyboard`, script);
            }
        }
    </script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

For mobile, the width can be set to `100%`. The JSON script is assumed to be in a folder on your server at

(your domain)/resources/json/simple.json
Enter fullscreen mode Exit fullscreen mode

The code above uses the relatively-new standard function fetch() to get the named script from a file on the server. It then calls render() in the Webson package (webson.js in the repository) to create the DOM tree that corresponds to the JSON script.

From here on in

This has been a necessarily brief introduction to Webson, since to cover every feature in detail would result in a very lengthy article. A more in-depth treatment can be found in the Webson repository. The example used is the following on-screen virtual keyboard:
Image description
The repository documentation starts with the page you are reading now, then goes on to describe how to make the virtual keyboard depicted above. It then shows how to make the keyboard respond to its Shift and ?123 keys being tapped, to change the key legends appropriately. This is all done with simple JSON commands and no conventional coding at all. You can see and test-drive the virtual keyboard with the above functionality here.

Comments are welcome, as are suggestions on how to improve Webson.

Discussion (2)

Collapse
khalyomede profile image
Khalyomede

Interesting concept, like the fact you pushed further by going on a such complex example as a virtual keyboard! I think it really shows the potential.

Some thoughts coming to my mind: this could help JS framework that provide reactive DOM to easily express elements using a simple syntax. I tried once to play and create a home made reactive app, and it's a nightmare if you don't organize your in memory representation of the DOM elements, so maybe Webson can play a key role in it since reactive frameworks needs to keep track of changes (e.g. performing DOM diffing).

Collapse
gtanyware profile image
Graham Trott Author

While walking the dog this morning (the time I get most of my best ideas) I was thinking about what you wrote. What you're suggesting is to shift the job of laying out a screen from code to markup - a low-code way of thinking. I think Webson would be quite good for that because of its modular block structure.

One thought led to another. My next project is likely to be a kind of IDE that allows a Webson script to be built in one panel (lots of menus and clickable choices) and the result instantly viewed in another. Again, the block structure makes this feasible as it's easier to avoid breaking the entire structure with a simple change. I'll do the acreen layout in Webson, of course, and the logic in EasyCoder, which has its own Webson plugin. This keeps my exposure to raw JavaScript to a minimum; a good thing since that's where most of the mistakes occur.

My best guess is this will take a few months, but it could be less. I'm retired so all this is a part-time hobby, which is far better than working to someone else's timescales.