UNPKG

machine-framework

Version:

self-assembling client-side web framework

294 lines (196 loc) 6.82 kB
import AliasContext from './aliasContext.js'; import ScriptMonitor from './scriptMonitor.js'; import ScriptLoader from './scriptLoader.js'; import Placeholder from './placeholder.js'; import Catbus from './catbus.es.js'; import Cog from './cog.js'; import PartBuilder from './partBuilder.js'; let _id = 0; function Chain(url, slot, parent, def, sourceName, keyField){ this.id = ++_id; this.head = null; this.placeholder = slot; this.elements = []; this.namedElements = {}; this.children = []; this.parent = parent || null; this.scope = parent ? parent.scope.createChild() : Catbus.createChild(); this.url = url; this.root = ''; this.script = null; this.config = null; //(def && def.config) || def || {}; this.scriptMonitor = null; this.aliasValveMap = null; this.aliasContext = null; this.sourceName = sourceName; this.keyField = keyField; this.bus = null; this.buildConfig(def); this.load(); } Chain.prototype.buildConfig = PartBuilder.buildConfig; Chain.prototype.killPlaceholder = function() { if(!this.placeholder) return; Placeholder.give(this.placeholder); this.placeholder = null; }; Chain.prototype.load = function() { if(ScriptLoader.has(this.url)){ this.onScriptReady(); } else { ScriptLoader.request(this.url, this.onScriptReady.bind(this)); } }; Chain.prototype.onScriptReady = function() { this.script = Object.create(ScriptLoader.read(this.url)); this.script.id = this.id; this.script.config = this.config; this.root = this.script.root; this.prep(); }; Chain.prototype.prep = function(){ const parent = this.parent; const aliasValveMap = parent ? parent.aliasValveMap : null; //const aliasList = this.script.alias; const aliasHash = this.script.aliases; if(parent && parent.root === this.root && !aliasHash && !aliasValveMap){ // same relative path, no new aliases and no valves, reuse parent context this.aliasContext = parent.aliasContext; this.aliasContext.shared = true; } else { // new context, apply valves from parent then add aliases from cog this.aliasContext = parent ? parent.aliasContext.clone() : new AliasContext(this.root); // root of application this.aliasContext.restrictAliasList(aliasValveMap); //this.aliasContext.injectAliasList(aliasList); this.aliasContext.injectAliasHash(aliasHash); } this.loadBooks(); }; Chain.prototype.loadBooks = function loadBooks(){ if(this.script.books.length === 0) { this.loadTraits(); return; } const urls = this.aliasContext.freshUrls(this.script.books); if (urls.length) { this.scriptMonitor = new ScriptMonitor(urls, this.readBooks.bind(this)); } else { this.readBooks() } }; Chain.prototype.readBooks = function readBooks() { const urls = this.script.books; if(this.aliasContext.shared) // need a new context this.aliasContext = this.aliasContext.clone(); for (let i = 0; i < urls.length; ++i) { const url = urls[i]; const book = ScriptLoader.read(url); if(book.type !== 'book') console.log('EXPECTED BOOK: got ', book.type, book.url); this.aliasContext.injectAliasList(book.alias); } this.loadTraits(); }; Chain.prototype.loadTraits = function loadTraits(){ const urls = this.aliasContext.freshUrls(this.script.traits); if(urls.length){ this.scriptMonitor = new ScriptMonitor(urls, this.build.bind(this)); } else { this.build(); } }; Chain.prototype.getNamedElement = function getNamedElement(name){ if(!name) return null; const el = this.namedElements[name]; if(!el) throw new Error('Named element ' + name + ' not found in display!'); return el; }; Chain.prototype.build = function build(){ // urls loaded const meow = this.sourceName + ' * buildCogsByIndex'; this.bus = this.scope.bus().context(this).meow(meow).pull(); }; Chain.prototype.buildCogsByIndex = function buildCogsByIndex(msg){ const len = msg.length; const children = this.children; const childCount = children.length; const updateCount = len > childCount ? childCount : len; // update existing for(let i = 0; i < updateCount; ++i){ const d = msg[i]; const c = children[i]; c.source.write(d); } if(len === 0 && childCount > 0){ // restore placeholder as all children will be gone const el = this.getFirstElement(); // grab first child element this.placeholder = Placeholder.take(); el.parentNode.insertBefore(this.placeholder, el); } if(childCount < len) { // create new children const lastEl = this.getLastElement(); const nextEl = lastEl.nextElementSibling; const parentEl = lastEl.parentNode; const before = !!nextEl; const el = nextEl || parentEl; for (let i = childCount; i < len; ++i) { // create cogs for new data const slot = Placeholder.take(); if (before) { el.parentNode.insertBefore(slot, el); } else { el.appendChild(slot); } const cog = new Cog(this.url, slot, this, this.config, i); const d = msg[i]; cog.source.write(d); children.push(cog); } } else { for (let i = childCount - 1; i >= len; --i) { // remove cogs without corresponding data children[i].destroy(); children.splice(i, 1); } } this.tail = children.length > 0 ? children[children.length - 1] : null; this.head = children.length > 0 ? children[0] : null; }; Chain.prototype.getFirstElement = function(){ let c = this; while(c && !c.placeholder && c.elements.length === 0){ c = c.head; } return c.placeholder || c.elements[0]; }; Chain.prototype.getLastElement = function(){ let c = this; while(c && !c.placeholder && c.elements.length === 0){ c = c.tail; } return c.placeholder || c.elements[c.elements.length - 1]; }; Chain.prototype.destroy = function(){ this.dead = true; const len = this.children.length; for(let i = 0; i < len; ++i){ const c = this.children[i]; c.destroy(); } if(this.placeholder){ this.killPlaceholder(); } else { const len = this.elements.length; for(let i = 0; i < len; ++i){ const e = this.elements[i]; e.parentNode.removeChild(e); } } this.scope.destroy(); this.children = []; }; export default Chain;