NewFiveFour | Blog | Portfolio


A simple and naive Virtual DOM implementation, part 3

You’ll recall from part 1 we have a javascript structure, vd, like this:

[{
  "type": "node",
  "name": "HTML",
  "children": [{
    "type": "node",
    "name": "HEAD"
  }, {
    "type": "node",
    "name": "BODY",
    "children": [{
      "type": "node",
      "name": "DIV",
      "attrs": [{
        "id": "hi"
      }],
      "children": [{
        "type": "text",
        "value": "hi there"
      }]
    }]
  }]
}]

Let’s say we have another, vd1, which is the same except the text value is hi there again.

We want a function which loops through all the children, finds this difference and tells us. And that is simple enough:

function compare(v, v1, path) {
    if(v.type != v1.type) console.log(path, "type", v1.type)
    if(v.name != v1.name) console.log(path, "name", v1.name)
    if(JSON.stringify(v.attrs) != JSON.stringify(v1.attrs)) console.log(path, "attrs", v1.attrs)
    if(v.value != v1.value) console.log(path, "value", v1.value)
    for(var i = 0; i < v.children.length; i++) {
      compare(v.children[i], v1.children[i], path + "" + i)
    }
}; compare(vd[0], vd1[0], "")

It will output:

100 value hi there again

Telling us that at node one, zero, zero we should change the value attribute to “hi there again”.

For our first iteration of this function, this isn’t overly bad. (Although we really need to do better with the attrs part but we’ll leave it for now).

But what happens if we don’t just change values, but we insert a new node, a P with some text, above the first div?

In other words:

<html>
  <head>
  </head>
  <body>
    <p>
      a new insert
    </p>
    <div id="hi">
      hi there
    </div>
  </body>
</html>

It will output this:

10 name P
10 [{"id":"hi"}] [] attrs []
100 value a new insert

It’s replacing the first DIV for a P, with new attributes. And replacing the existing text node.

But we didn’t want it replaced. We wanted a new node inserted above it. We will have to think of a new implementation to overcome this problem.

And in part 4, we will.

javascript virtual-dom

A simple and naive Virtual DOM implementation, part 2

We now have a javascript reprentation of our DOM. (See part 1).

But when we traverse this structure we want to know what parts relate to which part in the DOM.

Let’s make a function that shows us where each javascript structure relates in the DOM tree:

function show_dom_location(v, path) {
    console.log(v.type, v.name, path)
    for(var i = 0; i < v.children.length; i++) {
      show_dom_location(v.children[i], path + "" + i)
    }
}; compare(vd[0], "")

vd is your virtual dom javascript structure from part 1 of this series. We pass in [0] since we’re starting at the root element of the structure.

And we pass in a blank string. This will hold the location of each node.

Each time we loop through a child element and recurse into the same function, we will add the loop iteration number. This seem strange but let’s see the output:

node HTML 
node HEAD 0
node BODY 1
node DIV 10
text undefined 100

The first output is the HTML node and the blank string we passed in.

The second output is saying “I’m the zeroth node in HTML element, i.e. HEAD

And last output is saying "I’m the first child (not zeroth) of the HTML element, i.e. BODY. And the zeroth of that, i.e. DIV. And the zeroth output of that, i.e. our text node.

Let’s then use this 100 to find the text node in our dom:

document.documentElement.childNodes[1].childNodes[0].childNodes[0]

In part three we’ll look at comparing two virtual dom javascript structures to find differences.

javascript virtual-dom

Javascript: A simple and naive Virtual DOM implementation, part 1

First thing we’ll do is make a Javascript representation of a DOM tree.

Let’s say we have this DOM tree:

<html>
  <head>
  </head>
  <body>
    <div id="hi">
      hi there
    </div>
  </body>
</html>

Let’s convert that into:

[{
  "type": "node",
  "name": "HTML",
  "children": [{
    "type": "node",
    "name": "HEAD"
  }, {
    "type": "node",
    "name": "BODY",
    "children": [{
      "type": "node",
      "name": "DIV",
      "attrs": [{
        "id": "hi"
      }],
      "children": [{
        "type": "text",
        "value": "hi there"
      }]
    }]
  }]
}]

Each object is either a normal node or text (there are others we could do like comments, but let’s keep it simple). And each has a name, like BODY etc.

We also store the attributes for a node in a list of objects. And the children for a node are included.

Here’s the function that does that:

function traverse(vd, ele) {
  var nu = {}
  vd.push(nu)
  if (ele.nodeType == 1) {
    nu.type = "node"
    nu.name = ele.nodeName
    nu.attrs = []
    for (var i = 0; i < ele.attributes.length; i++) {
      var attr = {}
      attr[ele.attributes[i].name] = ele.attributes[i].value;
      nu.attrs.push(attr)
    }
  } else if (ele.nodeType = 3) {
    nu.type = "text"
    nu.value = ele.nodeValue
  }
  nu.children = []
  for(var i = 0; i < ele.childNodes.length; i++) {
    traverse(nu.children, ele.childNodes[i])
  }
}; 
traverse(vd, document.documentElement); // vd is an empty array to be populated
JSON.stringify(vd, null, 2)

Next we should compare two of theses representations. And we will. Hopefully we will…

javascript virtual-dom

Javascript: Save text or string as a local file

This isn’t hard. You use HTML5’s download attribute on an a link.

var a = document.createElement("a");
a.href="data:application/json;charset=utf-8,YOUR_TEXT_HERE"
a.download = "some.json"
document.body.appendChild(a)
a.click()
document.body.removeChild(a)

In this case I’m saving it as JSON. Your file turns up in the Downloads directory.

You can’t seem to specify the location though, either in code or via a popup - likely due to security concerns.

javascript

Quick introduction leaflet.js

Let’s create a HTML file:

<link rel="stylesheet" href="https://unpkg.com/leaflet@1.0.1/dist/leaflet.css" />
<style>
  #mapid { height: 380px;
           width: 380px;
  }
</style>
<script src="https://unpkg.com/leaflet@1.0.1/dist/leaflet.js"></script>
<div id="mapid"></div>
<script>
  ...
</script>

We’re fetching leaflet.js from the internet somewhere, and we have a mapid div, along with explicit hight on that div, which leaflet demands.

Now in the script tag, let’s create a leaflet object, with a zoom level and lat lon:

var mymap = L.map('mapid').setView([51.379959, -1.144644], 13);

Let’s now create a tile layer, using mapbox’s streets layer, passing in a max zoom level, attribution, id of layer and token.

L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={token}', {
  maxZoom: 18,
  attribution: '',
  id: 'mapbox.streets',
  token: 'pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpandmbXliNDBjZWd2M2x6bDk3c2ZtOTkifQ._QA7i5Mpkd_m30IGElHziw'
}).addTo(mymap);

Finally let’s create a marker, and give it a name.

var marker = L.marker([51.379959, -1.144644]).addTo(mymap);
marker.bindPopup("Some company here").openPopup();

And that’s as simple as it gets.

leaflet-js javascript

Page 2 of 84
Previous Next