304 lines
13 KiB
TypeScript
Raw Normal View History

/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {validateAgainstEventAttributes} from '../../sanitization/sanitization';
import {assertDataInRange, assertEqual} from '../../util/assert';
import {assertHasParent} from '../assert';
import {attachPatchData} from '../context_discovery';
import {registerPostOrderHooks} from '../hooks';
import {TAttributes, TNodeFlags, TNodeType} from '../interfaces/node';
import {RElement, isProceduralRenderer} from '../interfaces/renderer';
import {SanitizerFn} from '../interfaces/sanitization';
import {StylingContext} from '../interfaces/styling';
import {BINDING_INDEX, QUERIES, RENDERER, TVIEW} from '../interfaces/view';
import {assertNodeType} from '../node_assert';
import {appendChild} from '../node_manipulation';
import {applyOnCreateInstructions} from '../node_util';
import {decreaseElementDepthCount, getElementDepthCount, getIsParent, getLView, getPreviousOrParentTNode, getSelectedIndex, increaseElementDepthCount, setIsParent, setPreviousOrParentTNode} from '../state';
import {getInitialClassNameValue, getInitialStyleStringValue, initializeStaticContext, patchContextWithStaticAttrs, renderInitialClasses, renderInitialStyles} from '../styling/class_and_style_bindings';
import {getStylingContextFromLView, hasClassInput, hasStyleInput} from '../styling/util';
import {registerInitialStylingIntoContext} from '../styling_next/instructions';
import {runtimeIsNewStylingInUse} from '../styling_next/state';
import {NO_CHANGE} from '../tokens';
import {attrsStylingIndexOf, setUpAttributes} from '../util/attrs_utils';
import {renderStringify} from '../util/misc_utils';
import {getNativeByIndex, getNativeByTNode, getTNode} from '../util/view_utils';
import {createDirectivesAndLocals, createNodeAtIndex, elementCreate, executeContentQueries, initializeTNodeInputs, setInputsForProperty, setNodeStylingTemplate} from './shared';
import {getActiveDirectiveStylingIndex} from './styling';
/**
* Create DOM element. The instruction must later be followed by `elementEnd()` call.
*
* @param index Index of the element in the LView array
* @param name Name of the DOM Node
* @param attrs Statically bound set of attributes, classes, and styles to be written into the DOM
* element on creation. Use [AttributeMarker] to denote the meaning of this array.
* @param localRefs A set of local reference bindings on the element.
*
* Attributes and localRefs are passed as an array of strings where elements with an even index
* hold an attribute name and elements with an odd index hold an attribute value, ex.:
* ['id', 'warning5', 'class', 'alert']
*
* @codeGenApi
*/
export function ΔelementStart(
index: number, name: string, attrs?: TAttributes | null, localRefs?: string[] | null): void {
const lView = getLView();
const tView = lView[TVIEW];
ngDevMode && assertEqual(
lView[BINDING_INDEX], tView.bindingStartIndex,
'elements should be created before any bindings ');
ngDevMode && ngDevMode.rendererCreateElement++;
const native = elementCreate(name);
const renderer = lView[RENDERER];
ngDevMode && assertDataInRange(lView, index - 1);
const tNode = createNodeAtIndex(index, TNodeType.Element, native !, name, attrs || null);
let initialStylesIndex = 0;
let initialClassesIndex = 0;
let lastAttrIndex = -1;
if (attrs) {
lastAttrIndex = setUpAttributes(native, attrs);
// it's important to only prepare styling-related datastructures once for a given
// tNode and not each time an element is created. Also, the styling code is designed
// to be patched and constructed at various points, but only up until the styling
// template is first allocated (which happens when the very first style/class binding
// value is evaluated). When the template is allocated (when it turns into a context)
// then the styling template is locked and cannot be further extended (it can only be
// instantiated into a context per element)
setNodeStylingTemplate(tView, tNode, attrs, lastAttrIndex);
if (tNode.stylingTemplate) {
// the initial style/class values are rendered immediately after having been
// initialized into the context so the element styling is ready when directives
// are initialized (since they may read style/class values in their constructor)
initialStylesIndex = renderInitialStyles(native, tNode.stylingTemplate, renderer);
initialClassesIndex = renderInitialClasses(native, tNode.stylingTemplate, renderer);
}
}
appendChild(native, tNode, lView);
createDirectivesAndLocals(tView, lView, localRefs);
// any immediate children of a component or template container must be pre-emptively
// monkey-patched with the component view data so that the element can be inspected
// later on using any element discovery utility methods (see `element_discovery.ts`)
if (getElementDepthCount() === 0) {
attachPatchData(native, lView);
}
increaseElementDepthCount();
// if a directive contains a host binding for "class" then all class-based data will
// flow through that (except for `[class.prop]` bindings). This also includes initial
// static class values as well. (Note that this will be fixed once map-based `[style]`
// and `[class]` bindings work for multiple directives.)
if (tView.firstTemplatePass) {
const inputData = initializeTNodeInputs(tNode);
if (inputData && inputData.hasOwnProperty('class')) {
tNode.flags |= TNodeFlags.hasClassInput;
}
if (inputData && inputData.hasOwnProperty('style')) {
tNode.flags |= TNodeFlags.hasStyleInput;
}
}
// we render the styling again below in case any directives have set any `style` and/or
// `class` host attribute values...
if (tNode.stylingTemplate) {
renderInitialClasses(native, tNode.stylingTemplate, renderer, initialClassesIndex);
renderInitialStyles(native, tNode.stylingTemplate, renderer, initialStylesIndex);
}
if (runtimeIsNewStylingInUse() && lastAttrIndex >= 0) {
registerInitialStylingIntoContext(tNode, attrs as TAttributes, lastAttrIndex);
}
const currentQueries = lView[QUERIES];
if (currentQueries) {
currentQueries.addNode(tNode);
lView[QUERIES] = currentQueries.clone();
}
executeContentQueries(tView, tNode, lView);
}
/**
* Mark the end of the element.
*
* @codeGenApi
*/
export function ΔelementEnd(): void {
let previousOrParentTNode = getPreviousOrParentTNode();
if (getIsParent()) {
setIsParent(false);
} else {
ngDevMode && assertHasParent(getPreviousOrParentTNode());
previousOrParentTNode = previousOrParentTNode.parent !;
setPreviousOrParentTNode(previousOrParentTNode);
}
// this is required for all host-level styling-related instructions to run
// in the correct order
previousOrParentTNode.onElementCreationFns && applyOnCreateInstructions(previousOrParentTNode);
ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.Element);
const lView = getLView();
const currentQueries = lView[QUERIES];
if (currentQueries) {
lView[QUERIES] = currentQueries.parent;
}
registerPostOrderHooks(getLView()[TVIEW], previousOrParentTNode);
decreaseElementDepthCount();
// this is fired at the end of elementEnd because ALL of the stylingBindings code
// (for directives and the template) have now executed which means the styling
// context can be instantiated properly.
let stylingContext: StylingContext|null = null;
if (hasClassInput(previousOrParentTNode)) {
stylingContext = getStylingContextFromLView(previousOrParentTNode.index, lView);
setInputsForProperty(
lView, previousOrParentTNode.inputs !['class'] !, getInitialClassNameValue(stylingContext));
}
if (hasStyleInput(previousOrParentTNode)) {
stylingContext =
stylingContext || getStylingContextFromLView(previousOrParentTNode.index, lView);
setInputsForProperty(
lView, previousOrParentTNode.inputs !['style'] !,
getInitialStyleStringValue(stylingContext));
}
}
/**
* Creates an empty element using {@link elementStart} and {@link elementEnd}
*
* @param index Index of the element in the data array
* @param name Name of the DOM Node
* @param attrs Statically bound set of attributes, classes, and styles to be written into the DOM
* element on creation. Use [AttributeMarker] to denote the meaning of this array.
* @param localRefs A set of local reference bindings on the element.
*
* @codeGenApi
*/
export function Δelement(
index: number, name: string, attrs?: TAttributes | null, localRefs?: string[] | null): void {
ΔelementStart(index, name, attrs, localRefs);
ΔelementEnd();
}
/**
* Updates the value of removes an attribute on an Element.
*
* @param number index The index of the element in the data array
* @param name name The name of the attribute.
* @param value value The attribute is removed when value is `null` or `undefined`.
* Otherwise the attribute value is set to the stringified value.
* @param sanitizer An optional function used to sanitize the value.
* @param namespace Optional namespace to use when setting the attribute.
*
* @codeGenApi
*/
export function ΔelementAttribute(
index: number, name: string, value: any, sanitizer?: SanitizerFn | null,
namespace?: string): void {
if (value !== NO_CHANGE) {
ngDevMode && validateAgainstEventAttributes(name);
const lView = getLView();
const renderer = lView[RENDERER];
const element = getNativeByIndex(index, lView) as RElement;
if (value == null) {
ngDevMode && ngDevMode.rendererRemoveAttribute++;
isProceduralRenderer(renderer) ? renderer.removeAttribute(element, name, namespace) :
element.removeAttribute(name);
} else {
ngDevMode && ngDevMode.rendererSetAttribute++;
const tNode = getTNode(index, lView);
const strValue =
sanitizer == null ? renderStringify(value) : sanitizer(value, tNode.tagName || '', name);
if (isProceduralRenderer(renderer)) {
renderer.setAttribute(element, name, strValue, namespace);
} else {
namespace ? element.setAttributeNS(namespace, name, strValue) :
element.setAttribute(name, strValue);
}
}
}
}
/**
* Assign static attribute values to a host element.
*
* This instruction will assign static attribute values as well as class and style
* values to an element within the host bindings function. Since attribute values
* can consist of different types of values, the `attrs` array must include the values in
* the following format:
*
* attrs = [
* // static attributes (like `title`, `name`, `id`...)
* attr1, value1, attr2, value,
*
* // a single namespace value (like `x:id`)
* NAMESPACE_MARKER, namespaceUri1, name1, value1,
*
* // another single namespace value (like `x:name`)
* NAMESPACE_MARKER, namespaceUri2, name2, value2,
*
* // a series of CSS classes that will be applied to the element (no spaces)
* CLASSES_MARKER, class1, class2, class3,
*
* // a series of CSS styles (property + value) that will be applied to the element
* STYLES_MARKER, prop1, value1, prop2, value2
* ]
*
* All non-class and non-style attributes must be defined at the start of the list
* first before all class and style values are set. When there is a change in value
* type (like when classes and styles are introduced) a marker must be used to separate
* the entries. The marker values themselves are set via entries found in the
* [AttributeMarker] enum.
*
* NOTE: This instruction is meant to used from `hostBindings` function only.
*
* @param directive A directive instance the styling is associated with.
* @param attrs An array of static values (attributes, classes and styles) with the correct marker
* values.
*
* @codeGenApi
*/
export function ΔelementHostAttrs(attrs: TAttributes) {
const hostElementIndex = getSelectedIndex();
const lView = getLView();
const tNode = getTNode(hostElementIndex, lView);
// non-element nodes (e.g. `<ng-container>`) are not rendered as actual
// element nodes and adding styles/classes on to them will cause runtime
// errors...
if (tNode.type === TNodeType.Element) {
const native = getNativeByTNode(tNode, lView) as RElement;
const lastAttrIndex = setUpAttributes(native, attrs);
const stylingAttrsStartIndex = attrsStylingIndexOf(attrs, lastAttrIndex);
if (stylingAttrsStartIndex >= 0) {
const directiveStylingIndex = getActiveDirectiveStylingIndex();
if (tNode.stylingTemplate) {
patchContextWithStaticAttrs(
tNode.stylingTemplate, attrs, stylingAttrsStartIndex, directiveStylingIndex);
} else {
tNode.stylingTemplate =
initializeStaticContext(attrs, stylingAttrsStartIndex, directiveStylingIndex);
}
}
}
}