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.

320 lines (319 loc) 9.5 kB
/*! * NENT 2022 */ import { Component, Element, Prop, State } from '@stencil/core'; import { CommonStateSubscriber } from '../../services/common'; import { debugIf, warn } from '../../services/common/logging'; import { commonState } from '../../services/common/state'; import { fetchJson } from '../../services/content'; import { replaceHtmlInElement } from '../../services/content/elements'; import { resolveChildElementXAttributes } from '../../services/data/elements'; import { DATA_EVENTS } from '../../services/data/interfaces'; import { filterData } from '../../services/data/jsonata.worker'; import { resolveTokens } from '../../services/data/tokens'; import { dedent } from '../n-content/services/utils'; import { ROUTE_EVENTS } from '../n-views/services/interfaces'; import { routingState } from '../n-views/services/state'; /** * Render data directly into HTML using declarative expressions. * This element renders the expression with all data-tokens * replaced with the values provided by the provider. * * @system content * @extension data * @extension elements */ export class ContentTemplate { constructor() { this.contentClass = 'dynamic'; this.contentElement = 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; /** * Cross Origin Mode */ this.mode = 'cors'; /** * When declared, the child script tag is required and should be * the query text for the request. Also, this forces the HTTP * method to 'POST'. */ this.graphql = false; } get childTemplate() { return this.el.querySelector('template'); } get childScript() { return this.el.querySelector('script'); } componentWillLoad() { this.dataSubscription = new CommonStateSubscriber(this, 'dataEnabled', DATA_EVENTS.DataChanged); this.routeSubscription = new CommonStateSubscriber(this, 'routingEnabled', ROUTE_EVENTS.RouteChanged); if (this.childTemplate !== null) { this.innerTemplate = this.childTemplate.innerHTML; } } async componentWillRender() { let shouldRender = !this.deferLoad; if (shouldRender && this.when && commonState.dataEnabled) { const { evaluatePredicate } = await import('../../services/data/expressions'); shouldRender = await evaluatePredicate(this.when); } if (shouldRender) this.contentElement = await this.resolveContentElement(); else this.contentElement = null; } async resolveContentElement() { var _a; const content = await this.getContent(); if (content == null) return null; const container = document.createElement(this.innerTemplate ? 'div' : 'span'); container.innerHTML = content; container.className = this.contentClass; if (commonState.elementsEnabled) { resolveChildElementXAttributes(container); } if (routingState.router) { (_a = routingState.router) === null || _a === void 0 ? void 0 : _a.captureInnerLinks(container); } return container; } async getContent() { let content = null; const data = await this.resolveData(); if (this.innerTemplate) { content = await resolveTokens(this.innerTemplate, false, data); } else if (this.text) { content = await resolveTokens(this.text, false, data); } return content; } async resolveData() { var _a; let data = {}; if (this.el.dataset) { Object.assign(data, this.el.dataset); } if (this.childScript !== null && !this.graphql) { try { const json = JSON.parse(this.childScript.textContent || ''); data = Object.assign(data, json); } catch (error) { warn(`n-content-template: unable to deserialize JSON: ${error}`); } } if (this.src) { try { data = this.graphql ? await fetchJson(window, this.src, this.mode, 'POST', JSON.stringify({ query: dedent(((_a = this.childScript) === null || _a === void 0 ? void 0 : _a.textContent) || ''), })) : await fetchJson(window, this.src, this.mode); if (this.filter) { debugIf(this.debug, `n-content-template: filtering: ${this.filter}`); data = await filterData(this.filter, data); } } catch (error) { warn(`n-content-template: unable to fetch and filter data data ${error}`); } } return data; } render() { replaceHtmlInElement(this.el, `.${this.contentClass}`, this.contentElement); return null; } disconnectedCallback() { this.dataSubscription.destroy(); this.routeSubscription.destroy(); } static get is() { return "n-content-template"; } static get properties() { return { "text": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string | undefined", "references": {} }, "required": false, "optional": true, "docs": { "tags": [{ "name": "default", "text": "null" }], "text": "The data expression to obtain a value for rendering as inner-text for this element." }, "attribute": "text", "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" }, "src": { "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 data to bind to this template" }, "attribute": "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 }, "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 }, "mode": { "type": "string", "mutable": false, "complexType": { "original": "'cors' | 'navigate' | 'no-cors' | 'same-origin'", "resolved": "\"cors\" | \"navigate\" | \"no-cors\" | \"same-origin\"", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Cross Origin Mode" }, "attribute": "mode", "reflect": false, "defaultValue": "'cors'" }, "graphql": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "When declared, the child script tag is required and should be\nthe query text for the request. Also, this forces the HTTP\nmethod to 'POST'." }, "attribute": "graphql", "reflect": false, "defaultValue": "false" } }; } static get states() { return { "innerTemplate": {}, "contentElement": {} }; } static get elementRef() { return "el"; } }