A Bit about Event Delegation in Pure JS
Tags: webdev, javascript, programming, development
Published: 2020-08-11 04:50:46

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/b80oejb3wvfvx18r9pul.png)

In general, I am a supporter of a detailed study of a particular pattern or innovation, since this allows not only to see what implementation difficulties may developers meet but also to test myself in a certain role as the creator of this pattern. Of course, you need to try in isolation from all the frameworks, since using pure JS, you go down to the lowest level of abstraction.

This may not be a very good comparison, but I think that frameworks in JS are high level (since they hide a lot of problems and give for each a solution, sometimes suffered by the authors. You understand, here’s both the implementation method and the moodiness and peculiarity of browsers ). But pure JS is assembler in the web world. Here you are faced with all the problems in the forehead. And only your brain, skills, and StackOverflow can help you 😂.

So, the time has come to talk about pure JS and the simplest implementation of the ``Event Delegation`` pattern.

##Idea

Before starting the implementation of the pattern, I would like to fantasize and try something interesting that would show how to use some sorts of DOM API methods, which can greatly help in simplifying your task. And after a little thought, I decided that it was necessary to show the possibilities of templating and to create such a simple analogue of React using the Event Delegation pattern. This is what we will do next!

##Templating

What already exists from ready-made and simple solution in the browser DOM for our mini React implementation?

Meet the tag

>``<template>`` — this is a mechanism for ``deferred`` rendering of client content that is not displayed during page loading, but can be initialized using JavaScript.
>
>A template can be thought of as a piece of content saved for later use in a document. Although the parser processes the contents of the ``<template>`` element at the time the page loads, it does this only to make sure the content is valid; the content itself is not displayed.
>
>https://developer.mozilla.org/ru/docs/Web/HTML/Element/template

Fine! This is what you need!

##Component Requirements

Now let’s decide what our components will be able to do?

* Be able to process attached event handlers through attributes and respond to them using the ``Event Delegation`` principle

* Be able to use data from the first level properties of the data object. That is the usage of an attached property of a type ``property.subproperty`` not available. There will be the only opportunity to specify ``property`` instead of ``property.subproperty``.

* In the template, the properties of the data object and the names of the event handlers should be enclosed in curly brackets, for example, like this ``{property}`` or ``{handlerOfSomeEvent}``

* If during the ``DOM`` parsing, components that do not belong to the standard set from the HTML ``DOM`` are found, then you need to try to find the registered component and replace the non-standard ``DOM`` node with the component template. Just like in ``React``.

* The component should be declared as follows:
```<template name="TodoList">
  <ol id="todo_list" class="todo__list"></ol>
</template>```

where the **name** property will be used for the component name. And it will use the contents of the ``<template>`` tag as a component markup.

* To declare a component in the DOM, use a view construct of the form ``<TodoList></TodoList>``. 

##What will we test our implementation on?

Then a simple thought occurred to me. Implement a simple **Todo List**.

###Functional:

* In fact, you can add text tasks to the list, completing the entry with the ``Enter`` key or by clicking the ``Add`` button. In this case, the text field will be cleared, and the ``Add`` button will be disabled.

* If the task is completed, you press the button with ``x`` at the corresponding element in the list and this task will be permanently deleted from the list.

##The technology of our mini React

* **The first phase** is going through the ``DOM`` in search of ``<template>`` and custom elements (``HTML tags``). Component registration also enters into this phase — this is the key point where we will apply the ``Event Delegation``.

* **Registration of global event handlers** on the document element + **Associating an event processor** with each of the global handlers.

* The second phase is the replacement of custom tags with registered components from the ``<template>`` tags.

And now below, we will take a look at all these phases in turn.

##Phase One (walk through the DOM and search for component declarations)

Here you go through the DOM elements of an HTML document. In the HTML DOM, we already have the necessary tool that allows us to easily go through all the elements that interest us.

And that tool is [``document.createTreeWalker``] (https://developer.mozilla.org/en-US/docs/Web/API/Document/createTreeWalker). Bearded 🧔🧔🏾 guys wrote this API method for promenading over HTML elements. In this case, you can specify the filtering option for nodes of HTML elements. For our case, we will use ``NodeFilter.SHOW_ELEMENT``, we will not need text nodes, since we can walk through the text nodes ourselves, inside a specific element.


The code for this phase is concentrated here:


![Phase 1 Code](https://dev-to-uploads.s3.amazonaws.com/i/0o4ohyh59mw8aakoouza.png)



To begin with, as you can see, we create an iterator object over the ``DOM`` elements. And as the root element, from which the journey along with the ``DOM`` begins, we specify ``document.body``.

Then we specify the filter parameter ``NodeFilter.SHOW_ELEMENT``. After the filtering parameter, we specify the ``acceptNode`` handler, in which if you wish, you can add additional filtering conditions for ``DOM`` elements. This handler should return ``NodeFilter.FILTER_ACCEPT`` for the necessary nodes, and for skipping ``NodeFilter.FILTER_REJECT``. In our case, for example, we always return ``NodeFilter.FILTER_ACCEPT``, since the ``NodeFilter.SHOW_ELEMENT`` flag suits us.

After creating an iterator over the ``DOM``, using the ``nextNode()`` method and while loop.

Inside the loop, we collect ``non-standard DOM`` elements. To do this, check the name of the constructor of the ``DOM`` node and for non-standard components, the name of the constructor will correspond to ``HTMLUnknownElement``. The found elements are written to the array for subsequent processing.

The second step, we check the name of the node for compliance with ``TEMPLATE``. This is an announcement of our elements. And each node found, we send to the ``registerTemplate`` procedure.

Next, we will see how the component registration phase works.

##First phase (registration of component templates)

Here is the registration procedure:

![Item Template Registration Procedure
](https://dev-to-uploads.s3.amazonaws.com/i/4s73o5avp6l6guxdtlal.png)


1. First, we copy the contents of the template node using ``node.content.cloneNode(true)``. You can read more about cloning [here] (https://developer.mozilla.org/en-US/docs/Web/API/Node/cloneNode). Cloning is necessary in order to leave the original template unchanged.

2. As a next step, we must go through the contents of the template, identify the text variables to be inserted, and also get event handlers.

3. Representation of an element in our collection will look like this:
  ```{
  element,
  handlers: {},
  textVars: {}
};```

* ``element`` — this is the component template node, as is, without processing.
* ``handlers`` — this is a collection of ``key — function`` where all handlers for a particular element will be stored.
* ``textVars`` — this is a collection of ``index — value``. Here the position is saved in the DOM with the contents of the current node (naturally, without keeping in attention the ability of position changing, imagine that in our case the text nodes will not change positions and this is done for simplification).


4. Inside the loop through the elements of the template, the unique identifier of the template element is generated. It consists of the following parts:

```const indexedTemplateName = `${TemplateName}:${id}:${node.nodeName}`;```


From the ``template name`` + ``index number`` + ``node name``. This is quite enough for us to identify the element.

* Adding of ``data-template`` attribute, which contains a unique identifier.
* We go through the collection of attributes of the current node and identify the appropriate event handlers. They start with ``on`` prefix. And also we are forced to immediately remove the event attribute of the current node so that when inserted into the DOM, we don’t confuse the browser.
* We go through the first level of nesting of child elements and fill in ``textVars`` for the current element.

##Registering global event handlers and event processor binding

This is the backbone of ``Event Delegation``, the core of event processing on different elements.

The event processor looks like this:

![Event processor
](https://dev-to-uploads.s3.amazonaws.com/i/503nt43yo1p3hw8ejb5g.png)

And also, for it to work, you need to associate it with events on the ``document`` element.

![Associating an event handler with global events of interest to us
](https://dev-to-uploads.s3.amazonaws.com/i/1x9cjcicyd98w67mni7l.png)

Thus, we can now respond to three necessary events.


How will we distinguish for which ``component`` which ``handler`` to call? And it’s very simple, earlier we marked each element with a special ``data-template attribute``, in which we put the necessary information. Thus, dividing the identifier string by the symbol ``:``, we can:

* Find a registered component

* Get the event handler by the full initial value of the ``data-template`` + ``e.type`` attribute.

* Check if it matches our pattern ``{nameOfEventHandler}``

* If everything is fine, select the name ``nameOfEventHandler`` from ``{nameOfEventHandler}`` and find the handler function in the ``globalEventHandlers`` collection using this key

* And finally, execute the handler, passing it the event object.

##Second phase — rendering custom DOM tags

This part of the work phase of our simple version of ``React`` consists of two methods:

* **phaseTwo**

* **applyTemplate**

``phaseTwo`` — passes through the collection of custom elements detected in the previous phase and using the [``Node.replaceChild``] (https://developer.mozilla.org/en-US/docs/Web/API/Node/replaceChild) method replaces the node with the component template.

![Phase 2 Procedure](https://dev-to-uploads.s3.amazonaws.com/i/mnqkcalj15pt1ahvskf6.png)

``applyTemplate`` — inserts data from the passed options object into the element template ``HTML`` in accordance with ``textVars`` and returns the processed ``HTML`` node, ready for insertion into the ``DOM``.


![The function of filling the template with data](https://dev-to-uploads.s3.amazonaws.com/i/olnuiqifmd16hx330028.png)

##Entry point

This is a piece of code that will breathe life into our simple application that uses ``Event Delegation``.



![The entry point to our simple React](https://dev-to-uploads.s3.amazonaws.com/i/j465923d7z6bbsa8g9ba.png)


After the ``runApp`` procedure is launched, the phased execution of ``Phase 1`` and then ``Phase 2`` will immediately begin. As well as the availability setting for the Add button, given the state of the text field.

##Results Analysis

First, let’s see how our HTML **“Before”** and **“After”** changes.

Here is the original HTML:

![HTML source before processing with our simple React](https://dev-to-uploads.s3.amazonaws.com/i/h5r70cogj607e4cwm5i8.png)


![And this is what we get as a result of the work of our simple React](https://dev-to-uploads.s3.amazonaws.com/i/n2qinspbn8gq8gqsqfel.png)

* As you can see, inside the ``div#app`` instead of custom elements, elements from specific templates are displayed. You can also notice the abundance of ``data-template`` with identifiers for each template node.

* You may also notice the absence of ``onclick`` and other events outside the ``<template>`` tags.

* Inside the ``<template>`` tags, all ``HTML`` nodes remain as they are. So you can definitely check which events with which handlers you specified.


Let’s check now if we really use ``Event Delegation``.


##First, let’s look at the Add button

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/fb846r04ldz65vq63d07.png)


* We observe the presence of the ``processEvent`` handler for the click event at the document level, there are no local handlers, there is no element level handlers.

##Now check the text box

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/mrdiykscaijf6rrgfvds.png)

And for the text field, the input event, we also observe the presence of a global ``processEvent`` handler.

##How things are with the buttons to remove todo item from the list


Add a few todos and then inspect event handlers:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/hj512spa3ed3ggc705pu.png)

And again we see from the long list, the absence of local event handlers. Only global!


The application perfectly displays todo and allows you to remove the todos from the list!

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/lijzzoutdks9kyoiblrl.png)


##And where can I see in action?


    // Import stylesheets
    import './style.css';

    function createTodo(text) {
      return applyTemplate('todoitem', { text })
    }

    function removeTodo(todoEl) {
      window.todo_list.removeChild(todoEl);
    }

    function addTodo(text) {
      window.todo_list.appendChild(createTodo(text));
    }

    function clearInput() {
      window.todo_text.value = '';
    }

    function isInputEmpty() {
      return window.todo_text.value.trim() === '';
    }

    function setAvailabilityOfAddButton() {
      const todoTextEl = window.todo_text;
      const todoBtn = window.add_todo_btn;
      const isEmpty = todoTextEl.value.trim() === '';
      if (isEmpty) {
        todoBtn.setAttribute('disabled', 'disabled');
      } else {
        todoBtn.removeAttribute('disabled');
      }
    }

    function getFormData(target) {
      const form = new FormData(target);
      const { done, value } = form.entries().next();
      if (value) {
        const data = {};
        data[value[0]] = value[1];
        return data;
      }
      return null;
    }

    const globalEventHandlers = {
      removeTodoItem(e) {
        removeTodo(e.target.parentNode);
      },
      formTodoTextInput(e) {
        setAvailabilityOfAddButton();
      },
      formButtonSubmit(e) {
        setAvailabilityOfAddButton();
      },
      formSubmit(e) { 
        e.preventDefault();
        const data = getFormData(e.target);
        if (data) {
          addTodo(data.todo_text);
          clearInput();
        }
        setAvailabilityOfAddButton();
      }
    };

    function processEvent(e) {
      const id = e.target.getAttribute('data-template');
      if (!id) return;
      const [tempalteName] = id.split(':')
      const template = templates[tempalteName];
      if (template) {
        const handlerKey = `${id}:on${e.type}`;
        const handlerFnName = template.handlers[handlerKey];
        if (handlerFnName && handlerFnName.match(/\{(.*)\}/)) {
          const eventHandlerName = handlerFnName.match(/\{(.*)\}/)[1];
          const eventHandler = 
    globalEventHandlers[eventHandlerName];
          if (typeof eventHandler === 'function') {
            eventHandler(e);
      }
      }
      }
      }

    const templates = {};
    let id = 1;

    function registerTemplate(node) {
      const element = node.content.cloneNode(true);
      const templateTreeWalker = document.createTreeWalker(
        element,
        NodeFilter.SHOW_ELEMENT,
      );
      const TemplateName = node.getAttribute('name').toLowerCase();
      templates[TemplateName] = { 
        element, 
        handlers: {},
        textVars: {}
      };

      const currentTemplate = templates[TemplateName];
  
      while(templateTreeWalker.nextNode()) {
        const node = templateTreeWalker.currentNode;
        const indexedTemplateName = 
    `${TemplateName}:${id}:${node.nodeName}`;
        node.setAttribute('data-template', indexedTemplateName);
        Array.from(node.attributes).forEach(a => {
          if (a.nodeName.startsWith('on') && a.nodeValue.match(/\ 
           {.*\}/)) {currentTemplate.handlers[`${indexedTemplateName}:${a.nodeName}`] = a.nodeValue;
                node.removeAttribute(a.nodeName);
              }
            });
            Array.from(node.childNodes).forEach((el, index) => {
              if (el.nodeName === '#text' && el.wholeText.match(/\ 
       {.*\}/)) {
                currentTemplate.textVars[index] = el.nodeValue;
                  }
                });
            id += 1;
              }
            }

        function applyTemplate(templateName, options) {
          const template = templates[templateName];
          const html = template.element.cloneNode(true);
          const topElement = html.children[0];
          Object.entries(template.textVars).forEach(([index, name]) => {
            const nameMatch = name.match(/\{(.*)\}/);
            const propName = nameMatch && nameMatch[1];
            topElement.childNodes[index].nodeValue = 
        topElement.childNodes[index].nodeValue.replace(new 
    RegExp(name, 'ig'), options[propName])
          });
          return html;
        }

     const NotStandardElements = [];
    function phaseOne() {
      const treeWalker = document.createTreeWalker(
        document.body,
        NodeFilter.SHOW_ELEMENT,
        { 
          acceptNode(node) { return NodeFilter.FILTER_ACCEPT; } 
        }
      );

      while(treeWalker.nextNode()) {
        if (treeWalker.currentNode.constructor.name === 'HTMLUnknownElement') {
          NotStandardElements.push(treeWalker.currentNode);
        }
        if (treeWalker.currentNode.nodeName === 'TEMPLATE') {
          registerTemplate(treeWalker.currentNode);
        }
      }
    }

    function phaseTwo() {
      const app = window.app;
      NotStandardElements.forEach(oldEl =>
       
     app.replaceChild(applyTemplate(oldEl.nodeName.toLowerCase(), {}), 
    oldEl));
    }

    // GLOBAL EVENT DELEGATION METHODS
    ['oninput', 'onclick', 'onsubmit'].forEach(event => 
    document[event] = processEvent);

    function runApp() {
      phaseOne();
      phaseTwo();
      setAvailabilityOfAddButton(); 
    }

    // entry point
    runApp();


##Conclusion


From the above, we can conclude that we have successfully applied the principles and capabilities of Event Delegation, and also implemented the simplest version of “React” for educational and research purposes.


Most importantly, now if you decide to write your application in pure JS, then the ``Event Delegation`` approach can:

* save you from having to worry about clearing events on an element before deleting it.

* help organize centralized event processing.

* help to concentrate all the code in one place, with the possibility of dividing the logic into modules.

* rid of hell event handlers.

* eliminate the need to insert pieces of JS code in templates.

Thank you very much for reading! I hope you enjoyed it!


Previously published at [maddevs.io.] (https://maddevs.io/blog/a-bit-about-event-delegation-in-pure-js?utm_source=devto&utm_medium=event-delegation-pure-js)