DEV Community

Cover image for Introduction to DML - part 3:  Use of classes and objects
Eckehard
Eckehard

Posted on

Introduction to DML - part 3: Use of classes and objects

The Document Makeup Library DML claims to enable an new approach on Object Oriented Web Programming. What does this mean an how can you benefit using an object oriented style?

Looking to the core routines of DML, you will find mostly procedural code. The DML core does not provide any object oriented code, but it removes some conceptual elements of the traditional HTML-CSS-JS approach, that prevent you from writing object oriented code.

One of the core concepts of DML is uniformity: Why use different concepts and languages, if you can get the same result using only one language? So, DML introduces a whole set of functions to make the use of HTML superfluous: instead of a tag <h1>Headline</h1> you use a function h1("Headline") that achieves the same result. While you can still use HTML and CSS, DML allows you to do the same things using javascript only.

As you may have seen in part 2 of the introduction, this can be very handy, but til now, this was only a procedural approach. So, why use objects instead?

About the 'O' in OO

Classes are a way to organize your code. Classes can contain functions (called methods, if they are part of a class) and variables or constants. Usually they reflect a logical unit that is well protected against external access, to avoid unwanted effects.

But wait: We are talking about classes, but what are objects?

Classes are like a template: The do not create any working code. To use a class, you need to create an object from this class (called an instance) using the new-operator.

class myClass{
   ...
}
let myObj = new myClass()
Enter fullscreen mode Exit fullscreen mode

This makes things a bit more verbose as you first have to define the class and then create an object myObj = new myClass(...). But it has one big advatage: each object contains it´s individual set of variables making it easy to maintain an object state. Multiple objects can be created without the danger of side effects or interference.

But is´s true: using objects often is a bit more verbose and complex than a functional approach, so there should be good reasons to introduce classes:

  • There is more than one element of this class in your program
  • You need some common functionallity in a group of different objects
  • You begin to loose an overview over your program structure

There are many different reasons to use classes.

Three Different ways to use inheritance

Abstract classes

As you may know, classes can be derived from other classes:

class MyClass extends HTMLElement {
  constructor() {
    super();
    // write element functionality in here
    ...
  }
}
Enter fullscreen mode Exit fullscreen mode

Here, MyClass inherits all functions and properties of HTMLElement, which can be pretty much. Luckily, you do not need to know much about the "parent" class: It just makes yor new element be part of the HTML-ecosystem.

HTMLElement is an abstract class, which means, that is is never directly instantiated. Abstract classes define the general behavoir, but no useful functionality.

Abstract classes often are used to define a "common sense" in an object hierarchy. Assume you have a group of elements that do different things, but need some common features like saving or loading their state. Load() and Save() may be definded in an abstract class and are inherited to all their descendants. So, the whole family will have the same methods, but each element implements the functionality in a different way.

To store the state of a group of elements it is not necessary to know, how the individual element handles the job, we can simply call the method of the parent class for all descendants.

System binding

In many cases classes are derived from other classes to inherit a specific functionality. But there are also cases, where you are not interested in the functions of the parent at all. Derinving a class from HTMLElement is such a case. We do not know anything about HTMLElement and we do not use any function of the class. It´s just, we make our new class part of the HTML-ecosystem.

System binding is used in many cases. React makes your new component be part of the React-ecosystem

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}
Enter fullscreen mode Exit fullscreen mode

Systems like the Windows-GDI used the same technology for years. System binding lets you just connect your class to a larger system, that was created long before you designed your class.

There are cases, when binding to a single system is not enough. Maybe you need your new class be part of different ecosystems. Some OO languages like C++ allow classes to be derived from more than one parent class. This is called "Multiple Inheritance".

Direct inheritance

In some cases, you will find a component, that has most of the features you need. Think of a list element in HTML, that does right what we want. It just cannot deal with arrays. So, maybe you just want to add some useful functions to your class.

Classes allow you, to create a new class, that inherits anything from your base class, but let´s you add your desired functionality. You will end up with a new class, called myList, that can be used like a standard list, but has some new and useful functions.

Writeup

So, we see that there are different ways to use inheritance:

  • Direct inheritance
  • Abstract classes
  • System binding

Each approach has its own target, in many cases all three approaches can be combined to benefit most.

Example

The following example creates a stateful component, that shows a switchable markdown editor similar to the one used in dev.to. The state however is stored in an textarea element. All interactions are encapsulated, so to use the class, you do not need to know how it works. Nevertheless: If necessary, you can access the internal properties as shown in the button functions below. Try out and enjoy!

To be honest: Using a class was not really necessary here, but using setters and getters makes external access more convenient:

      // set value and convert 
      set value(value) {
        this.mdinp.value = value;
        this.convert()
      }
Enter fullscreen mode Exit fullscreen mode

Here, a new value was set to the editor. After that, the input is immediately converted to reflect the change in both views.

Here is the complete example:

<!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://adamvleggett.github.io/drawdown/drawdown.js"></script>
  <script src="https://cdn.jsdelivr.net/gh/efpage/DML/lib/DML.js"></script>
  <style>
  </style>
</head>

<body>
  <script>  "use strict";
    class t_Markdown {
      constructor(val = "") {
        const css_border = _box + _bigPadding + _radius
        const css_size = "width: 300px; height: 300px; margin: 5px;" + _border

        // *** Define input box ***
        this.inpBox = sidiv(h4("This is your input some text"), css_border);
        this.mdinp = textarea("", css_size, "Input some text here...");
        br()
        button("show Output").onclick = () => this.toggle(false);
        unselectBase()

        // *** Define output box ***
        this.outBox = sidiv(h4("This is your output"), css_border)
        this.mdout = div("", css_size + "margin: 15px 5px 10px 5px; overflow: auto;");
        button("show Input").onclick = () => this.toggle(true);
        unselectBase(); br(2);

        this.value = val;
        // define input function
        (this.mdinp.oninput = this.convert)()  // define and run

        // toggle visibility
        this.toggle(true)
      }
      // show other input
      toggle(doInp) {
        const v1 = "inline-block", v2 = "none"
        this.inpBox.style.display = doInp ? v1 : v2
        this.outBox.style.display = doInp ? v2 : v1
      }
      // convert markdown
      convert = () => { this.mdout.innerHTML = markdown(this.mdinp.value) }

      // setter and getter
      // get value from input
      get value() { return this.mdinp.value }

      // set value and convert 
      set value(value) {
        this.mdinp.value = value;
        this.convert()
      }
    }
    // Button after

    let myMarkdown = new t_Markdown("## Hello World\n")
    button("Show both").onclick = () => { myMarkdown.inpBox.style.display = myMarkdown.outBox.style.display = "inline-block" }
    button("Clear").onclick = () => { myMarkdown.value = "" }
    button("Set brown Fox").onclick = () => {
      for (let i = 0; i < 10; i++)
        myMarkdown.value += "* The quick brown fox jumps...\n"
    }
  </script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

And this is the result:

Discussion (0)