Skip to content

Instantly share code, notes, and snippets.

@yelouafi
Last active July 1, 2016 18:21
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 yelouafi/12bb24ba3ec2d566319d569c2eba0054 to your computer and use it in GitHub Desktop.
Save yelouafi/12bb24ba3ec2d566319d569c2eba0054 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Raw DOM Nostalgia</title>
<style>
.done {
text-decoration: line-through;
}
</style>
</head>
<body>
<script src="rawc.js"></script>
<script>
function todo(title) {
return comp(`
<input type=checkbox @done="checked:click">
<span class="{it.done ? 'done' : ''}">${title}</span>
`)
}
const c = comp(`
<input #input>
<button #add>Add</button>
<button #reverse>Reverse</button>
<ul each="todos">
<li here></li>
</ul>
`, {
todos: []
})
c.add.onclick = () => {
c.todos.push(todo(c.input.value))
c.input.value = ''
c.update()
}
c.reverse.onclick = () => {
c.todos = c.todos.reverse()
c.update()
}
document.body.appendChild(c.el)
</script>
</body>
</html>
function each(it, ac) {
for(let i = 0; i < it.length; i++) {
ac(it[i], i)
}
}
const expRE = /\{[\s\S]+\}/g
function interpolate(exp) {
if(exp.startsWith('{'))
return new Function(['it'], `return ${exp.substring(1, exp.length - 1)}`)
else {
const fns = []
const body = 'return `' + exp.replace(expRE, s => {
fns.push(interpolate(s))
return '${fns[' + (fns.length - 1) + '](it)}'
}) + '`'
const fn = new Function(['it', 'fns'], body)
return ctx => fn(ctx, fns)
}
}
function visitAttribute(attr, el, ctx, queue) {
const attrName = attr.name
const attrValue = attr.value
if(attrName.startsWith('#')) {
ctx[attrName.slice(1)] = el
}
else if(attrName.startsWith('@')) {
const id = attrName.slice(1)
const [prop, event] = attrValue.split(':')
Object.defineProperty(ctx, id, { get: () => el[prop] })
if(event) {
el.addEventListener(event, () => ctx.update())
}
}
else if(attrName === 'each') {
let tpl = el.children.length ? el.children[0] : null
if(tpl)
tpl.remove()
queue.push(() => {
const children = ctx[attrValue]
children.forEach(ch => {
if(tpl) {
if(!ch.wrapper) {
const node = tpl.cloneNode(true)
ch.wrapper = node.hasAttribute('here') ? node : node.querySelector('[here]')
ch.wrapper.appendChild(ch.el ? ch.el : ch)
}
el.appendChild(ch.wrapper)
} else
el.appendChild(ch.el ? ch.el : ch)
})
if(el.children.length > children.length) {
for(let i = children.length; i < el.children.length; i++) {
el.children[i].remove()
}
}
})
}
else if(expRE.test(attrValue)) {
const fn = interpolate(attrValue)
queue.push(() => attr.value = fn(ctx))
}
}
function visitNode(node, ctx, queue) {
if(node.nodeType === 1)
travserse(node, ctx, queue)
else if(node.nodeType === 3 && expRE.test(node.nodeValue)) {
const fn = interpolate(node.nodeValue)
queue.push(() => node.nodeValue = fn(ctx))
}
}
function travserse(el, ctx, queue) {
each(el.attributes, attr => {
visitAttribute(attr, el, ctx, queue)
})
each(el.childNodes, node => {
visitNode(node, ctx, queue)
})
}
function comp(html, ctx = {}) {
let tmp = document.createElement('div');
tmp.innerHTML = html;
let el = tmp.children.length === 1 ? tmp.children[0] : tmp
ctx.el = el
const queue = []
travserse(el, ctx, queue)
ctx.update = () => queue.forEach(ac => ac())
ctx.update()
return ctx
}
"use strict"
function updateChildren(node, children) {
let actCh = node.firstElementChild
let curCh
for(let i = 0, len = children.length; i < len; i++) {
curCh = children[i]
if(!actCh) {
node.appendChild(curCh)
} else if(curCh !== actCh) {
const nextCh = actCh.nextElementSibling
if(curCh === nextCh) {
actCh.remove()
actCh = nextCh.nextElementSibling
} else {
node.insertBefore(curCh, actCh)
}
} else {
actCh = actCh.nextElementSibling
}
}
if(curCh) {
while(curCh.nextElementSibling) {
curCh.nextElementSibling.remove()
}
}
}
function domUpdater() {
const queue = []
function bindAttr(node, attr, getValue) {
if(typeof attr === 'string')
attr = node.getAttributeNode(attr)
queue.push(() => {
const newval = getValue()
if(attr.value !== newval) {
attr.value = newval
}
})
}
function bindText(node, getText) {
queue.push(() => {
const newtext = getText()
if(node.textContent !== newtext) {
node.textContent = newtext
}
})
}
function bindChildren(node, getChildren) {
queue.push(() => {
updateChildren(node, getChildren())
})
}
function update(ac) {
if(ac) ac()
queue.forEach(task => task())
}
return {
add : task => queue.push(task),
update,
bindAttr,
bindText,
bindChildren
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment