UNPKG

marko

Version:

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

450 lines (370 loc) • 9.11 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.bw_; var VDocumentFragment = vdom.bx_; var VText = vdom.by_; var VComponent = vdom.bz_; var VFragment = vdom.bA_; var virtualizeHTML = vdom.bB_; var EVENT_UPDATE = "update"; var EVENT_FINISH = "finish"; function State(tree) { this.bC_ = new EventEmitter(); this.bD_ = tree; this.bE_ = false; } function AsyncVDOMBuilder(globalData, parentNode, parentOut) { if (!parentNode) { parentNode = new VDocumentFragment(); } var state; if (parentOut) { state = parentOut.z_; } else { state = new State(parentNode); } this.bF_ = 1; this.bG_ = 0; this.bH_ = null; this.bI_ = parentOut; this.data = {}; this.z_ = state; this.aa_ = parentNode; this.global = globalData || {}; this.bJ_ = [parentNode]; this.bK_ = false; this.bL_ = undefined; this.b_ = null; this._Z_ = null; this.a__ = null; this.ba_ = null; } var proto = AsyncVDOMBuilder.prototype = { bq_: true, B_: typeof document === "object" && document, bc: function (component, key, ownerComponent) { var vComponent = new VComponent(component, key, ownerComponent); return this.bM_(vComponent, 0, true); }, bb_: function (component, key, ownerComponent) { var vComponent = new VComponent(component, key, ownerComponent, true); this.bM_(vComponent, 0); }, bM_: function (child, childCount, pushToStack) { this.aa_.bN_(child); if (pushToStack === true) { this.bJ_.push(child); this.aa_ = 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.bM_(element, childCount); }, bk_: 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.bO_(); this.node(clone); clone._L_ = component; return this; }, node: function (node) { this.aa_.bN_(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.aa_.bN_(new VText(text, ownerComponent)); return this; }, 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.bM_(element, childCount, true); return this; }, bi_: 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.bM_(fragment, null, true); return this; }, ef: function () { this.endElement(); }, endElement: function () { var stack = this.bJ_; stack.pop(); this.aa_ = stack[stack.length - 1]; }, end: function () { this.aa_ = undefined; var remaining = --this.bF_; var parentOut = this.bI_; if (remaining === 0) { if (parentOut) { parentOut.bP_(); } else { this.bQ_(); } } else if (remaining - this.bG_ === 0) { this.bR_(); } return this; }, bP_: function () { var remaining = --this.bF_; if (remaining === 0) { var parentOut = this.bI_; if (parentOut) { parentOut.bP_(); } else { this.bQ_(); } } else if (remaining - this.bG_ === 0) { this.bR_(); } }, bQ_: function () { var state = this.z_; state.bE_ = true; state.bC_.emit(EVENT_FINISH, this.bs_()); }, bR_: 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.bK_) { 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.z_; if (options) { if (options.last) { this.bG_++; } } this.bF_++; var documentFragment = this.aa_.bS_(); var asyncOut = new AsyncVDOMBuilder(this.global, documentFragment, this); state.bC_.emit("beginAsync", { out: asyncOut, parentOut: this }); return asyncOut; }, createOut: function () { return new AsyncVDOMBuilder(this.global); }, flush: function () { var events = this.z_.bC_; if (events.listenerCount(EVENT_UPDATE)) { events.emit(EVENT_UPDATE, new RenderResult(this)); } }, ag_: function () { return this.z_.bD_; }, bs_: function () { return this.bT_ || (this.bT_ = new RenderResult(this)); }, on: function (event, callback) { var state = this.z_; if (event === EVENT_FINISH && state.bE_) { callback(this.bs_()); } else if (event === "last") { this.onLast(callback); } else { state.bC_.on(event, callback); } return this; }, once: function (event, callback) { var state = this.z_; if (event === EVENT_FINISH && state.bE_) { callback(this.bs_()); } else if (event === "last") { this.onLast(callback); } else { state.bC_.once(event, callback); } return this; }, emit: function (type, arg) { var events = this.z_.bC_; 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.z_.bC_; events.removeListener.apply(events, arguments); return this; }, sync: function () { this.bK_ = true; }, isSync: function () { return this.bK_; }, onLast: function (callback) { var lastArray = this._last; if (lastArray === undefined) { this._last = [callback]; } else { lastArray.push(callback); } return this; }, af_: function (host) { var node = this.bL_; if (!node) { var vdomTree = this.ag_(); if (!host) host = this.B_; this.bL_ = node = vdomTree.bm_(host, null); morphdom(node, vdomTree, host, this.b_); } return node; }, toString: function (host) { var docFragment = this.af_(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) { 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._Z_ = componentDef; this.a__ = key; this.ba_ = customEvents; } }; proto.e = proto.element; proto.be = proto.beginElement; proto.ee = proto.bj_ = proto.endElement; proto.t = proto.text; proto.h = proto.w = proto.write = proto.html; module.exports = AsyncVDOMBuilder;