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 div
s its own background color so it stands out clearly. It looks like this:
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>
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"
}
}
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 div
s. 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"
},
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
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:
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"
}
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>
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
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:
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.
Top comments (2)
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).
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.