Last active
February 1, 2022 02:11
-
-
Save dux/89c59570228d745128aafef98164de1f to your computer and use it in GitHub Desktop.
Creates custom DOM element and passes props. Bare bones custom nodes
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
// https://gist.github.com/dux/89c59570228d745128aafef98164de1f | |
// micro custom dom elements, no shadow dom | |
// exposes props: root, props and state | |
window.CustomElement = { | |
// we need to find custom node in exact name | |
// CustomElement.find(domNode, 'foo-bar') | |
find: (node, uid) => { | |
while(node) { | |
if (node.customNode && node.customNode.UID == uid) { | |
return node.customNode | |
} else { | |
node = node.parentNode | |
} | |
} | |
alert('Custom node not found') | |
}, | |
// expose node attributes as object | |
attributes: (node) => { | |
// if you want to send nested complex data, best to define as data-props encoded as JSON | |
let props = node.getAttribute('data-props') | |
if (props) { | |
props = JSON.parse(props) | |
} else { | |
props = Array.prototype.slice.call(node.attributes).reduce(function(h, el) { | |
h[el.name] = el.value; | |
return h; | |
}, {}); | |
} | |
if (node.innerHTML) { | |
props.html = node.innerHTML | |
node.innerHTML = '' | |
} | |
return props; | |
}, | |
// define custom element | |
define: (name, klass) => { | |
if (!customElements.get(name)) { | |
window.addEventListener('DOMContentLoaded', () => { | |
customElements.define(name, class extends HTMLElement { | |
connectedCallback() { | |
let props = CustomElement.attributes(this) | |
let el = new klass(name, this, props) | |
this.customNode = el | |
if (el['init']) { | |
el.init(this, props) | |
} else { | |
el.render() | |
} | |
} | |
}) | |
}) | |
} | |
} | |
}; | |
class CustomNode { | |
static counter = 0 | |
// shuld not be overloaded | |
constructor(name, node, props) { | |
this.UID = ++CustomNode.counter | |
this.name = name | |
this.root = node | |
this.props = props | |
this.state = {} | |
} | |
// set state shortcut | |
set(name, value) { | |
this.state[name] = value | |
this.render() | |
} | |
// get innerHTML child nodes as an array of objects | |
children() { | |
let node = document.createElement('div') | |
node.innerHTML = this.props.html | |
return Array.prototype.slice | |
.call(node.children) | |
.map((slot)=>CustomElement.attributes(slot)) | |
} | |
// replace double $$ with pointer to current custom element, and pass plain html | |
html(data) { | |
data = data.replace(/\$\$\./g, `CustomElement.find(this, ${this.UID}).`) | |
this.root.innerHTML = data | |
} | |
// placeholder | |
render() {} | |
} | |
window.CustomNode = CustomNode |
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
// this.root : custome element dom node | |
// this.props : custome element attributes | |
// this.html(data) renders node innerHTML and parses $$. to current node pointer | |
// this.set(name, value) : sets value to state and calls render | |
// <foo-bar time="11:55"></foo-bar> | |
CustomElement.define('foo-bar', class FooBarNode extends CustomNode { | |
// init is called if defined, otherwise render() is called | |
init(rootNode, props) { | |
render() | |
} | |
toggle() { | |
this.state.foo = !this.state.foo; | |
this.render() | |
// or | |
this.set('foo', !this.state.foo) | |
} | |
render() { | |
this.html(`<div onclick="$$.toggle()">${this.props.time} : ${this.state.foo ? 1 : 0}</div>`) | |
} | |
}) | |
// %nav-main{ class: 'navbar-nav' } | |
// %slot{ href:'/heimdall', name:'Tasks' } | |
// %slot{ href:'/heimdall/schedules', name:'Schedules' } | |
// %slot{ href:'/heimdall/ques', name:'Ques' } | |
// %slot{ href:'/heimdall/sys', name:'Sys' } | |
CustomElement.define('nav-main', class extends CustomNode { | |
render() { | |
let children = this.children().map((el)=> | |
tag('li.nav-item', | |
tag('a.nav-link', el.name, {href: el.href, class: el.href == location.pathname ? 'active' : null}) | |
) | |
) | |
this.html(tag('ul', children.join(''), {class: this.props.class})) | |
} | |
}) | |
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
# can be compiled here https://coffeescript.org/#try | |
# tag 'a', { href: '#'}, 'link name' -> <a href="#">link name</a> | |
# tag 'a', 'link name' -> <a>link name</a> | |
# tag '.a', 'b' -> <div class="a">b</div> | |
# tag '#a.b', ['c','d'] -> <div class="b" id="a">cd</div> | |
# tag '#a.b', {c:'d'} -> <div c="d" class="b" id="a"></div> | |
tag_events = {} | |
tag_uid = 0 | |
window.tag = (name, args...) -> | |
return tag_events unless name | |
# evaluate function if data is function | |
args = args.map (el) -> if typeof el == 'function' then el() else el | |
# swap args if first option is object | |
args[1] ||= undefined # fill second value | |
[opts, data] = if typeof args[0] == 'object' && !Array.isArray(args[0]) then args else args.reverse() | |
# set default values | |
opts ||= {} | |
data = '' if typeof(data) == 'undefined' | |
data = data.join('') if Array.isArray(data) | |
# haml style id define | |
name = name.replace /#([\w\-]+)/, (_, id) -> | |
opts['id'] = id | |
'' | |
# haml style class add with a dot | |
name_parts = name.split('.') | |
name = name_parts.shift() || 'div' | |
if name_parts[0] | |
old_class = if opts['class'] then ' '+opts['class'] else '' | |
opts['class'] = name_parts.join(' ') + old_class | |
node = ['<'+name] | |
for key in Object.keys(opts).sort() | |
val = opts[key] | |
# hide function calls | |
if typeof val == 'function' | |
uid = ++tag_uid | |
tag_events[uid] = val | |
val = "tag()[#{uid}](this)" | |
node.push ' '+key+'="'+val+'"' | |
if ['input', 'img'].indexOf(name) > -1 | |
node.push ' />' | |
else | |
node.push '>'+data+'</'+name+'>' | |
node.join('') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment