@nent/core
Version:
496 lines (495 loc) • 15 kB
JavaScript
/*!
* NENT 2022
*/
import { Component, Element, h, Host, Method, Prop, State, } from '@stencil/core';
import { commonState, CommonStateSubscriber, debugIf, slugify, } from '../../services/common';
import { warn } from '../../services/common/logging';
import { replaceHtmlInElement } from '../../services/content/elements';
import { resolveRemoteContentElement } from '../../services/content/remote';
import { DATA_EVENTS } from '../../services/data/interfaces';
import { routingState } from '../n-views/services/state';
import { resolveNext } from './services/next';
import { markVisit } from './services/visits';
/**
* The View element holds a segment of content visible only when
* a URL path matches. It defines a route and its content.
* This provides the declarative mechanism
* for in-page content/component routing by URL.
*
* @slot default - The default slot is rendered when this route is
* activated, visible by default to all routes matching
* the route URL (typically, child routes).
*
* @slot content - The content route is rendered only when the route
* matches EXACTLY. Note: This HTML is removed when the
* route changes.
* @system routing
*
* @extension data
* @extension elements
*/
export class View {
constructor() {
this.match = null;
this.exactMatch = false;
this.srcElement = null;
this.contentElement = null;
/**
* The title for this view. This is prefixed
* before the app title configured in n-views
*
*/
this.pageTitle = '';
/**
* The page description for this view.
*
*/
this.pageDescription = '';
/**
* The keywords to add to the keywords meta-tag for this view.
*
*/
this.pageKeywords = '';
/**
* The robots instruction for search indexing
*
*/
this.pageRobots = 'all';
/**
* Header height or offset for scroll-top on this
* view.
*/
this.scrollTopOffset = 0;
/**
* The path for this route should only be matched
* when it is exact.
*/
this.exact = false;
/**
* Cross Origin Mode if the content is pulled from
* a remote location
*/
this.mode = 'cors';
/**
* Before rendering remote HTML, replace any data-tokens with their
* resolved values. This also commands this element to
* re-render it's HTML for data-changes. This can affect
* performance.
*
* IMPORTANT: ONLY WORKS ON REMOTE HTML
*/
this.resolveTokens = false;
/**
* Turn on debug statements for load, update and render events.
*/
this.debug = false;
/**
* Force render with data & route changes.
*/
this.noCache = false;
}
/**
* Return all child elements used for processing. This function is
* primarily meant for testing.
*
*/
async getChildren() {
return {
activators: this.route.actionActivators,
views: this.childViews,
dos: this.childPrompts,
};
}
get parent() {
var _a;
return ((_a = this.el.parentElement) === null || _a === void 0 ? void 0 : _a.closest('n-view')) || null;
}
get childPrompts() {
return Array.from(this.el.querySelectorAll('n-view-prompt') || []).filter(e => this.route.isChild(e));
}
get childViews() {
return Array.from(this.el.querySelectorAll('n-view') || []).filter(e => this.route.isChild(e));
}
async componentWillLoad() {
debugIf(this.debug, `n-view: ${this.path} loading`);
this.contentKey = `rem-content-${slugify(this.contentSrc || 'none')}`;
this.srcKey = `rem-source-${slugify(this.src || 'none')}`;
if (!routingState.router) {
warn(`n-view: ${this.path} cannot load outside of an n-views element`);
return;
}
this.route = routingState.router.createRoute(this.el, this.parent, (match) => {
this.match = match ? Object.assign({}, match) : null;
this.exactMatch = (match === null || match === void 0 ? void 0 : match.isExact) || false;
});
if (commonState.dataEnabled && this.resolveTokens) {
this.dataSubscription = new CommonStateSubscriber(this, 'dataEnabled', DATA_EVENTS.DataChanged);
}
}
async componentWillRender() {
var _a;
debugIf(this.debug, `n-view: ${this.path} will render`);
if (this.match) {
debugIf(this.debug, `n-view: ${this.path} route is matched `);
if (this.src && this.srcElement == null) {
this.srcElement = await resolveRemoteContentElement(window, this.src, this.mode, this.srcKey, this.resolveTokens);
replaceHtmlInElement(this.el, `#${this.srcKey}`, this.srcElement);
}
debugIf(this.debug, `n-view: ${this.path} found ${this.childViews.length} child views and` +
` ${this.childPrompts.length} child view-prompts`);
// exact-match
if (this.match.isExact) {
debugIf(this.debug, `n-view: ${this.path} route exactly matched `);
const viewDos = this.childPrompts.map(el => {
const { path, when, visit } = el;
return {
path: this.route.normalizeChildUrl(path),
when,
visit,
};
});
const nextDo = await resolveNext(viewDos);
if (nextDo) {
this.route.replaceWithRoute(nextDo.path);
return;
}
else {
if (this.contentSrc && this.contentElement == null)
this.contentElement = await resolveRemoteContentElement(window, this.contentSrc, this.mode, this.contentKey, this.resolveTokens, 'content');
markVisit((_a = this.match) === null || _a === void 0 ? void 0 : _a.url);
}
}
}
}
render() {
debugIf(this.debug, `n-view: ${this.path} render`);
replaceHtmlInElement(this.el, `#${this.contentKey}`, this.contentElement);
return (h(Host, null,
h("slot", null),
h("slot", { name: "content" })));
}
async componentDidRender() {
var _a, _b, _c, _d;
if (!((_b = (_a = this.route) === null || _a === void 0 ? void 0 : _a.match) === null || _b === void 0 ? void 0 : _b.isExact)) {
(_c = this.contentElement) === null || _c === void 0 ? void 0 : _c.remove();
if (this.noCache)
this.contentElement = null;
}
await ((_d = this.route) === null || _d === void 0 ? void 0 : _d.loadCompleted());
}
disconnectedCallback() {
var _a, _b;
(_a = this.dataSubscription) === null || _a === void 0 ? void 0 : _a.destroy();
(_b = this.route) === null || _b === void 0 ? void 0 : _b.destroy();
}
static get is() { return "n-view"; }
static get encapsulation() { return "shadow"; }
static get originalStyleUrls() { return {
"$": ["view.css"]
}; }
static get styleUrls() { return {
"$": ["view.css"]
}; }
static get properties() { return {
"route": {
"type": "unknown",
"mutable": true,
"complexType": {
"original": "Route",
"resolved": "Route",
"references": {
"Route": {
"location": "import",
"path": "./services/route"
}
}
},
"required": true,
"optional": false,
"docs": {
"tags": [],
"text": "Route information"
}
},
"pageTitle": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "The title for this view. This is prefixed\nbefore the app title configured in n-views"
},
"attribute": "page-title",
"reflect": false,
"defaultValue": "''"
},
"pageDescription": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "The page description for this view."
},
"attribute": "page-description",
"reflect": false,
"defaultValue": "''"
},
"pageKeywords": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "The keywords to add to the keywords meta-tag for this view."
},
"attribute": "page-keywords",
"reflect": false,
"defaultValue": "''"
},
"pageRobots": {
"type": "string",
"mutable": false,
"complexType": {
"original": "'all' | 'noindex' | 'nofollow' | 'none'",
"resolved": "\"all\" | \"nofollow\" | \"noindex\" | \"none\"",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "The robots instruction for search indexing"
},
"attribute": "page-robots",
"reflect": false,
"defaultValue": "'all'"
},
"scrollTopOffset": {
"type": "number",
"mutable": false,
"complexType": {
"original": "number",
"resolved": "number",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Header height or offset for scroll-top on this\nview."
},
"attribute": "scroll-top-offset",
"reflect": false,
"defaultValue": "0"
},
"transition": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string | undefined",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Navigation transition between routes.\nThis is a CSS animation class."
},
"attribute": "transition",
"reflect": false
},
"path": {
"type": "string",
"mutable": true,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": true,
"optional": false,
"docs": {
"tags": [],
"text": "The path for this route, including the parent's\nroutes, excluding the router's root."
},
"attribute": "path",
"reflect": true
},
"exact": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "The path for this route should only be matched\nwhen it is exact."
},
"attribute": "exact",
"reflect": false,
"defaultValue": "false"
},
"src": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string | undefined",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Remote URL for this route's HTML. HTML from this\nURL will be not be assigned to any slot.\n\nYou can add slot='content' to any containers\nwithin this HTML if you have a mix of HTML for\nthis exact-route and its children."
},
"attribute": "src",
"reflect": false
},
"contentSrc": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string | undefined",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Remote URL for this Route's content."
},
"attribute": "content-src",
"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 if the content is pulled from\na remote location"
},
"attribute": "mode",
"reflect": false,
"defaultValue": "'cors'"
},
"resolveTokens": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Before rendering remote HTML, replace any data-tokens with their\nresolved values. This also commands this element to\nre-render it's HTML for data-changes. This can affect\nperformance.\n\nIMPORTANT: ONLY WORKS ON REMOTE HTML"
},
"attribute": "resolve-tokens",
"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"
}
}; }
static get states() { return {
"match": {},
"exactMatch": {}
}; }
static get methods() { return {
"getChildren": {
"complexType": {
"signature": "() => Promise<{ activators: HTMLNActionActivatorElement[]; views: HTMLNViewElement[]; dos: HTMLNViewPromptElement[]; }>",
"parameters": [],
"references": {
"Promise": {
"location": "global"
},
"HTMLNActionActivatorElement": {
"location": "global"
},
"HTMLNViewElement": {
"location": "global"
},
"HTMLNViewPromptElement": {
"location": "global"
}
},
"return": "Promise<{ activators: HTMLNActionActivatorElement[]; views: HTMLNViewElement[]; dos: HTMLNViewPromptElement[]; }>"
},
"docs": {
"text": "Return all child elements used for processing. This function is\nprimarily meant for testing.",
"tags": []
}
}
}; }
static get elementRef() { return "el"; }
}