2017-01-20 13:10:57 -08: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
|
|
|
|
*/
|
|
|
|
|
2017-01-26 17:07:37 -08:00
|
|
|
import {isDevMode} from '../application_ref';
|
2017-01-20 13:10:57 -08:00
|
|
|
import {SecurityContext} from '../security';
|
|
|
|
|
2017-02-01 11:32:27 -08:00
|
|
|
import {BindingDef, BindingType, DebugContext, DisposableFn, ElementData, ElementOutputDef, EntryAction, NodeData, NodeDef, NodeFlags, NodeType, QueryValueType, Refs, ViewData, ViewDefinition, ViewFlags, asElementData} from './types';
|
2017-01-31 14:52:01 -08:00
|
|
|
import {checkAndUpdateBinding, dispatchEvent, entryAction, setBindingDebugInfo, setCurrentNode, sliceErrorStack, unwrapValue} from './util';
|
2017-01-20 13:10:57 -08:00
|
|
|
|
2017-01-20 09:21:09 -08:00
|
|
|
export function anchorDef(
|
2017-01-31 08:51:42 -08:00
|
|
|
flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number,
|
|
|
|
childCount: number, template?: ViewDefinition): NodeDef {
|
2017-01-23 16:59:20 -08:00
|
|
|
const matchedQueryDefs: {[queryId: string]: QueryValueType} = {};
|
|
|
|
if (matchedQueries) {
|
|
|
|
matchedQueries.forEach(([queryId, valueType]) => { matchedQueryDefs[queryId] = valueType; });
|
|
|
|
}
|
2017-01-26 17:07:37 -08:00
|
|
|
// skip the call to sliceErrorStack itself + the call to this function.
|
|
|
|
const source = isDevMode() ? sliceErrorStack(2, 3) : '';
|
2017-01-20 09:21:09 -08:00
|
|
|
return {
|
|
|
|
type: NodeType.Element,
|
|
|
|
// will bet set by the view definition
|
|
|
|
index: undefined,
|
|
|
|
reverseChildIndex: undefined,
|
|
|
|
parent: undefined,
|
|
|
|
childFlags: undefined,
|
2017-01-23 16:59:20 -08:00
|
|
|
childMatchedQueries: undefined,
|
2017-01-20 09:21:09 -08:00
|
|
|
bindingIndex: undefined,
|
|
|
|
disposableIndex: undefined,
|
|
|
|
// regular values
|
|
|
|
flags,
|
2017-01-31 08:51:42 -08:00
|
|
|
matchedQueries: matchedQueryDefs, ngContentIndex, childCount,
|
2017-01-20 09:21:09 -08:00
|
|
|
bindings: [],
|
|
|
|
disposableCount: 0,
|
2017-01-25 13:45:07 -08:00
|
|
|
element: {
|
|
|
|
name: undefined,
|
|
|
|
attrs: undefined,
|
|
|
|
outputs: [], template,
|
|
|
|
// will bet set by the view definition
|
2017-01-31 08:51:42 -08:00
|
|
|
providerIndices: undefined, source,
|
2017-01-25 13:45:07 -08:00
|
|
|
},
|
2017-01-20 09:21:09 -08:00
|
|
|
provider: undefined,
|
|
|
|
text: undefined,
|
2017-01-25 13:45:07 -08:00
|
|
|
pureExpression: undefined,
|
|
|
|
query: undefined,
|
2017-01-31 08:51:42 -08:00
|
|
|
ngContent: undefined
|
2017-01-20 09:21:09 -08:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-01-20 13:10:57 -08:00
|
|
|
export function elementDef(
|
2017-01-31 08:51:42 -08:00
|
|
|
flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number,
|
|
|
|
childCount: number, name: string, fixedAttrs: {[name: string]: string} = {},
|
2017-01-19 10:25:03 -08:00
|
|
|
bindings?:
|
|
|
|
([BindingType.ElementClass, string] | [BindingType.ElementStyle, string, string] |
|
|
|
|
[BindingType.ElementAttribute | BindingType.ElementProperty, string, SecurityContext])[],
|
|
|
|
outputs?: (string | [string, string])[]): NodeDef {
|
2017-01-26 17:07:37 -08:00
|
|
|
// skip the call to sliceErrorStack itself + the call to this function.
|
|
|
|
const source = isDevMode() ? sliceErrorStack(2, 3) : '';
|
2017-01-23 16:59:20 -08:00
|
|
|
const matchedQueryDefs: {[queryId: string]: QueryValueType} = {};
|
|
|
|
if (matchedQueries) {
|
|
|
|
matchedQueries.forEach(([queryId, valueType]) => { matchedQueryDefs[queryId] = valueType; });
|
|
|
|
}
|
2017-01-19 10:25:03 -08:00
|
|
|
bindings = bindings || [];
|
2017-01-20 13:10:57 -08:00
|
|
|
const bindingDefs = new Array(bindings.length);
|
|
|
|
for (let i = 0; i < bindings.length; i++) {
|
|
|
|
const entry = bindings[i];
|
|
|
|
let bindingDef: BindingDef;
|
|
|
|
const bindingType = entry[0];
|
|
|
|
const name = entry[1];
|
|
|
|
let securityContext: SecurityContext;
|
|
|
|
let suffix: string;
|
|
|
|
switch (bindingType) {
|
|
|
|
case BindingType.ElementStyle:
|
|
|
|
suffix = <string>entry[2];
|
|
|
|
break;
|
|
|
|
case BindingType.ElementAttribute:
|
|
|
|
case BindingType.ElementProperty:
|
|
|
|
securityContext = <SecurityContext>entry[2];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
bindingDefs[i] = {type: bindingType, name, nonMinfiedName: name, securityContext, suffix};
|
|
|
|
}
|
2017-01-19 10:25:03 -08:00
|
|
|
outputs = outputs || [];
|
|
|
|
const outputDefs: ElementOutputDef[] = new Array(outputs.length);
|
|
|
|
for (let i = 0; i < outputs.length; i++) {
|
|
|
|
const output = outputs[i];
|
|
|
|
let target: string;
|
|
|
|
let eventName: string;
|
|
|
|
if (Array.isArray(output)) {
|
|
|
|
[target, eventName] = output;
|
|
|
|
} else {
|
|
|
|
eventName = output;
|
|
|
|
}
|
|
|
|
outputDefs[i] = {eventName: eventName, target: target};
|
|
|
|
}
|
2017-01-20 13:10:57 -08:00
|
|
|
return {
|
|
|
|
type: NodeType.Element,
|
|
|
|
// will bet set by the view definition
|
|
|
|
index: undefined,
|
|
|
|
reverseChildIndex: undefined,
|
|
|
|
parent: undefined,
|
|
|
|
childFlags: undefined,
|
2017-01-23 16:59:20 -08:00
|
|
|
childMatchedQueries: undefined,
|
2017-01-20 13:10:57 -08:00
|
|
|
bindingIndex: undefined,
|
2017-01-19 10:25:03 -08:00
|
|
|
disposableIndex: undefined,
|
2017-01-20 13:10:57 -08:00
|
|
|
// regular values
|
|
|
|
flags,
|
2017-01-31 08:51:42 -08:00
|
|
|
matchedQueries: matchedQueryDefs, ngContentIndex, childCount,
|
2017-01-20 13:10:57 -08:00
|
|
|
bindings: bindingDefs,
|
2017-01-19 10:25:03 -08:00
|
|
|
disposableCount: outputDefs.length,
|
2017-01-25 13:45:07 -08:00
|
|
|
element: {
|
|
|
|
name,
|
|
|
|
attrs: fixedAttrs,
|
|
|
|
outputs: outputDefs,
|
|
|
|
template: undefined,
|
|
|
|
// will bet set by the view definition
|
2017-01-31 08:51:42 -08:00
|
|
|
providerIndices: undefined, source,
|
2017-01-25 13:45:07 -08:00
|
|
|
},
|
2017-01-20 13:10:57 -08:00
|
|
|
provider: undefined,
|
|
|
|
text: undefined,
|
2017-01-25 13:45:07 -08:00
|
|
|
pureExpression: undefined,
|
|
|
|
query: undefined,
|
2017-01-31 08:51:42 -08:00
|
|
|
ngContent: undefined
|
2017-01-20 13:10:57 -08:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-01-25 13:45:07 -08:00
|
|
|
export function createElement(view: ViewData, renderHost: any, def: NodeDef): ElementData {
|
2017-01-20 13:10:57 -08:00
|
|
|
const elDef = def.element;
|
2017-02-01 11:32:27 -08:00
|
|
|
const rootSelectorOrNode = view.root.selectorOrNode;
|
2017-01-20 13:10:57 -08:00
|
|
|
let el: any;
|
2017-02-01 11:32:27 -08:00
|
|
|
if (view.parent || !rootSelectorOrNode) {
|
|
|
|
const parentNode =
|
|
|
|
def.parent != null ? asElementData(view, def.parent).renderElement : renderHost;
|
|
|
|
if (view.renderer) {
|
|
|
|
const debugContext = isDevMode() ? Refs.createDebugContext(view, def.index) : undefined;
|
|
|
|
el = elDef.name ? view.renderer.createElement(parentNode, elDef.name, debugContext) :
|
|
|
|
view.renderer.createTemplateAnchor(parentNode, debugContext);
|
|
|
|
} else {
|
|
|
|
el = elDef.name ? document.createElement(elDef.name) : document.createComment('');
|
|
|
|
if (parentNode) {
|
|
|
|
parentNode.appendChild(el);
|
|
|
|
}
|
|
|
|
}
|
2017-01-20 13:10:57 -08:00
|
|
|
} else {
|
2017-02-01 11:32:27 -08:00
|
|
|
if (view.renderer) {
|
|
|
|
const debugContext = isDevMode() ? Refs.createDebugContext(view, def.index) : undefined;
|
|
|
|
el = view.renderer.selectRootElement(rootSelectorOrNode, debugContext);
|
|
|
|
} else {
|
|
|
|
el = typeof rootSelectorOrNode === 'string' ? document.querySelector(rootSelectorOrNode) :
|
|
|
|
rootSelectorOrNode;
|
|
|
|
el.textContent = '';
|
2017-01-20 13:10:57 -08:00
|
|
|
}
|
2017-01-19 10:25:03 -08:00
|
|
|
}
|
|
|
|
if (elDef.attrs) {
|
|
|
|
for (let attrName in elDef.attrs) {
|
|
|
|
if (view.renderer) {
|
|
|
|
view.renderer.setElementAttribute(el, attrName, elDef.attrs[attrName]);
|
|
|
|
} else {
|
2017-01-20 13:10:57 -08:00
|
|
|
el.setAttribute(attrName, elDef.attrs[attrName]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-01-19 10:25:03 -08:00
|
|
|
if (elDef.outputs.length) {
|
|
|
|
for (let i = 0; i < elDef.outputs.length; i++) {
|
|
|
|
const output = elDef.outputs[i];
|
|
|
|
let disposable: DisposableFn;
|
|
|
|
if (view.renderer) {
|
|
|
|
const handleEventClosure = renderEventHandlerClosure(view, def.index, output.eventName);
|
|
|
|
if (output.target) {
|
|
|
|
disposable =
|
|
|
|
<any>view.renderer.listenGlobal(output.target, output.eventName, handleEventClosure);
|
|
|
|
} else {
|
|
|
|
disposable = <any>view.renderer.listen(el, output.eventName, handleEventClosure);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
let target: any;
|
|
|
|
switch (output.target) {
|
|
|
|
case 'window':
|
|
|
|
target = window;
|
|
|
|
break;
|
|
|
|
case 'document':
|
|
|
|
target = document;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
target = el;
|
|
|
|
}
|
|
|
|
const handleEventClosure = directDomEventHandlerClosure(view, def.index, output.eventName);
|
|
|
|
target.addEventListener(output.eventName, handleEventClosure);
|
|
|
|
disposable = target.removeEventListener.bind(target, output.eventName, handleEventClosure);
|
|
|
|
}
|
|
|
|
view.disposables[def.disposableIndex + i] = disposable;
|
|
|
|
}
|
|
|
|
}
|
2017-01-20 13:10:57 -08:00
|
|
|
return {
|
2017-01-25 13:45:07 -08:00
|
|
|
renderElement: el,
|
|
|
|
embeddedViews: (def.flags & NodeFlags.HasEmbeddedViews) ? [] : undefined,
|
|
|
|
projectedViews: undefined
|
2017-01-20 13:10:57 -08:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-01-19 10:25:03 -08:00
|
|
|
function renderEventHandlerClosure(view: ViewData, index: number, eventName: string) {
|
2017-01-31 14:52:01 -08:00
|
|
|
return entryAction(
|
|
|
|
EntryAction.HandleEvent, (event: any) => dispatchEvent(view, index, eventName, event));
|
2017-01-19 10:25:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function directDomEventHandlerClosure(view: ViewData, index: number, eventName: string) {
|
2017-01-26 17:07:37 -08:00
|
|
|
return entryAction(EntryAction.HandleEvent, (event: any) => {
|
2017-01-31 14:52:01 -08:00
|
|
|
const result = dispatchEvent(view, index, eventName, event);
|
2017-01-19 10:25:03 -08:00
|
|
|
if (result === false) {
|
|
|
|
event.preventDefault();
|
|
|
|
}
|
|
|
|
return result;
|
2017-01-26 17:07:37 -08:00
|
|
|
});
|
2017-01-19 10:25:03 -08:00
|
|
|
}
|
|
|
|
|
2017-01-20 13:10:57 -08:00
|
|
|
export function checkAndUpdateElementInline(
|
|
|
|
view: ViewData, def: NodeDef, v0: any, v1: any, v2: any, v3: any, v4: any, v5: any, v6: any,
|
|
|
|
v7: any, v8: any, v9: any) {
|
|
|
|
// Note: fallthrough is intended!
|
|
|
|
switch (def.bindings.length) {
|
|
|
|
case 10:
|
|
|
|
checkAndUpdateElementValue(view, def, 9, v9);
|
|
|
|
case 9:
|
|
|
|
checkAndUpdateElementValue(view, def, 8, v8);
|
|
|
|
case 8:
|
|
|
|
checkAndUpdateElementValue(view, def, 7, v7);
|
|
|
|
case 7:
|
|
|
|
checkAndUpdateElementValue(view, def, 6, v6);
|
|
|
|
case 6:
|
|
|
|
checkAndUpdateElementValue(view, def, 5, v5);
|
|
|
|
case 5:
|
|
|
|
checkAndUpdateElementValue(view, def, 4, v4);
|
|
|
|
case 4:
|
|
|
|
checkAndUpdateElementValue(view, def, 3, v3);
|
|
|
|
case 3:
|
|
|
|
checkAndUpdateElementValue(view, def, 2, v2);
|
|
|
|
case 2:
|
|
|
|
checkAndUpdateElementValue(view, def, 1, v1);
|
|
|
|
case 1:
|
|
|
|
checkAndUpdateElementValue(view, def, 0, v0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function checkAndUpdateElementDynamic(view: ViewData, def: NodeDef, values: any[]) {
|
|
|
|
for (let i = 0; i < values.length; i++) {
|
|
|
|
checkAndUpdateElementValue(view, def, i, values[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function checkAndUpdateElementValue(view: ViewData, def: NodeDef, bindingIdx: number, value: any) {
|
|
|
|
if (!checkAndUpdateBinding(view, def, bindingIdx, value)) {
|
|
|
|
return;
|
|
|
|
}
|
2017-01-31 11:08:29 -08:00
|
|
|
value = unwrapValue(value);
|
2017-01-20 13:10:57 -08:00
|
|
|
|
|
|
|
const binding = def.bindings[bindingIdx];
|
|
|
|
const name = binding.name;
|
2017-01-25 13:45:07 -08:00
|
|
|
const renderNode = asElementData(view, def.index).renderElement;
|
2017-01-20 13:10:57 -08:00
|
|
|
switch (binding.type) {
|
|
|
|
case BindingType.ElementAttribute:
|
|
|
|
setElementAttribute(view, binding, renderNode, name, value);
|
|
|
|
break;
|
|
|
|
case BindingType.ElementClass:
|
|
|
|
setElementClass(view, renderNode, name, value);
|
|
|
|
break;
|
|
|
|
case BindingType.ElementStyle:
|
|
|
|
setElementStyle(view, binding, renderNode, name, value);
|
|
|
|
break;
|
|
|
|
case BindingType.ElementProperty:
|
|
|
|
setElementProperty(view, binding, renderNode, name, value);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function setElementAttribute(
|
|
|
|
view: ViewData, binding: BindingDef, renderNode: any, name: string, value: any) {
|
|
|
|
const securityContext = binding.securityContext;
|
2017-02-01 11:32:27 -08:00
|
|
|
let renderValue = securityContext ? view.root.sanitizer.sanitize(securityContext, value) : value;
|
2017-01-20 13:10:57 -08:00
|
|
|
renderValue = renderValue != null ? renderValue.toString() : null;
|
|
|
|
if (view.renderer) {
|
|
|
|
view.renderer.setElementAttribute(renderNode, name, renderValue);
|
|
|
|
} else {
|
|
|
|
if (value != null) {
|
|
|
|
renderNode.setAttribute(name, renderValue);
|
|
|
|
} else {
|
|
|
|
renderNode.removeAttribute(name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function setElementClass(view: ViewData, renderNode: any, name: string, value: boolean) {
|
|
|
|
if (view.renderer) {
|
|
|
|
view.renderer.setElementClass(renderNode, name, value);
|
|
|
|
} else {
|
|
|
|
if (value) {
|
|
|
|
renderNode.classList.add(name);
|
|
|
|
} else {
|
|
|
|
renderNode.classList.remove(name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function setElementStyle(
|
|
|
|
view: ViewData, binding: BindingDef, renderNode: any, name: string, value: any) {
|
2017-02-01 11:32:27 -08:00
|
|
|
let renderValue = view.root.sanitizer.sanitize(SecurityContext.STYLE, value);
|
2017-01-20 13:10:57 -08:00
|
|
|
if (renderValue != null) {
|
|
|
|
renderValue = renderValue.toString();
|
|
|
|
const unit = binding.suffix;
|
|
|
|
if (unit != null) {
|
|
|
|
renderValue = renderValue + unit;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
renderValue = null;
|
|
|
|
}
|
|
|
|
if (view.renderer) {
|
|
|
|
view.renderer.setElementStyle(renderNode, name, renderValue);
|
|
|
|
} else {
|
|
|
|
if (renderValue != null) {
|
|
|
|
renderNode.style[name] = renderValue;
|
|
|
|
} else {
|
|
|
|
// IE requires '' instead of null
|
|
|
|
// see https://github.com/angular/angular/issues/7916
|
|
|
|
(renderNode.style as any)[name] = '';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function setElementProperty(
|
|
|
|
view: ViewData, binding: BindingDef, renderNode: any, name: string, value: any) {
|
|
|
|
const securityContext = binding.securityContext;
|
2017-02-01 11:32:27 -08:00
|
|
|
let renderValue = securityContext ? view.root.sanitizer.sanitize(securityContext, value) : value;
|
2017-01-20 13:10:57 -08:00
|
|
|
if (view.renderer) {
|
|
|
|
view.renderer.setElementProperty(renderNode, name, renderValue);
|
2017-01-26 17:07:37 -08:00
|
|
|
if (isDevMode() && (view.def.flags & ViewFlags.DirectDom) === 0) {
|
2017-01-20 13:10:57 -08:00
|
|
|
setBindingDebugInfo(view.renderer, renderNode, name, renderValue);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
renderNode[name] = renderValue;
|
|
|
|
}
|
|
|
|
}
|