UNPKG

marko

Version:

UI Components + streaming, async, high performance, HTML templating for Node.js and the browser.

456 lines (375 loc) • 9.31 kB
"use strict";var EventEmitter = require("events-light"); var RenderResult = require("../RenderResult"); var attrsHelper = require("./helpers/attrs"); var morphdom = require("./morphdom"); var vdom = require("./vdom"); var VElement = vdom.bz_; var VComment = vdom.bA_; var VDocumentFragment = vdom.bB_; var VText = vdom.bC_; var VComponent = vdom.bD_; var VFragment = vdom.bE_; var virtualizeHTML = vdom.bF_; var EVENT_UPDATE = "update"; var EVENT_FINISH = "finish"; function State(tree) { this.bG_ = new EventEmitter(); this.bH_ = tree; this.bI_ = false; } function AsyncVDOMBuilder(globalData, parentNode, parentOut) { if (!parentNode) { parentNode = new VDocumentFragment(); } var state; if (parentOut) { state = parentOut.A_; } else { state = new State(parentNode); } this.bJ_ = 1; this.bK_ = 0; this.bL_ = null; this.bM_ = parentOut; this.data = {}; this.A_ = state; this.ad_ = parentNode; this.global = globalData || {}; this.bN_ = [parentNode]; this.bO_ = false; this.bP_ = undefined; this.b_ = null; this.ab_ = null; this.ac_ = null; this.be_ = null; } var proto = AsyncVDOMBuilder.prototype = { bv_: true, C_: typeof document === "object" && document, bc: function (component, key, ownerComponent) { var vComponent = new VComponent(component, key, ownerComponent); return this.bQ_(vComponent, 0, true); }, bf_: function (component, key, ownerComponent) { var vComponent = new VComponent(component, key, ownerComponent, true); this.bQ_(vComponent, 0); }, bQ_: function (child, childCount, pushToStack) { this.ad_.bR_(child); if (pushToStack === true) { this.bN_.push(child); this.ad_ = child; } return childCount === 0 ? this : child; }, element: function (tagName, attrs, key, component, childCount, flags, props) { var element = new VElement( tagName, attrs, key, component, childCount, flags, props ); return this.bQ_(element, childCount); }, bo_: function (tagName, attrs, key, componentDef, props) { return this.element( tagName, attrsHelper(attrs), key, componentDef.s_, 0, 0, props ); }, n: function (node, component) { // NOTE: We do a shallow clone since we assume the node is being reused // and a node can only have one parent node. var clone = node.bS_(); this.node(clone); clone._O_ = component; return this; }, node: function (node) { this.ad_.bR_(node); return this; }, text: function (text, ownerComponent) { var type = typeof text; if (type != "string") { if (text == null) { return; } else if (type === "object") { if (text.toHTML) { return this.h(text.toHTML(), ownerComponent); } } text = text.toString(); } this.ad_.bR_(new VText(text, ownerComponent)); return this; }, comment: function (comment, ownerComponent) { return this.node(new VComment(comment, ownerComponent)); }, html: function (html, ownerComponent) { if (html != null) { var vdomNode = virtualizeHTML(html, ownerComponent); this.node(vdomNode); } return this; }, beginElement: function ( tagName, attrs, key, component, childCount, flags, props) { var element = new VElement( tagName, attrs, key, component, childCount, flags, props ); this.bQ_(element, childCount, true); return this; }, bm_: function (tagName, attrs, key, componentDef, props) { return this.beginElement( tagName, attrsHelper(attrs), key, componentDef.s_, 0, 0, props ); }, bf: function (key, component, preserve) { var fragment = new VFragment(key, component, preserve); this.bQ_(fragment, null, true); return this; }, ef: function () { this.endElement(); }, endElement: function () { var stack = this.bN_; stack.pop(); this.ad_ = stack[stack.length - 1]; }, end: function () { this.ad_ = undefined; var remaining = --this.bJ_; var parentOut = this.bM_; if (remaining === 0) { if (parentOut) { parentOut.bT_(); } else { this.bU_(); } } else if (remaining - this.bK_ === 0) { this.bV_(); } return this; }, bT_: function () { var remaining = --this.bJ_; if (remaining === 0) { var parentOut = this.bM_; if (parentOut) { parentOut.bT_(); } else { this.bU_(); } } else if (remaining - this.bK_ === 0) { this.bV_(); } }, bU_: function () { var state = this.A_; state.bI_ = true; state.bG_.emit(EVENT_FINISH, this.n_()); }, bV_: function () { var lastArray = this._last; var i = 0; function next() { if (i === lastArray.length) { return; } var lastCallback = lastArray[i++]; lastCallback(next); if (!lastCallback.length) { next(); } } next(); }, error: function (e) { try { this.emit("error", e); } finally { // If there is no listener for the error event then it will // throw a new Error here. In order to ensure that the async fragment // is still properly ended we need to put the end() in a `finally` // block this.end(); } return this; }, beginAsync: function (options) { if (this.bO_) { throw Error( "Tried to render async while in sync mode. Note: Client side await is not currently supported in re-renders (Issue: #942)." ); } var state = this.A_; if (options) { if (options.last) { this.bK_++; } } this.bJ_++; var documentFragment = this.ad_.bW_(); var asyncOut = new AsyncVDOMBuilder(this.global, documentFragment, this); state.bG_.emit("beginAsync", { out: asyncOut, parentOut: this }); return asyncOut; }, createOut: function () { return new AsyncVDOMBuilder(this.global); }, flush: function () { var events = this.A_.bG_; if (events.listenerCount(EVENT_UPDATE)) { events.emit(EVENT_UPDATE, new RenderResult(this)); } }, aj_: function () { return this.A_.bH_; }, n_: function () { return this.bX_ || (this.bX_ = new RenderResult(this)); }, on: function (event, callback) { var state = this.A_; if (event === EVENT_FINISH && state.bI_) { callback(this.n_()); } else if (event === "last") { this.onLast(callback); } else { state.bG_.on(event, callback); } return this; }, once: function (event, callback) { var state = this.A_; if (event === EVENT_FINISH && state.bI_) { callback(this.n_()); } else if (event === "last") { this.onLast(callback); } else { state.bG_.once(event, callback); } return this; }, emit: function (type, arg) { var events = this.A_.bG_; switch (arguments.length) { case 1: events.emit(type); break; case 2: events.emit(type, arg); break; default: events.emit.apply(events, arguments); break; } return this; }, removeListener: function () { var events = this.A_.bG_; events.removeListener.apply(events, arguments); return this; }, sync: function () { this.bO_ = true; }, isSync: function () { return this.bO_; }, onLast: function (callback) { var lastArray = this._last; if (lastArray === undefined) { this._last = [callback]; } else { lastArray.push(callback); } return this; }, ai_: function (host) { var node = this.bP_; if (!node) { var vdomTree = this.aj_(); if (!host) host = this.C_; this.bP_ = node = vdomTree.br_(host, null); morphdom(node, vdomTree, host, this.b_); } return node; }, toString: function (host) { var docFragment = this.ai_(host); var html = ""; var child = docFragment.firstChild; while (child) { var nextSibling = child.nextSibling; if (child.nodeType != 1) { var container = docFragment.ownerDocument.createElement("div"); container.appendChild(child.cloneNode()); html += container.innerHTML; } else { html += child.outerHTML; } child = nextSibling; } return html; }, then: function (fn, fnErr) { // eslint-disable-next-line @typescript-eslint/no-this-alias var out = this; var promise = new Promise(function (resolve, reject) { out.on("error", reject).on(EVENT_FINISH, function (result) { resolve(result); }); }); return Promise.resolve(promise).then(fn, fnErr); }, catch: function (fnErr) { return this.then(undefined, fnErr); }, isVDOM: true, c: function (componentDef, key, customEvents) { this.ab_ = componentDef; this.ac_ = key; this.be_ = customEvents; } }; proto.e = proto.element; proto.be = proto.beginElement; proto.ee = proto.bn_ = proto.endElement; proto.t = proto.text; proto.h = proto.w = proto.write = proto.html; module.exports = AsyncVDOMBuilder;