@servicenow/ui-renderer-snabbdom
Version:
Snabbdom renderer for UI Framework on Next Experience
122 lines (113 loc) • 4.85 kB
JavaScript
/**
* Copyright (c) 2020 ServiceNow, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import { createInteractionId, setInteractionId, mark, types as metrics } from '@servicenow/ui-metrics';
import { errorTypes, locations, toLower, allLogTypes as logTypes } from '@servicenow/ui-internal';
import { patch } from './utils';
import { VNODE, INTERACTION_ID } from './symbols';
import createElementFromNode from './createElementFromNode';
import log from './log';
const noop = () => null;
const helperNoop = Object.assign(noop, {
dispatch: noop,
updateState: noop,
updateProperties: noop
});
/**
* Renders a view using Snabbdom
* @method render
* @param {HTMLElement} element Element to append rendered view to
* @param {Function} handler Render function
* @param {Object} [props = {}] Optional properties object
* @param {Object } [helpers] Optional object containing helpful functions, such as dispatch, updateState, and updateProperties
* @param {Function} [helpers.dispatch] Optional function for dispatching actions
* @param {Function} [helpers.updateState] Optional function for updating component state
* @param {Function} [helpers.updateProperties] Optional function for updating component properties
* @param {String} [interactionId] Optional identifier used by ui-metrics for instrumentation
* @returns {VNode}
*/
export default function onStateChange(element, handler, props = {}, helpers = helperNoop, interactionId = createInteractionId()) {
// The renderer is sometimes used to render DOM to native elements, typically in test fixtures
// or example code. Since we're not in a shadowRoot, we're not in a framework element, we
// shouldn't mark as context is not complete, e.g. componentId, componentName, etc are missing
// from `element.host`.
const isShadowRoot = element instanceof ShadowRoot;
try {
const result = handler(props, helpers);
const prev = element[VNODE] || createElementFromNode(element);
const styles = [];
const {
children
} = prev;
const {
length: childrenCount
} = children;
for (let i = 0; i < childrenCount; i++) {
const child = children[i];
if (child && typeof child.sel === 'string' && (child.sel.startsWith('style') || child.sel.startsWith('link'))) styles.push(child);
}
const next = { ...prev,
children: [].concat(result, styles)
};
const {
host = {}
} = element;
host[INTERACTION_ID] = interactionId; // eslint-disable-line no-param-reassign
if (isShadowRoot) mark(host, interactionId, metrics.PATCH_START, {
location: locations.VIEW
});
const vnode = patch(prev, next);
if (isShadowRoot) mark(host, interactionId, metrics.PATCH_END, {
location: locations.VIEW
});
element[VNODE] = vnode; // eslint-disable-line no-param-reassign
delete host[INTERACTION_ID]; // eslint-disable-line no-param-reassign
return vnode;
} catch (e) {
const {
host = {}
} = element;
const metadata = setInteractionId({
internal: true
}, interactionId);
if (isShadowRoot) mark(host, interactionId, metrics.ERROR, {
location: locations.VIEW
});
helpers.dispatch(errorTypes.COMPONENT_ERROR_THROWN, {
host,
location: locations.VIEW,
error: e,
details: {
boundaryError: true
}
}, metadata, true);
const tagName = toLower(host.tagName);
const componentId = host.getAttribute('component-id');
log(`An Error occurred in <${tagName} component-id="${componentId}"/>. COMPONENT_ERROR_THROWN action type is dispatched with error details.`, {
error: e,
level: logTypes.ERROR,
host,
origin: 'onStateChange'
});
return createElementFromNode(element);
}
}
//# sourceMappingURL=onStateChange.js.map