HTML is the language of the internet - every web page consists of HTML elements, right? Of course not! If we would use only HTML, pages would look really boring. We always need some CSS and - like most websites today - some Javascript to make pages more colourful and responsive.
Technicall, the browser reads HTML to build the DOM-tree, we call this process "rendering". For every tag it finds in the sources, he adds an appropriate element to the tree, using internal functions. Modern browsers expose these functions through the "HTML-DOM-API. So, instead of letting the browser render HTML, we can mimic this process with Javascript. Let's check out, if this is a way to create modern web apps.
While the HTML-DOM-API is very powerful, it is not very convenient to use. Adding a single paragraph with text is like this:
let el = document.createElement('p')
let tx = document.createTextNode('this is my text')
el.appendChild(tx)
document.body.appendChild(el)
Not very convenient. But as we can use Javascript, this is easy to make things better. First, we get rid of all the complicated stuff with a single, universal function called "make":
let base = document.body
// universal "create"-function for DOM elements
function make(typ, txt = "", style) {
let el = document.createElement(typ)
el.appendChild(document.createTextNode(txt))
if (style) el.setAttribute("style", style)
base.appendChild(el)
return el
}
The use of "make()" ist simple:
- "make" needs at least a string like "h1", "p" or "br" to determine the type of the new DOM element (See document.createElement())
- An optional text content
- An optional style definitions (CSS-inline-style)
Inline-styles are best used, if there is only a single element that needs special styling. Using CSS in that case is somwhat "blown up".
This is only a simplified version of "make" to show the power of the concept See the DML github page for more details on the DML project, that expands the concept. To make the function more flexible, we also use a "base" variable to append new objects.
Initially, "base" is set to the document body. If we set base a div, new objects are created inside the div. With make(), we can define some really simple functions to create DOM elements:
function br() { make("br") }
function div(style) { return make("div", "", style); }
function input(style) { return make("input", "", style); }
function button(txt, style) { return make("button", txt, style) }
Now, things are much easier: we can just create elements on thy fly, using the names that we are used to: br() to create a line break, div() for a <div> and button("Press me") for a button.
All functions create a new dom element and return the object reference. This is really cool, because this lets you access the DOM directly. So, you can access the new element without dealing with "getElementBy...". You can also apply event handler very simple like this:
let myInput = input("width: 300px;");
button("Press me").onclick = (e) =>
myInput.value = "Button pressed";
In just one simple line you can do the following steps:
- Create an new DOM-element
- Store it´s reference in a JS-variable
- Add a textcontent
- Apply an inline CSS style
- Set an event function
Bottomline, this is a very compact way to create Javascript applications with a few lines of code.
As a demonstration, I created a simple calculator-app using only dynamic DOM elements. I hope, you like the really compact code. The html-page has in total about 100 lines, including the HTML-page, CSS styles, DOM creation and calculator code (61 active lines). Detailed analysis of the code is
- 6 lines: constants and variable definitions
- 17 lines: "Framework"
- 12 lines: Create DOM elements / UI
- 26 lines: calculation logic and events
By the way, the desk calculator logic is a bit tricky, as you work with a state dependent logic. AND: you can run the calculator with keyboard or mouse input. This is all covered in the demo app.
There are some really cool lines in the code I like most:
kb.map(key => { button(key,key=="C"?"background-color: #f70":"");
if ((++i % 3) == 0) br(); }) // --> Create keyboard buttons
As you may notice, the onclick-handler ist applied inside the button() function here for convenience. In a real app, you would create a wrapper function that works similar. This code fragment, that you can write as a single line of code, creates as a result the complete numbers panel, applies event handler and cares for the color of the "C"-button.
If you like the approach, please check out the DML-project. There is also a nice tutorial page on efpage.de/dml, that containts lot´s of working examples you can use right inside the page.
The source code of the calculator uses plain vanilla Javascript without HTML or any external libraries, so you can run the code as an HTML-file directly from your desk. So, have fun and check out, if this could be an approach for your next project.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>title</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
div {
display: inline-block;
}
button {
font-size: 18px;
width: 30px;
height: 30px;
border-radius: 50%;
background-color: #fea;
border: 0.5px solid gray;
box-shadow: 3px 3px 5px #ffe inset, -3px -3px 5px silver inset, 3px 3px 4px black;
margin: 5px;
}
button:active {
transform: translate(1px, 1px);
box-shadow: 3px 3px 5px #ffe inset, -3px -3px 5px silver inset, 2px 2px 3px black;
}
</style>
</head>
<body>
<script> "use strict";
const kb = ["1", "2", "3", "4", "5", "6", "7", "8", "9", ".", "0", "C"]
const fn = ["+", "-", "*", "/"]
const framestyle = "border: 1px solid black; padding: 8px; border-radius: 15px; margin: 5px;background-color: #ccc; "+
"box-shadow: 2px 2px 4px white inset, -3px -3px 5px gray inset, 3px 3px 4px gray;"
let base = document.body // input point for new elements
let operand = "", buffer = "" // Calculation buffer
let newCalc = false
let frame, disp // some element variables
// universal "create"-function for DOM elements at the current base object
function make(typ, txt = "", style) {
let el = document.createElement(typ)
el.appendChild(document.createTextNode(txt))
if (style) el.setAttribute("style", style)
base.appendChild(el)
return el
}
// functions to Create elements
function br() { make("br") }
function div(style) { return make("div", "", style); }
function input(style) { let inp = make("input", "", style); inp.setAttribute("readonly", "true"); return inp; }
function button(txt, style) {
let r = make("button", txt, style)
r.onclick = (e) => { press(e.srcElement.textContent) } // add onClick-function to every button
return r
}
// create DOM elements
base = frame = div(framestyle) // Base DIV element
disp = input("font-size: 22px; padding: 4px; width: 150px; margin: 10px; box-shadow: 1px 1px 2px white , 3px 3px 5px silver inset; "); br() // Create input element
base = div()// Create keyboard box
let i = 0;
kb.map(key => { button(key,key=="C"?"background-color: #f70":""); if ((++i % 3) == 0) br() })// --> Create keyboard buttons
base = frame // use frame again
base = div("margin-left: 10px") // Create functions box right of keyboard box
fn.map(key => { button(key); br() }) // --> Create function buttons
base = frame; // return to base frame
br() // newline
let calcButton = button("=", "margin: 15px; border-radius: 10px; width: 140px;") // Calculate button
calcButton.onclick = (e) => calculate("=")
// Install keydown event
document.onkeydown = (e) => press(e.key)
// Calucluation function
function calculate(key) {
let a = Number(buffer)
let b = Number(disp.value)
if (operand == "+") disp.value = a + b
if (operand == "-") disp.value = a - b
if (operand == "*") disp.value = a * b
if (operand == "/") disp.value = a / b
operand = key
newCalc = true;
}
function press(key) { // buttons pressed
if (fn.includes(key)) { calculate(key); return }
// Accept only valid keys
if (kb.includes(key)) {
if (key == "C") {
disp.value = ""; buffer = ""; operand = ""
newCalc = false
return
}
if (newCalc) {
buffer = disp.value; disp.value = "" // Transfer display value to buffer
newCalc = false
}
disp.value += key
}
}
</script>
</body>
</html>
Top comments (2)
Hey Eckehard,
Very nice, that is a step towords 'Core DML' :-)
Regards
For the one who want to test Eckehard's work, you can click here :
Calculator
Just trying to explain the core principles.
Reducing a library to its core is appealing, as it reduces the overhead for page downloads. I just think, we should not go this way. A broad ecosystem of useful tools is very important to speed up your work. So, we should do some tree-shaking instead to get rid of the overhead. I was checking some tools like parcel.js, or even move to typescript or dart, but did not find a realy hassle-free option til now.
For really small tools like the desk calculator, we should not have such a big overhead.