Created
March 14, 2017 21:37
-
-
Save wldcordeiro/b7e1cc78529688ec7d6c1bb9f7f7352f to your computer and use it in GitHub Desktop.
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
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