casualjavascript / blog

A collection of javascript articles stored as Github issues.
https://casualjavascript.com
MIT License
34 stars 1 forks source link

Imitating the DOM tree #8

Open mateogianolio opened 8 years ago

mateogianolio commented 8 years ago

The document object model (DOM) is essentially a tree (data structure), containing nodes with a number of optional attributes such as id or class.

Each node in the tree can have one parent and several children and these properties connect the nodes together to form a tree.

Consider this boilerplate HTML (left) and its corresponding tree (right):

<html>
  <head>
    <title></title>
    <script><script>
  </head>
  <body>
  </body>
</html>
        html
        /  \
   head      body
   /  \
title script

Note that the entire tree structure can be derived from any node in the tree by visiting all of its parents and children. This means that one single node will suffice to describe the entire tree.

So let's make a Node class:

class Node {
  constructor(name, attribs) {
    this.name = name;
    this.children = [];
    this.attribs = attribs;
  }
}
var html = new Node('html');

console.log(html);
// Node { name: 'html', children: [] }

For inheritance to work we need to extend Node with a method that allows us to append child nodes:

append(node) {
  node.parent = this;
  this.children.push(node);
}
var html = new Node('html');
html.append(new Node('head'));

console.log(html);
/* Node {
  name: 'html',
  children: [ Node { name: 'head', children: [], parent: [Circular] } ] } */

Neat! We just learned how to make a circular object. How do we traverse it? With generators, we can do it recursively in a few lines of code:

*traverse(node) {
  if (node === undefined)
    node = this;

  yield node;
  for (var child of node.children)
    yield *this.traverse(child);
}
var html = new Node('html');
var head = new Node('head');

head.append(new Node('title'));
html.append(head);
html.append(new Node('body'));

for (var node of html.traverse())
  console.log(node.name);

// html
// head
// title
// body

Finally, we can add a simple rendering method:

toString(node) {
  if (node === undefined)
    node = this;

  var out = '<' + node.name,
      attributes = [];

  for (var key in node.attribs)
    if (node.attribs.hasOwnProperty(key))
      attributes.push(' ' + key + '="' + node.attribs[key] + '"');

  out += attributes.join('') + '>';

  for (var child of node.children)
    out += this.toString(child);

  out += '</' + node.name + '>';
  return out;
}
var html = new Node('html', { class: 'cool' });
var head = new Node('head');

head.append(new Node('title'));
html.append(head);
html.append(new Node('body'));

console.log(html.toString());
// <html class="cool"><head><title></title></head><body></body></html>
SkyHacks commented 8 years ago

Great stuff Mateo! I'm loving these articles!!

mateogianolio commented 8 years ago

Thanks dude!