Skip to content

Instantly share code, notes, and snippets.

@wldcordeiro
Created March 14, 2017 21:37
Show Gist options
  • Save wldcordeiro/b7e1cc78529688ec7d6c1bb9f7f7352f to your computer and use it in GitHub Desktop.
Save wldcordeiro/b7e1cc78529688ec7d6c1bb9f7f7352f to your computer and use it in GitHub Desktop.
const eventMap = {
onClick: 'click',
onChange: 'change',
onKeyDown: 'keydown',
onKeyUp: 'keyup'
}
const ROOT_KEY = '__rektroot_';
const instancesByRootID = {};
let ROOT_ID = 1;
/*
* createElement - Element render helper
*
* Takes a tag (with dot-separated classes), an array of children
* (assumed to be either nodes or text) and possible raw text
* (used for inlining SVGs) and returns an HTML element ready to be appended
* or modified.
*/
export function createElement(tag, props, children) {
const [tagName, ...className] = tag.split('.');
if (props.hasOwnProperty('className')) {
const classList = props.className.split(' ');
className.push(...classList);
}
const newProps = Object.assign({}, props);
if (className.length) {
newProps.className = className.join(' ');
}
if (children) {
newProps.children = typeof children == 'string'
? children : children.filter(c => c == null ? '' : c);
}
return { type: tagName, props: newProps };
}
function isRoot(node) {
return node.dataset[ROOT_KEY] ? true : false;
}
/*
* render - Takes a VDOM Node returned from `createElement` and renders into
* the parent element passed in. Mounting or updating the rendered content
* based on if it has changed.
*/
export function render(vdomNode, parent) {
if (isRoot(parent)) {
update(vdomNode, parent);
} else {
mount(vdomNode, parent);
}
}
function mount(vdomNode, parent) {
const node = renderElement(vdomNode);
node._currentElem = vdomNode;
instancesByRootID[ROOT_ID] = node;
parent.dataset[ROOT_KEY] = ROOT_ID;
parent.appendChild(node);
ROOT_ID++
}
function update(vdomNode, parent) {
const id = parent.dataset[ROOT_KEY];
const instance = instancesByRootID[id];
if (instance._currentElem.type === vdomNode.type) {
updateNode(instance, vdomNode);
} else {
unmountElemAtNode(parent);
mount(vdomNode, parent);
}
}
function unmountElemAtNode(node) {
const id = node.dataset[ROOT_KEY];
const instance = instancesByRootID[id];
node.removeChild(instance);
delete instancesByRootID[id];
ROOT_ID--;
}
function updateNode(node, vdomNode) {
const propKeys = Object.keys(vdomNode.props);
propKeys.map(key => {
if (eventMap.hasOwnProperty(key)) {
node.addEventListener(eventMap[key], vdomNode.props[key]);
}
if (node._currentElem.props[key] != vdomNode.props[key]) {
if (key == 'value') {
node.setAttribute('value', vdomNode.props[key]);
} else if (key == 'children') {
const vdomChildren = vdomNode.props.children;
if (typeof vdomChildren == 'string') {
if (node._currentElem.children != vdomChildren) {
node._currentElem.children = vdomChildren;
node.replaceChild(renderElement(vdomChildren), node.children[0]);
return;
}
}
if (vdomChildren) {
vdomChildren
.forEach((newChild, i) => {
if (vdomChildren.length < node.children.length) {
while (node.children.length > vdomChildren.length) {
node.removeChild(node.children[vdomChildren.length]);
}
return;
}
if (i > node.children.length - 1) {
node.appendChild(renderElement(newChild));
node.children[i]._currentElem = newChild;
} else {
node.replaceChild(renderElement(newChild), node.children[i]);
node.children[i]._currentElem = newChild;
}
updateNode(node.children[i], newChild);
});
}
} else {
node[key] = vdomNode.props[key];
}
node._currentElem.props[key] = vdomNode.props[key];
}
});
}
function renderElement(vdomNode) {
// Handle null nodes.
if (vdomNode == null || vdomNode == '') {
return document.createTextNode('');
}
const node = document.createElement(vdomNode.type);
Object
.keys(eventMap)
.map((key) => {
if (vdomNode.props.hasOwnProperty(key)) {
node.addEventListener(eventMap[key], vdomNode.props[key]);
}
});
if (vdomNode.props.hasOwnProperty('value')) {
node.setAttribute('value', vdomNode.props.value);
}
const children = vdomNode.props.children;
Object.keys(vdomNode.props)
.map(key => {
if (!['value', 'children'].concat(Object.keys(eventMap)).includes(key)) {
node[key] = vdomNode.props[key];
}
});
if (children) {
if (typeof children == 'string') {
const child = document.createTextNode(children);
node.appendChild(child);
} else {
children.forEach(child => {
// Handle null children.
if (child == null) { child = ''; }
node.appendChild(renderElement(child));
});
}
}
return node;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment