Last active
March 22, 2016 10:20
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"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