Skip to content

Instantly share code, notes, and snippets.

@cjohansen
Created March 11, 2024 15:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cjohansen/464027efa8d24d7cd8508817803fb252 to your computer and use it in GitHub Desktop.
Save cjohansen/464027efa8d24d7cd8508817803fb252 to your computer and use it in GitHub Desktop.
En liten virtuell DOM-implementasjon
<!DOCTYPE html>
<html>
<head>
<title>Virtuell DOM fra bunnen av</title>
<meta charset="utf-8">
</head>
<body>
<script src="vdom.js"></script>
</body>
</html>
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