DEV Community

Cover image for Beautiful HTML-free desk-calculator app, built with just 61 lines of Javascript, framework included...
Eckehard
Eckehard

Posted on

Beautiful HTML-free desk-calculator app, built with just 61 lines of Javascript, framework included...

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)
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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) }
Enter fullscreen mode Exit fullscreen mode

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";
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

Image description

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>
Enter fullscreen mode Exit fullscreen mode

Discussion (2)

Collapse
artydev profile image
artydev • Edited on

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

Collapse
efpage profile image
Eckehard Author

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.