UNPKG

oxe

Version:

A mighty tiny web components framework/library

212 lines (173 loc) 6.94 kB
import standard from './binder/standard'; import checked from './binder/checked'; import inherit from './binder/inherit'; import value from './binder/value'; import each from './binder/each'; import html from './binder/html'; import text from './binder/text'; import on from './binder/on'; import computer from './computer'; import parser from './parser'; const TN = Node.TEXT_NODE; const EN = Node.ELEMENT_NODE; // const AN = Node.ATTRIBUTE_NODE; export default class Binder { prefix = 'o-'; prefixEach = 'o-each'; prefixValue = 'o-value'; syntaxEnd = '}}'; syntaxStart = '{{'; syntaxLength = 2; syntaxMatch = new RegExp('{{.*?}}'); prefixReplace = new RegExp('^o-'); syntaxReplace = new RegExp('{{|}}', 'g'); nodeBinders: Map<Node, any> = new Map(); ownerBinders: Map<Node, Set<any>> = new Map(); pathBinders: Map<string, Set<any>> = new Map(); binders = { standard, checked, inherit, value, each, html, text, on, }; get (data: any) { if (typeof data === 'string') { return this.pathBinders.get(data); } else { return this.nodeBinders.get(data); } } async unbind (node: Node) { const ownerBinders = this.ownerBinders.get(node); if (!ownerBinders) return; for (const ownerBinder of ownerBinders) { this.nodeBinders.delete(ownerBinder.node); for (const path of ownerBinder.paths) { const pathBinders = this.pathBinders.get(path); if (!pathBinders) continue; pathBinders.delete(ownerBinder); if (!pathBinders.size) this.pathBinders.delete(path); } } this.nodeBinders.delete(node); this.ownerBinders.delete(node); } async bind (node: Node, container: any, name, value, owner, context: any, rewrites?: any) { const type = name.startsWith('on') ? 'on' : name in this.binders ? name : 'standard'; const handler = this.binders[ type ]; const binder = { meta: {}, ready: true, binder: this, paths: undefined, render: undefined, compute: undefined, unrender: undefined, binders: this.pathBinders, node, owner, name, value, rewrites, context, container, type, }; const [ paths, compute ] = await Promise.all([ parser(value, rewrites), computer(binder) ]); binder.paths = paths; binder.compute = compute; binder.render = handler.render.bind(null, binder); binder.unrender = handler.unrender.bind(null, binder); for (const reference of paths) { const binders = binder.binders.get(reference); if (binders) { binders.add(binder); } else { binder.binders.set(reference, new Set([ binder ])); } } this.nodeBinders.set(node, binder); const ownerBinders = this.ownerBinders.get(binder.owner); if (ownerBinders) { ownerBinders.add(binder); } else { this.ownerBinders.set(binder.owner, new Set([ binder ])); } return binder.render(); }; async remove (node: Node) { const tasks = []; // if (node.nodeType === AN) { // tasks.push(this.unbind(node)); if (node.nodeType === TN) { this.unbind(node); } else if (node.nodeType === EN) { this.unbind(node); const attributes = (node as Element).attributes; for (const attribute of attributes) { tasks.push(this.unbind(attribute)); } let child = node.firstChild; while (child) { // this.remove(child); tasks.push(this.remove(child)); child = child.nextSibling; } } return Promise.all(tasks); } async add (node: Node, container: any, context: any, rewrites?: any) { // if (node.nodeType === AN) { // const attribute = (node as Attr); // if (this.syntaxMatch.test(attribute.value)) { // tasks.push(this.bind(node, container, attribute.name, attribute.value, attribute.ownerElement, context, rewrites)); // } // } else if (node.nodeType === TN) { const tasks = []; const start = node.nodeValue.indexOf(this.syntaxStart); if (start === -1) return; if (start !== 0) node = (node as Text).splitText(start); const end = node.nodeValue.indexOf(this.syntaxEnd); if (end === -1) return; if (end + this.syntaxLength !== node.nodeValue.length) { const split = (node as Text).splitText(end + this.syntaxLength); tasks.push(this.add(split, container, context, rewrites)); } tasks.push(this.bind(node, container, 'text', node.nodeValue, node, context, rewrites)); return Promise.all(tasks); } else if (node.nodeType === EN) { const attributes = (node as Element).attributes; const inherit = attributes[ 'inherit' ]; if (inherit) { // await window.customElements.whenDefined((node as any).localName); // await (node as any).whenReady(); if (!(node as any).ready) { await new Promise((resolve: any) => node.addEventListener('ready', resolve)); } await this.bind(inherit, container, inherit.name, inherit.value, inherit.ownerElement, context, rewrites); } const each = attributes[ 'each' ]; if (each) await this.bind(each, container, each.name, each.value, each.ownerElement, context, rewrites); if (!each && !inherit) { let child = node.firstChild; if (child) { const tasks = []; do { tasks.push(this.add(child, container, context, rewrites)); } while (child = child.nextSibling); if (tasks.length) await Promise.all(tasks); } } if (attributes.length) { const tasks = []; for (const attribute of attributes) { if (attribute.name !== 'each' && attribute.name !== 'inherit' && this.syntaxMatch.test(attribute.value)) { tasks.push(this.bind(attribute, container, attribute.name, attribute.value, attribute.ownerElement, context, rewrites)); } } if (tasks.length) await Promise.all(tasks); } } } };