DEV Community

Cover image for Introduction to DML - part 2: using functional templating
Eckehard
Eckehard

Posted on • Updated on

Introduction to DML - part 2: using functional templating

DML features a new approach to create dynamic web content using Javascript only. This enables some new design patterns. This post will explain the use of Functional Templating.

Templates in other frameworks are design patterns generated from short HTML snippets. They may contain of one or more HTML-elements that are arranged during runtime using external parameters. As DML generates all content dynamically, templates can be provided by the use of functions or classes. This is called "Functional Templating". As a demonstration, a simple calculator application is build using template-functions. Each step is explained in detail.

Simple calculator app

calculator
You will need an empty HTML-page and two external libraries:

<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
<script src="https://efpage.de/DML/DML_homepage/lib/DML-min.js"></script>
Enter fullscreen mode Exit fullscreen mode

A functional template may be as simple as follows:

// round button
function RB(s, color) {
  const r = 30;
  return button(s, "width:" + px(r) + "; height:" + px(r) + "; border-radius: 50%; background-color: " + color + ";  margin: 3px;")
}
Enter fullscreen mode Exit fullscreen mode

This creates a round button with a diameter of 30 pixels. This is just a button, not a real template. But it may serve as a demonstrations, that different properties (and elements) can be combined using functions, that act as a template.

For our application, we need two types of buttons

  • yellow buttons for the numbers
  • gray buttons for the operators

So, we add two more functions that use RB() and add some more properties:

// yellow button for numbers
function number(s) {
  let bt = RB(s, "yellow")
  bt.onmouseover = () => bt.style.backgroundColor = "orange"
  bt.onmouseout = () => bt.style.backgroundColor = "yellow"
  return bt
}

// gray button for operators
function operator(s) {
  let bt = RB(s, "silver")
  bt.onmouseover = () => bt.style.backgroundColor = "#6060FF "
  bt.onmouseout = () => bt.style.backgroundColor = "silver"
  br() // create line break
  return bt
}
Enter fullscreen mode Exit fullscreen mode

Here we apply different colors to the buttons and some event-functions, that create a hover effect. We could have used CSS to create the same effect, which in some cases might be advised. But CSS is limited to styling only, while the functions we apply in our template may also define some operational features (check a value, store it to a database or whatever...). Template functions can be very powerful and apply also some program logic to the DOM elements directly.

The operator function also creates a line break br() after each button.

Both functions return a reference to the newly created button to give access to the newly generated button. Now we want to create our keyboard. In DML you can let a program do this work. First, we create our number block:

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, ".", 0]
// build number block  
sidiv("", "margin-right: 10px;") // ---> create a box 
i = 0
for (n of numbers) {
  number(String(n)).onclick = numberClick
  if (++i % 3 == 0) br() // line break after 3rd column
}
number("C").onclick = () => { op = "="; display.textContent = ""; buffer = "0" }
unselectBase() // <--- finish box
Enter fullscreen mode Exit fullscreen mode

The buttons are generated within a div. In DML, sidiv() is a combined command, that creades an inline div and sets the insert point into this box. The number buttons are created in a loop from an array "numbers". After every third button we insert a line break. An onclick-event is applied during creation to every button (onclick -> numberClick()) to make the buttons work.

As the C-button (clear) needs a different event, it was not created from the arry, but manually in advance.

The same procedure is used to create the operator buttons. As a line break br() is necessary after each operator button, it was created directly in the template function operator(s)

const operators = ["+", "-", "*", "/", "="]
// Right box for operators
sidiv() // ---> create a box
for (o of operators)
  operator(o).onclick = operatorClick;
unselectBase() // <--- finish box

Enter fullscreen mode Exit fullscreen mode

Finish! We have created 2 div´s with 17 functional buttons that build the main part of our Interface. The full application has some additional code to evaluate the operations, which is a bit tricky if you want to mimic the behavoir of a standard calculator.

The full code

This was a short demonstration of the use of "Functional Templates" in DML. The full code is given below:

<!DOCTYPE html>
<html lang="de">

<head>
  <meta charset="utf-8">
  <title>title</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
  <script src="https://efpage.de/DML/DML_homepage/lib/DML-min.js"></script>
</head>

<body>
  <script>  "use strict";
    let i, n, o, v, history, display, buffer = "0", op = "=";
    const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, ".", 0]
    const operators = ["+", "-", "*", "/", "="]

    // round button
    function RB(s, color = "yellow") {
      const r = 30;
      return button(s, "width:" + px(r) + "; height:" + px(r) + "; border-radius: 50%; background-color: " + color + ";  margin: 3px;")
    }

    // yellow button for numbers
    function number(s) {
      let bt = RB(s, "yellow")
      bt.onmouseover = () => bt.style.backgroundColor = "orange"
      bt.onmouseout = () => bt.style.backgroundColor = "yellow"
      return bt
    }

    // gray button for operators
    function operator(s) {
      let bt = RB(s, "silver")
      bt.onmouseover = () => bt.style.backgroundColor = "#6060FF "
      bt.onmouseout = () => bt.style.backgroundColor = "silver"
      br()
      return bt
    }

    function bval() { return Number(buffer) }
    function dval() { return Number(display.textContent) }

    // Click on number
    function numberClick(e) {
      if (op == "=") {
        display.textContent = ""
        op = ""
      }
      if (op != "")
        if (buffer == "0") {
          buffer = display.textContent
          display.textContent = ""
        }
      display.textContent += e.srcElement.textContent
    }

    // evaluate last function and set as display value
    function evaluate() {
      switch (op) {
        case "+": v = bval() + dval(); break;
        case "-": v = bval() - dval(); break;
        case "*": v = bval() * dval(); break;
        case "/": v = bval() / dval(); break;
        default: v = Number(display.textContent);
      }
      return String(v)
    }

    // evaluate the operator click
    function operatorClick(e) {
      let flg = (op != "=") && (buffer != 0)
      let o = bval() + op + dval() + "="
      display.textContent = evaluate() // evaluate the last operator to display
      buffer = "0"                     // clear buffer
      o += display.textContent
      if (flg) {
        history.value += "\n" + o
        history.scrollTop = history.scrollHeight;
      }
      op = e.srcElement.textContent    // set new operator
    }
    /****************************************************************************************
      Build the panels
    ****************************************************************************************/

    // build Main box
    sidiv("", _bigPadding + _radius + _box)

    // left subbox for numbers 
    sidiv("Calculator", "margin-right: 10px;"); br()
    history = textarea("", { readonly: true, style: "resize: none; font-size: 60%; height: 50px;" })
    display = div("", _border + _right + "margin-bottom: 15px; height: 22px;") // result display

// build number block
i = 0
for (n of numbers) {
  number(String(n)).onclick = numberClick
  if (++i % 3 == 0) br()
}
number("C").onclick = () => { op = "="; display.textContent = ""; buffer = "0" }
unselectBase()

// Right box for operators
sidiv()
for (o of operators)
  operator(o).onclick = operatorClick;
unselectBase(2)

  </script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Discussion (4)

Collapse
artydev profile image
artydev

Hy Eckehard,

Is the a reason you don't use template strings lke this : ?

// round button
    function RB(s, color = "yellow") {
      const r = 30;
      //return button(s, "width:" + px(r) + "; height:" + px(r) + "; border-radius: 50%; background-color: " + color + ";  margin: 3px;")
      return button(s,  {style: `width:px(${r});height:px(${r});border-radius: 50%; background-color:${color};margin: 3px;`})
    }
  RB("2", "red")
Enter fullscreen mode Exit fullscreen mode

Regards

Collapse
efpage profile image
Eckehard Author

I just dont like the mixed syntax, but there is nothing wrong with. I assume it should be width:${r}px;...

Collapse
artydev profile image
artydev • Edited on

Ok
I am not fan of '+' for concatenate strings :-)
I understand your view, although I don't like '$' in strings, I like the fact I can make multilines easier

Collapse
artydev profile image
artydev

Great , thank you Eckehard