lets try to build a dom diffing library

In this example below you will see how to do a lets try to build a dom diffing library with some HTML / CSS and Javascript

it should take a source and a string representation of changes

Thumbnail
This awesome code was written by tevko, you can see more from this user in the personal repository.
You can find the original code on Codepen.io
Copyright tevko ©

Technologies

  • HTML
  • CSS
  • JavaScript
<!DOCTYPE html>
<html lang="en" >

<head>
  <meta charset="UTF-8">
  <title>lets try to build a dom diffing library</title>
  
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css">

  
      <link rel="stylesheet" href="css/style.css">

  
</head>

<body>

  <h1>Introducing: Bad-DOM - the smallest<sup>*</sup> component renderer</h1>

<span><sup>*</sup>seriously, it's 800 bytes minified + gzipped</span>

<h2>What does it do?</h2>
<p>Bad-DOM is a lazy diffing algorithm and HTML component renderer. It's lazy because it doesn't try too hard to keep the browser from doing unnecessary work, but still does a pretty good job.</p>
<p>Bad-DOM exposes a single function, <code>vDiff</code>, which takes 2 parameters. The first being an existing element on the page, and the second being a template string representing the markup you would like to replace the first item with.
</p>
<p>Calling this function will result in parameter 1 being replaced by parameter 2, with the goal of the least amount of changes taking place in the DOM in order to achieve that replacement. Here's what that would look like using some "hello world" text in a p tag:</p>
<p>
<code>vDiff(document.querySelector('.hello-world-demo'), `&lt;p class="hello-world-demo"&gt;GoodBye!&lt;/p&gt;`)</code>
</p>
<p>In this example, we're taking an element from the page (a paragraph tag with text in it that says "hello world!") and changing the text inside it. Bad-DOM knows not to change anything other than the text, because it can tell that nothing else has changed.</p>
<h2>What else does it do?</h2>
<p>Since Bad-DOM takes a template string as its second parameter, it becomes a fully capable component renderer as well.</p>
<p>For example, given an html page with an empty element: <code>&lt;div id="My-App"&gt;&lt;/div&gt;</code></p>
<p>We can render markup into our My-App container:
<pre>
<code>
const name = 'Tim';
vDiff(document.querySelector('#My-App'), `&lt;div id="My-App"&gt;Hello, ${name}&lt;/div&gt;`);
</code>	
</pre>
</p>
<p>An example of this behavior can be found below, wherein a working image slider is being rendered into the dom and transitioned based on a counter state. 
<section>
	<img style="display: block" src="https://placeimg.com/700/200/animals" alt="">
</section>
<button>Get Next Image</button>
<p>Here's the code necessary to render the slider:
<pre>
<code>
document.querySelector('button').addEventListener('click', () => {
  x = (x === 3 ? 0 : x + 1);
  vDiff(document.querySelector('section'),
	  `&lt;section&gt;
	    &lt;img style=&quot;display:${x === 1 ? 'block' : 'none'}&quot; src=&quot;https://placeimg.com/700/200/tech&quot; alt=&quot;phones p much&quot;&gt;
	    &lt;img style=&quot;display:${x === 2 ? 'block' : 'none'}&quot; src=&quot;https://placeimg.com/700/200/people&quot; alt=&quot;peeps&quot;&gt;
	    &lt;img style=&quot;display:${x === 0 ? 'block' : 'none'}&quot; src=&quot;https://placeimg.com/700/200/animals&quot; alt=&quot;woofers&quot;&gt;
	    &lt;img style=&quot;display:${x === 3 ? 'block' : 'none'}&quot; src=&quot;https://placeimg.com/700/200/nature&quot; alt=&quot;outside&quot;&gt;
	  &lt;/section&gt;`
	);
});
</code>	
</pre>
</p>
<h2>Why should I use this thing?</h2>
<p>Since Bad-DOM is a fully capable component renderer, you can use it to build fully capable UI's. An example is provided here: <a href="https://codepen.io/tevko/pen/MrwXdy" >https://codepen.io/tevko/pen/MrwXdy</a>.</p>
<p>For the above demo application to work, all we need to do is include the Bad-DOM library, provide a render target, and feed the vDiff function a template string. After the initial rendering takes place, the inline event handlers reference functions that update the global app state and rebuild the template string. No more DOM manipulation, render-hacks, or confusing framework methods to memorize.</p>
<p>Overall, you can use Bad-DOM to build powerful components as long as you provide your own state management and event systems. In the counter demo referenced above, basic inline html events are being used, while each callback function is only responsible for updating the application state and calling the render function. Essentially, Bad-DOM's advantage is that it doesn't keep any internal memory, but is still able to intelligently update the DOM based on whatever source it is given. Since Bad-DOM doesn't provide any reoccuring update functionality (you need to tell it to re-render explicitly) your application will be less likely to fall into common performance pitfalls seen with other component renderers. </p>
<h2>Conclusion</h2>
<ul>
	<li>Bad-DOM is fast.</li>
	<li>Bad-DOM is small.</li>
	<li>Bad-DOM is incredibly easy to use.</li>
</ul>
  
  

    <script  src="js/index.js"></script>




</body>

</html>

/*Downloaded from https://www.codeseek.co/tevko/lets-try-to-build-a-dom-diffing-library-LzXjKE */
body {
  padding: 3em;
}

span {
  font-family: monospace;
  font-size: smaller;
}

code, pre {
  background-color: #fdfdf7;
  border: solid 1px #b39d9d;
  padding: 2px;
  color: #d69090;
  display: inline-block;
}
pre code, pre pre {
  border: none;
}

section img {
  display: none;
}
section .active {
  display: block;
}

button {
  margin-top: 1em;
}


/*Downloaded from https://www.codeseek.co/tevko/lets-try-to-build-a-dom-diffing-library-LzXjKE */
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }

var vDiff = function vDiff(target, source) {
	var worker = {
		settings: {
			original: target
		},
		replace: function replace(target) {
			var source = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : target;

			var v = document.createElement('template');
			v.innerHTML = source;
			var vHTML = v.content.firstChild.nextElementSibling;
			if (vHTML.nodeName !== target.nodeName) {
				target.parentElement.replaceChild(vHTML, target);
				return;
			}
			this.iterate(target, vHTML);
		},
		iterate: function iterate(targetNode, sourceNode, tOriginal, sOriginal) {
			var _this = this;

			if (targetNode || sourceNode) {
				this.checkAdditions(targetNode, sourceNode, tOriginal, sOriginal);
				if (targetNode && sourceNode && targetNode.nodeName !== sourceNode.nodeName) {
					this.checkNodeName(targetNode, sourceNode);
				} else if (targetNode && sourceNode && targetNode.nodeName === sourceNode.nodeName) {
					this.checkTextContent(targetNode, sourceNode);
					targetNode.nodeType !== 3 && target.nodeType !== 8 && this.checkAttributes(targetNode, sourceNode);
				}
			}
			if (targetNode && sourceNode) {
				if (targetNode.childNodes && sourceNode.childNodes) {
					this.settings.lengthDifferentiator = [].concat(_toConsumableArray(target.childNodes), _toConsumableArray(sourceNode.childNodes));
				} else {
					this.settings.lengthDifferentiator = null;
				}
				Array.apply(null, this.settings.lengthDifferentiator).forEach(function (node, idx) {
					_this.settings.lengthDifferentiator && _this.iterate(targetNode.childNodes[idx], sourceNode.childNodes[idx], targetNode, sourceNode);
				});
			}
		},
		checkNodeName: function checkNodeName(targetNode, sourceNode) {
			var n = sourceNode.cloneNode(true);
			targetNode.parentElement.replaceChild(n, targetNode);
		},
		checkAttributes: function checkAttributes(targetNode, sourceNode) {
			var attributes = targetNode.attributes || [];
			var filteredAttrs = Object.keys(attributes).map(function (n) {
				return attributes[n];
			});
			var attributesNew = sourceNode.attributes || [];
			var filteredAttrsNew = Object.keys(attributesNew).map(function (n) {
				return attributesNew[n];
			});
			filteredAttrs.forEach(function (o) {
				return sourceNode.getAttribute(o.name) !== null ? targetNode.setAttribute(o.name, sourceNode.getAttribute(o.name)) : targetNode.removeAttribute(o.name);
			});
			filteredAttrsNew.forEach(function (a) {
				return targetNode.getAttribute(a.name) !== sourceNode.getAttribute(a.name) && targetNode.setAttribute(a.name, sourceNode.getAttribute(a.name));
			});
		},
		checkTextContent: function checkTextContent(targetNode, sourceNode) {
			if (targetNode.nodeValue !== sourceNode.nodeValue) {
				targetNode.textContent = sourceNode.textContent;
			}
		},
		checkAdditions: function checkAdditions(targetNode, sourceNode) {
			var tParent = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.settings.original;
			var sParent = arguments[3];

			if (sourceNode && targetNode === undefined) {
				var newNode = sourceNode.cloneNode(true);
				tParent.nodeType !== 3 && tParent.nodeType !== 8 && tParent.appendChild(newNode);
			} else if (targetNode && sourceNode === undefined) {
				targetNode.parentElement.removeChild(targetNode);
			}
		}
	};
	Object.create(worker).replace(target, source);
};

var x = 0;

document.querySelector('button').addEventListener('click', function () {
	x = x === 3 ? 0 : x + 1;
	vDiff(document.querySelector('section'), '\n\t\t\t<section>\n\t\t\t<img style="display:' + (x === 1 ? 'block' : 'none') + '" src="https://placeimg.com/700/200/tech" alt="phones p much">\n\t\t\t<img style="display:' + (x === 2 ? 'block' : 'none') + '" src="https://placeimg.com/700/200/people" alt="peeps">\n\t\t\t<img style="display:' + (x === 0 ? 'block' : 'none') + '" src="https://placeimg.com/700/200/animals" alt="woofers">\n\t\t\t<img style="display:' + (x === 3 ? 'block' : 'none') + '" src="https://placeimg.com/700/200/nature" alt="outside">\n\t\t\t</section>\n\t\t');
});

Comments