2019-04-01 15:36:43 -07:00
|
|
|
/**
|
|
|
|
* @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';
|
2019-04-02 16:16:00 -07:00
|
|
|
import {RElement, isProceduralRenderer} from '../interfaces/renderer';
|
2019-04-01 15:36:43 -07:00
|
|
|
import {SanitizerFn} from '../interfaces/sanitization';
|
2019-04-10 11:54:59 -07:00
|
|
|
import {StylingContext} from '../interfaces/styling';
|
2019-04-01 15:36:43 -07:00
|
|
|
import {BINDING_INDEX, QUERIES, RENDERER, TVIEW} from '../interfaces/view';
|
|
|
|
import {assertNodeType} from '../node_assert';
|
|
|
|
import {appendChild} from '../node_manipulation';
|
|
|
|
import {applyOnCreateInstructions} from '../node_util';
|
2019-04-18 14:22:32 +02:00
|
|
|
import {decreaseElementDepthCount, getElementDepthCount, getIsParent, getLView, getPreviousOrParentTNode, getSelectedIndex, increaseElementDepthCount, setIsParent, setPreviousOrParentTNode} from '../state';
|
2019-04-01 15:36:43 -07:00
|
|
|
import {getInitialClassNameValue, getInitialStyleStringValue, initializeStaticContext, patchContextWithStaticAttrs, renderInitialClasses, renderInitialStyles} from '../styling/class_and_style_bindings';
|
2019-04-10 11:54:59 -07:00
|
|
|
import {getStylingContextFromLView, hasClassInput, hasStyleInput} from '../styling/util';
|
2019-05-08 16:30:28 -07:00
|
|
|
import {registerInitialStylingIntoContext} from '../styling_next/instructions';
|
|
|
|
import {runtimeIsNewStylingInUse} from '../styling_next/state';
|
2019-04-01 15:36:43 -07:00
|
|
|
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';
|
2019-04-08 09:20:15 -07:00
|
|
|
import {getActiveDirectiveStylingIndex} from './styling';
|
2019-04-02 16:16:00 -07:00
|
|
|
|
2019-04-01 15:36:43 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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']
|
2019-04-04 11:41:52 -07:00
|
|
|
*
|
2019-04-10 13:45:26 -07:00
|
|
|
* @codeGenApi
|
2019-04-01 15:36:43 -07:00
|
|
|
*/
|
2019-05-09 11:47:25 -07:00
|
|
|
export function ΔelementStart(
|
2019-04-01 15:36:43 -07:00
|
|
|
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;
|
|
|
|
|
2019-05-08 16:30:28 -07:00
|
|
|
let lastAttrIndex = -1;
|
2019-04-01 15:36:43 -07:00
|
|
|
if (attrs) {
|
2019-05-08 16:30:28 -07:00
|
|
|
lastAttrIndex = setUpAttributes(native, attrs);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
2019-05-08 16:30:28 -07:00
|
|
|
if (runtimeIsNewStylingInUse() && lastAttrIndex >= 0) {
|
|
|
|
registerInitialStylingIntoContext(tNode, attrs as TAttributes, lastAttrIndex);
|
|
|
|
}
|
|
|
|
|
2019-04-01 15:36:43 -07:00
|
|
|
const currentQueries = lView[QUERIES];
|
|
|
|
if (currentQueries) {
|
|
|
|
currentQueries.addNode(tNode);
|
|
|
|
lView[QUERIES] = currentQueries.clone();
|
|
|
|
}
|
|
|
|
executeContentQueries(tView, tNode, lView);
|
|
|
|
}
|
|
|
|
|
2019-04-04 11:41:52 -07:00
|
|
|
/**
|
|
|
|
* Mark the end of the element.
|
|
|
|
*
|
2019-04-10 13:45:26 -07:00
|
|
|
* @codeGenApi
|
2019-04-04 11:41:52 -07:00
|
|
|
*/
|
2019-05-09 11:47:25 -07:00
|
|
|
export function ΔelementEnd(): void {
|
2019-04-01 15:36:43 -07:00
|
|
|
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.
|
2019-04-10 11:54:59 -07:00
|
|
|
let stylingContext: StylingContext|null = null;
|
2019-04-01 15:36:43 -07:00
|
|
|
if (hasClassInput(previousOrParentTNode)) {
|
2019-04-10 11:54:59 -07:00
|
|
|
stylingContext = getStylingContextFromLView(previousOrParentTNode.index, lView);
|
2019-04-01 15:36:43 -07:00
|
|
|
setInputsForProperty(
|
|
|
|
lView, previousOrParentTNode.inputs !['class'] !, getInitialClassNameValue(stylingContext));
|
|
|
|
}
|
|
|
|
if (hasStyleInput(previousOrParentTNode)) {
|
2019-04-10 11:54:59 -07:00
|
|
|
stylingContext =
|
|
|
|
stylingContext || getStylingContextFromLView(previousOrParentTNode.index, lView);
|
2019-04-01 15:36:43 -07:00
|
|
|
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.
|
2019-04-04 11:41:52 -07:00
|
|
|
*
|
2019-04-10 13:45:26 -07:00
|
|
|
* @codeGenApi
|
2019-04-01 15:36:43 -07:00
|
|
|
*/
|
2019-05-09 11:47:25 -07:00
|
|
|
export function Δelement(
|
2019-04-01 15:36:43 -07:00
|
|
|
index: number, name: string, attrs?: TAttributes | null, localRefs?: string[] | null): void {
|
2019-05-09 11:47:25 -07:00
|
|
|
ΔelementStart(index, name, attrs, localRefs);
|
|
|
|
ΔelementEnd();
|
2019-04-01 15:36:43 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
2019-04-04 11:41:52 -07:00
|
|
|
*
|
2019-04-10 13:45:26 -07:00
|
|
|
* @codeGenApi
|
2019-04-01 15:36:43 -07:00
|
|
|
*/
|
2019-05-09 11:47:25 -07:00
|
|
|
export function ΔelementAttribute(
|
2019-04-01 15:36:43 -07:00
|
|
|
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.
|
|
|
|
*
|
2019-04-10 13:45:26 -07:00
|
|
|
* @codeGenApi
|
2019-04-01 15:36:43 -07:00
|
|
|
*/
|
2019-05-09 11:47:25 -07:00
|
|
|
export function ΔelementHostAttrs(attrs: TAttributes) {
|
2019-04-02 16:16:00 -07:00
|
|
|
const hostElementIndex = getSelectedIndex();
|
2019-04-01 15:36:43 -07:00
|
|
|
const lView = getLView();
|
2019-04-02 16:16:00 -07:00
|
|
|
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);
|
|
|
|
}
|
2019-04-01 15:36:43 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|