Skip to content

Instantly share code, notes, and snippets.

@threepointone
Last active March 22, 2016 10:20
Show Gist options
  • Save threepointone/8368018 to your computer and use it in GitHub Desktop.
Save threepointone/8368018 to your computer and use it in GitHub Desktop.
project: Facebook's reconcilliation algorithm in regular js, applied to dom nodes License: MIT
"use strict";
var _ = require('underscore'),
slice = [].slice;
var mutations = {
append: append,
remove: remove,
replace: replace,
setAttr: setAttr
};
module.exports = function(dest, src, options) {
var actions = project(dest, src, options);
_(actions).each(function(arr) {
mutations[arr[0]].apply(null, slice.call(arr, 1));
});
return actions;
};
// first, a naive implementation that goes through all children
// node1, node2
// if tagName different, replace
// go through all attributes, replace the ones that have changed
// now children
// if keys available, pair by key - todo
// else, pair by index
// if extra children in node1, insert into parent
// if extra children in node2, remove from parent
function project(dest, src, options) {
// that's the whole api
// we take one element, and "project" it onto another element
var actions = [];
if (tag(dest) !== tag(src)) {
actions.push(['replace', dest, src]);
return actions;
}
if (tag(dest) === '#text') {
if (dest.textContent !== src.textContent) {
actions.push(['replace', dest, src]);
}
return actions;
}
// todo - 'checked'
var attrs = _(_.keys(attr(dest)).concat(_.keys(attr(src)))).unique();
// console.log(attrs);
_(attrs).each(function(k) {
if (attr(dest, k) != attr(src, k)) {
actions.push(['setAttr', dest, k, attr(src, k)]);
return;
}
});
// todo - compare by keys
// ignore comment nodes?
var length = Math.max(children(dest).length, children(src).length);
_.times(length, function(i) {
if (!child(dest, i) && !child(src, i)) {
return;
}
if (!child(dest, i) && child(src, i)) {
actions.push(['append', dest, child(src, i)]);
return;
}
if (!child(src, i) && child(dest, i)) {
throw new Error('whaaat')
actions.push(['remove', child(dest, i)]);
return;
}
actions = actions.concat(project(child(dest, i), child(src, i), options));
});
return actions;
}
function child(el, i) {
return el.childNodes[i];
}
function children(el) {
return el.childNodes;
}
function attr(el, key) {
if (el.getAttribute) {
if (key) {
var val = el.getAttribute(key);
return (val && val.value) ? val.value : val;
// return el.getAttribute(key);
} else {
return _(el.attributes).chain().map(function(attr) {
return [attr.name, attr.value];
}).object().value();
}
}
}
function setAttr(el, key, val) {
if(val=== null){
return el.removeAttribute(key);
}
return el.setAttribute(key, val);
}
function tag(el) {
return el.nodeName;
}
function append(dest, el) {
return dest.appendChild(el);
}
function parent(el) {
return el.parentElement || el.parentNode;
}
function remove(el) {
return parent(el).removeChild(el);
}
function replace(dest, src) {
if(parent(dest).replaceChild){
return parent(dest).replaceChild(src, dest);
}
else if (dest.nextSibling) {
parent(dest).insertBefore(src, dest.nextSibling);
} else {
parent(dest).appendChild(src);
}
return remove(dest);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment