@nent/core
Version:
320 lines (319 loc) • 9.5 kB
JavaScript
/*!
* 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"; }
}