Created
March 11, 2024 15:00
-
-
Save cjohansen/464027efa8d24d7cd8508817803fb252 to your computer and use it in GitHub Desktop.
En liten virtuell DOM-implementasjon
This file contains hidden or 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
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Virtuell DOM fra bunnen av</title> | |
<meta charset="utf-8"> | |
</head> | |
<body> | |
<script src="vdom.js"></script> | |
</body> | |
</html> |
This file contains hidden or 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
function createElement(tag, attrs, ...children) { | |
return { | |
tag, | |
attrs, | |
children | |
} | |
} | |
function createNode(vdom) { | |
if (typeof vdom === "string") { | |
return document.createTextNode(vdom); | |
} else { | |
var node = document.createElement(vdom.tag); | |
Object.keys(vdom.attrs).forEach(k => { | |
node.setAttribute(k, vdom.attrs[k]); | |
}); | |
vdom.children.forEach(c => { | |
node.appendChild(createNode(c)); | |
}); | |
return node; | |
} | |
} | |
function insertNode(parent, child, idx) { | |
var sibling = parent.childNodes[idx]; | |
if (sibling) { | |
parent.insertBefore(child, sibling); | |
} else { | |
parent.appendChild(child); | |
} | |
} | |
function replaceNode(parent, child, idx) { | |
var sibling = parent.childNodes[idx]; | |
if (sibling) { | |
parent.replaceChild(child, sibling); | |
} else { | |
parent.appendChild(child); | |
} | |
} | |
function updateNode(node, vdomNew, vdomOld) { | |
Object.keys(vdomNew.attrs) | |
.concat(Object.keys(vdomOld.attrs)) | |
.forEach(key => { | |
if (vdomNew.attrs[key]) { | |
node.setAttribute(key, vdomNew.attrs[key]); | |
} else { | |
node.removeAttribute(key); | |
} | |
}); | |
vdomNew.children.forEach((child, idx) => { | |
updateDOM(node, child, vdomOld.children[idx], idx); | |
}); | |
for (var i = vdomNew.children.length; i < vdomOld.children.length; i++) { | |
node.removeChild(node.childNodes[i]); | |
} | |
} | |
function updateDOM(parent, vdomNew, vdomOld, idx) { | |
if (!vdomOld) { | |
insertNode(parent, createNode(vdomNew), idx) | |
} else if (typeof vdomNew === "string") { | |
if (vdomNew !== vdomOld) { | |
replaceNode(parent, createNode(vdomNew), idx); | |
} | |
} else { | |
if (vdomNew.tag === vdomOld.tag) { | |
updateNode(parent.childNodes[idx], vdomNew, vdomOld); | |
} else { | |
replaceNode(parent, createNode(vdomNew), idx); | |
} | |
} | |
} | |
function render(el, vdomNew, vdomOld) { | |
updateDOM(el, vdomNew, vdomOld, 0); | |
return vdomNew; | |
} | |
////////////////// | |
// Sample usage // | |
////////////////// | |
function renderExample(el, vdom1, vdom2) { | |
el.innerHTML = ""; | |
var v1 = render(el, vdom1, null); | |
setTimeout(() => { | |
render(el, vdom2, v1); | |
}, 1000); | |
} | |
function createButton(text, target, vdom1, vdom2) { | |
var el = document.createElement("button"); | |
el.innerText = text; | |
el.addEventListener("click", e => { | |
e.preventDefault(); | |
renderExample(target, vdom1, vdom2); | |
}); | |
return el; | |
} | |
(function () { | |
document.body.innerHTML = "<div id=\"demo\"></div><div id=\"controls\"></div>"; | |
var demo = document.getElementById("demo"); | |
var controls = document.getElementById("controls"); | |
controls.appendChild( | |
createButton( | |
"Basic update", | |
demo, | |
createElement("h1", {class: "heading"}, "Hello world!"), | |
createElement("h1", {class: "heading"}, "Good bye") | |
) | |
); | |
controls.appendChild( | |
createButton( | |
"Add element", | |
demo, | |
createElement("h1", {class: "heading"}, "Hello world!"), | |
createElement("h1", {class: "heading"}, "Hello ", createElement("strong", {}, "WORLD!")) | |
) | |
); | |
controls.appendChild( | |
createButton( | |
"Remove element", | |
demo, | |
createElement("h1", {class: "heading"}, "Hello ", createElement("strong", {}, "WORLD!")), | |
createElement("h1", {class: "heading"}, "Hello world!") | |
) | |
); | |
controls.appendChild( | |
createButton( | |
"Change tag", | |
demo, | |
createElement("h1", {class: "heading"}, "Hello world!"), | |
createElement("p", {}, "Here's a paragraph") | |
) | |
); | |
}()); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment