From d8f1371b608bc8cb9b6be2f6727e30e30a25a62b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 12 Apr 2021 15:09:39 +0200 Subject: [PATCH] update TemplateView to add if and map fns --- src/utils/TemplateView.js | 59 +++++++++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 18 deletions(-) diff --git a/src/utils/TemplateView.js b/src/utils/TemplateView.js index c414c37..349907c 100644 --- a/src/utils/TemplateView.js +++ b/src/utils/TemplateView.js @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { setAttribute, text, isChildren, classNames, TAG_NAMES, HTML_NS, tag } from "./html.js"; +import { setAttribute, text, isChildren, classNames, TAG_NAMES, HTML_NS } from "./html.js"; /** Bindable template. Renders once, and allows bindings for given nodes. If you need @@ -33,11 +33,13 @@ import { setAttribute, text, isChildren, classNames, TAG_NAMES, HTML_NS, tag } f export class TemplateView { constructor(value, render = undefined) { this._value = value; + // TODO: can avoid this if we have a separate class for inline templates vs class template views this._render = render; this._eventListeners = null; this._bindings = null; this._subViews = null; this._root = null; + // TODO: can avoid this if we adopt the handleEvent pattern in our EventListener this._boundUpdateFromValue = null; } @@ -108,6 +110,10 @@ export class TemplateView { return this._root; } + _updateFromValue(changedProps) { + this.update(this._value, changedProps); + } + update(value) { this._value = value; if (this._bindings) { @@ -117,10 +123,6 @@ export class TemplateView { } } - _updateFromValue(changedProps) { - this.update(this._value, changedProps); - } - _addEventListener(node, name, fn, useCapture = false) { if (!this._eventListeners) { this._eventListeners = []; @@ -135,12 +137,19 @@ export class TemplateView { this._bindings.push(bindingFn); } - _addSubView(view) { + addSubView(view) { if (!this._subViews) { this._subViews = []; } this._subViews.push(view); } + + removeSubView(view) { + const idx = this._subViews.indexOf(view); + if (idx !== -1) { + this._subViews.splice(idx, 1); + } + } } // what is passed to render @@ -238,8 +247,6 @@ class TemplateBuilder { const newNode = renderNode(node); if (node.parentNode) { node.parentNode.replaceChild(newNode, node); - } else { - console.warn("Could not update parent of node binding"); } node = newNode; } @@ -279,15 +286,10 @@ class TemplateBuilder { } catch (err) { return errorToDOM(err); } - this._templateView._addSubView(view); + this._templateView.addSubView(view); return root; } - // sugar - createTemplate(render) { - return vm => new TemplateView(vm, render); - } - // map a value to a view, every time the value changes mapView(mapFn, viewCreator) { return this._addReplaceNodeBinding(mapFn, (prevNode) => { @@ -308,15 +310,36 @@ class TemplateBuilder { }); } - // creates a conditional subtemplate - if(fn, viewCreator) { + // Special case of mapView for a TemplateView. + // Always creates a TemplateView, if this is optional depending + // on mappedValue, use `if` or `mapView` + map(mapFn, renderFn) { + return this.mapView(mapFn, mappedValue => { + return new TemplateView(this._value, (t, vm) => { + const rootNode = renderFn(mappedValue, t, vm); + if (!rootNode) { + // TODO: this will confuse mapView which assumes that + // a comment node means there is no view to clean up + return document.createComment("map placeholder"); + } + return rootNode; + }); + }); + } + + ifView(predicate, viewCreator) { return this.mapView( - value => !!fn(value), + value => !!predicate(value), enabled => enabled ? viewCreator(this._value) : null ); } -} + // creates a conditional subtemplate + // use mapView if you need to map to a different view class + if(predicate, renderFn) { + return this.ifView(predicate, vm => new TemplateView(vm, renderFn)); + } +} function errorToDOM(error) { const stack = new Error().stack;