UNPKG

@nent/core

Version:

Functional elements to add routing, data-binding, dynamic HTML, declarative actions, audio, video, and so much more. Supercharge static HTML files into web apps without script or builds.

313 lines (312 loc) 9.97 kB
/*! * NENT 2022 */ import { Component, Element, h, Host, Prop, State, } from '@stencil/core'; import { commonState, CommonStateSubscriber, debugIf, valueToArray, warnIf, } from '../../services/common'; import { resolveChildElementXAttributes } from '../../services/data/elements'; import { DATA_EVENTS } from '../../services/data/interfaces'; import { filterData } from '../../services/data/jsonata.worker'; import { hasToken, resolveTokens } from '../../services/data/tokens'; import { ROUTE_EVENTS } from '../n-views/services/interfaces'; import { routingState } from '../n-views/services/state'; /** * This tag renders a template for each item in the configured array. * The item template uses value expressions to insert data from any * data provider as well as the item in the array. * * @system content * @extension data * @extension elements */ export class ContentDataRepeat { constructor() { this.dynamicContent = null; /** * If set, disables auto-rendering of this instance. * To fetch the contents change to false or remove * attribute. */ this.deferLoad = false; /** * Turn on debug statements for load, update and render events. */ this.debug = false; /** * Force render with data & route changes. */ this.noCache = false; } get childTemplate() { return this.el.querySelector('template'); } get childScript() { return this.el.querySelector('script'); } componentWillLoad() { debugIf(this.debug, 'n-content-repeat: loading'); this.dataSubscription = new CommonStateSubscriber(this, 'dataEnabled', DATA_EVENTS.DataChanged); this.routeSubscription = new CommonStateSubscriber(this, 'routingEnabled', ROUTE_EVENTS.RouteChanged); if (this.childTemplate === null) { warnIf(this.debug, 'n-content-repeat: missing child <template> tag'); } else { this.innerTemplate = this.childTemplate.innerHTML; } this.contentKey = `data-content`; } async componentWillRender() { var _a; if (!this.innerTemplate) return; if (this.dynamicContent && !this.noCache) { if (commonState.elementsEnabled) { resolveChildElementXAttributes(this.el); } return; } const remoteContent = this.el.querySelector(`.${this.contentKey}`); remoteContent === null || remoteContent === void 0 ? void 0 : remoteContent.remove(); const items = await this.resolveItems(); const innerContent = await this.resolveHtml(items); if (innerContent) { this.dynamicContent = this.el.ownerDocument.createElement('div'); this.dynamicContent.className = this.contentKey; this.dynamicContent.innerHTML = innerContent; if (commonState.elementsEnabled) { resolveChildElementXAttributes(this.dynamicContent); } this.dynamicContent.innerHTML = innerContent; if (routingState === null || routingState === void 0 ? void 0 : routingState.router) { (_a = routingState.router) === null || _a === void 0 ? void 0 : _a.captureInnerLinks(this.dynamicContent); } this.el.append(this.dynamicContent); } } async resolveHtml(items) { debugIf(this.debug, 'n-content-repeat: resolving html'); let shouldRender = !this.deferLoad; if (shouldRender && this.when && commonState.dataEnabled) { const { evaluatePredicate } = await import('../../services/data/expressions'); shouldRender = await evaluatePredicate(this.when); } if (!shouldRender) { return null; } // DebugIf(this.debug, `n-content-repeat: innerItems ${JSON.stringify(this.resolvedItems || [])}`); if (this.innerTemplate) { let resolvedTemplate = ''; return await items.reduce((previousPromise, item) => previousPromise.then(async () => resolveTokens(this.innerTemplate.slice(), false, item).then(html => { resolvedTemplate += html; return resolvedTemplate; })), Promise.resolve()); } return null; } async resolveItems() { var _a; let items = []; if (this.items) { items = await this.resolveItemsExpression(); } else if (this.childScript) { try { let text = ((_a = this.childScript.textContent) === null || _a === void 0 ? void 0 : _a.replace('\n', '')) || ''; text = commonState.dataEnabled && hasToken(text) ? await resolveTokens(text, true) : text; items = valueToArray(JSON.parse(text || '[]')); } catch (error) { warnIf(this.debug, `n-content-repeat: unable to deserialize JSON: ${error}`); } } else if (this.itemsSrc) { items = await this.fetchJson(); } else { warnIf(this.debug, 'n-content-repeat: you must include at least one of the following: items, json-src or a <script> element with a JSON array.'); } if (this.filter) { let filterString = this.filter.slice(); if (hasToken(filterString)) { filterString = await resolveTokens(filterString); } debugIf(this.debug, `n-content-repeat: filtering: ${filterString}`); items = valueToArray(await filterData(filterString, items)); } return items; } async fetchJson() { try { debugIf(this.debug, `n-content-repeat: fetching items from ${this.itemsSrc}`); const response = await window.fetch(this.itemsSrc); if (response.status === 200) { const data = await response.json(); return valueToArray(data); } warnIf(this.debug, `n-content-repeat: Unable to parse response from ${this.itemsSrc}`); } catch (err) { warnIf(this.debug, `n-content-repeat: Unable to parse response from ${this.itemsSrc}: ${err}`); } return []; } async resolveItemsExpression() { let items = []; try { let itemsString = this.items; if (itemsString && hasToken(itemsString)) { itemsString = await resolveTokens(itemsString); debugIf(this.debug, `n-content-repeat: items resolved to ${itemsString}`); } items = itemsString ? JSON.parse(itemsString) : []; } catch (error) { warnIf(this.debug, `n-content-repeat: unable to deserialize JSON: ${error}`); } return items; } render() { return (h(Host, null, h("slot", null))); } disconnectedCallback() { this.dataSubscription.destroy(); this.routeSubscription.destroy(); } static get is() { return "n-content-repeat"; } static get properties() { return { "items": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string | undefined", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "The array-string or data expression to obtain a collection for rendering the template.\n{{session:cart.items}}" }, "attribute": "items", "reflect": false }, "itemsSrc": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string | undefined", "references": {} }, "required": false, "optional": true, "docs": { "tags": [{ "name": "example", "text": "/data.json" }], "text": "The URL to remote JSON collection to use for the items." }, "attribute": "items-src", "reflect": false }, "filter": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string | undefined", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "The JSONata query to filter the json items\nsee <https://try.jsonata.org> for more info." }, "attribute": "filter", "reflect": false }, "deferLoad": { "type": "boolean", "mutable": true, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "If set, disables auto-rendering of this instance.\nTo fetch the contents change to false or remove\nattribute." }, "attribute": "defer-load", "reflect": false, "defaultValue": "false" }, "debug": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Turn on debug statements for load, update and render events." }, "attribute": "debug", "reflect": false, "defaultValue": "false" }, "noCache": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Force render with data & route changes." }, "attribute": "no-cache", "reflect": false, "defaultValue": "false" }, "when": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string | undefined", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "A data-token predicate to advise this element when\nto render (useful if used in a dynamic route or if\ntokens are used in the 'src' attribute)" }, "attribute": "when", "reflect": false } }; } static get states() { return { "innerTemplate": {}, "resolvedTemplate": {}, "dynamicContent": {} }; } static get elementRef() { return "el"; } }