Published
Edited
Nov 11, 2020
2 forks
Importers
4 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
async function sendMsg(evt) {
if (evt.keyCode === 13) {
console.log(msgs)
mutable msgs = msgs.concat([evt.target.value]);
}
}
return html`
${msgs.map(msg => html`<p>${msg}</p>`)}
<input class="text" onkeydown=${sendMsg}></input>
<button onclick=${() => mutable msgs = []}>clear</button>
`;
}
Insert cell
Insert cell
Insert cell
{
function sendMsg(evt) {
if (evt.keyCode === 13) {
console.log(msgs)
mutable msgs = msgs.concat([evt.target.value]);
}
}
return reconcile(this, html`
${msgs.map(msg => html`<p>${msg}</p>`)}
<input key="chat" class="text" onkeydown=${sendMsg}></input> <!-- Note key attribute added as well-->
<button onclick=${() => mutable msgs = []}>clear</button>
`);
}
Insert cell
Insert cell
Insert cell
function reconcile(current, target) {
// Some differences cannot be reconciled in place, return the target
if (!current ||
!target ||
current.nodeType != target.nodeType ||
current.nodeName != target.nodeName ||
current.namespaceURI != target.namespaceURI
) {
if (current && target && current.nodeName != target.nodeName) {
console.log("Cannot reconcile", current.nodeName, target.nodeName)
}
return target;
}

const hasChildren = current.firstChild || target.firstChild;
const hasAttibutes = current.hasAttributes || target.hasAttributes

if (current.nodeType === Node.TEXT_NODE) {
current.nodeValue = target.nodeValue
}
if (hasAttibutes) {
function indexAttributes(attributes) {
const index = {}
for(let i = attributes.length - 1; i >= 0; i--) {
index[attributes[i].name] = attributes[i].value;
}
return index;
}
const currentAttributes = indexAttributes(current.attributes)
const targetAttributes = indexAttributes(target.attributes)
const unionAttributeNames = new Set([...Object.keys(currentAttributes),
...Object.keys(targetAttributes)]);

for (let attributeName of unionAttributeNames) {
if (targetAttributes[attributeName]) {
if (targetAttributes[attributeName] !== currentAttributes[attributeName]) {
current.setAttribute(attributeName, targetAttributes[attributeName])
}
} else {
current.removeAttribute(attributeName);
}
}
}

for (let prop in target) {
// Events like onkeydown need to be copied over
if (prop.startsWith("on")) {
if (current[prop] !== target[prop]) {
current[prop] = target[prop];
}
}
}

// Index the children for reconciliation (if we have children)
if (hasChildren) {
function indexChildren(parent) {
const indexChildren = {}
// Collect children looking for key attribute
let index = 0;
for (let child = parent.firstChild; child; child = child.nextSibling) {
const key = child.hasAttributes && child.getAttribute("key") ?
child.getAttribute("key") : "$" + index;
indexChildren[key] = {
node: child,
index
};
index++;
}
return indexChildren;
}

const currentChildren = indexChildren(current);
const targetChildren = indexChildren(target);

// Create a new set of children, indexed by position
const newChildren = {}

// Generate reconciliation children and their ordering
const currentKeys = Object.keys(currentChildren);
const targetKeys = Object.keys(targetChildren);
const unionKeys = new Set([...currentKeys, ...targetKeys]);
for (let key of unionKeys) {
const currentChild = (currentChildren[key] || {}).node;
const targetChild = (targetChildren[key] || {}).node;
const reconciledChild = reconcile(currentChild, targetChild);
if (currentChild && reconciledChild !== currentChild) {
current.removeChild(currentChild);
}
if (reconciledChild) newChildren[targetChildren[key].index] = reconciledChild;
}

// Now we walk through the existing children,
// trying to avoid moving them if they already in right place
// This is fairly simple and probably does not scale to complex use cases
let curser = current.firstChild;
for (let i = 0; i < targetKeys.length; i++) {
if (curser === null) {
current.append(newChildren[i]);
} else {
if (curser === newChildren[i]) {
// Child is already in right place, no structural change in DOM required
curser = curser.nextSibling
} else {
// as we pruned unnecissary children already
// if there is a mismatch it probably implies the target is bigger
// If the element was in the current DOM it is moved
// If the element was in the target DOM it is added
current.insertBefore(newChildren[i], curser)
}
}
}
}
return current
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
NestedDOMUpdateInPlaceDOM = html`
<div id="NestedDOMUpdateInPlace"><p>
<b>raw</b>
</p></div>`
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more