Files
Angular-Cn/modules/angular2/src/render/dom/compiler/directive_parser.ts
T

188 lines
7.7 KiB
TypeScript
Raw Normal View History

2015-05-18 11:57:20 -07:00
import {
isPresent,
isBlank,
BaseException,
assertionsEnabled,
RegExpWrapper,
StringWrapper
} from 'angular2/src/facade/lang';
import {List, MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {Parser} from 'angular2/change_detection';
import {SelectorMatcher, CssSelector} from 'angular2/src/render/dom/compiler/selector';
import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
import {DirectiveMetadata} from '../../api';
2015-04-02 15:56:58 +02:00
import {dashCaseToCamelCase, camelCaseToDashCase, EVENT_TARGET_SEPARATOR} from '../util';
2015-06-12 23:11:11 +02:00
import {DirectiveBuilder} from '../view/proto_view_builder';
2015-06-12 23:11:11 +02:00
/**
* Parses the directives on a single element. Assumes ViewSplitter has already created
* <template> elements for template directives.
*/
export class DirectiveParser implements CompileStep {
_selectorMatcher: SelectorMatcher = new SelectorMatcher();
2015-06-12 23:11:11 +02:00
constructor(public _parser: Parser, public _directives: List<DirectiveMetadata>) {
for (var i = 0; i < _directives.length; i++) {
var directive = _directives[i];
var selector = CssSelector.parse(directive.selector);
this._ensureComponentOnlyHasElementSelector(selector, directive);
this._selectorMatcher.addSelectables(selector, i);
}
}
_ensureComponentOnlyHasElementSelector(selector, directive) {
var isElementSelector = selector.length === 1 && selector[0].isElementSelector();
2015-05-18 11:57:20 -07:00
if (!isElementSelector && directive.type === DirectiveMetadata.COMPONENT_TYPE) {
throw new BaseException(
`Component '${directive.id}' can only have an element selector, but had '${directive.selector}'`);
}
}
2015-05-18 11:57:20 -07:00
process(parent: CompileElement, current: CompileElement, control: CompileControl) {
var attrs = current.attrs();
var classList = current.classList();
var cssSelector = new CssSelector();
var nodeName = DOM.nodeName(current.element);
cssSelector.setElement(nodeName);
2015-05-18 11:57:20 -07:00
for (var i = 0; i < classList.length; i++) {
cssSelector.addClassName(classList[i]);
}
2015-05-18 11:57:20 -07:00
MapWrapper.forEach(attrs,
(attrValue, attrName) => { cssSelector.addAttribute(attrName, attrValue); });
var componentDirective;
var foundDirectiveIndices = [];
var elementBinder = null;
this._selectorMatcher.match(cssSelector, (selector, directiveIndex) => {
elementBinder = current.bindElement();
var directive = this._directives[directiveIndex];
if (directive.type === DirectiveMetadata.COMPONENT_TYPE) {
// components need to go first, so it is easier to locate them in the result.
ListWrapper.insert(foundDirectiveIndices, 0, directiveIndex);
if (isPresent(componentDirective)) {
2015-05-18 11:57:20 -07:00
throw new BaseException(
`Only one component directive is allowed per element - check ${current.elementDescription}`);
}
componentDirective = directive;
elementBinder.setComponentId(directive.id);
} else {
foundDirectiveIndices.push(directiveIndex);
}
});
ListWrapper.forEach(foundDirectiveIndices, (directiveIndex) => {
var dirMetadata = this._directives[directiveIndex];
var directiveBinderBuilder = elementBinder.bindDirective(directiveIndex);
current.compileChildren = current.compileChildren && dirMetadata.compileChildren;
if (isPresent(dirMetadata.properties)) {
ListWrapper.forEach(dirMetadata.properties, (bindConfig) => {
this._bindDirectiveProperty(bindConfig, current, directiveBinderBuilder);
});
}
if (isPresent(dirMetadata.hostListeners)) {
MapWrapper.forEach(dirMetadata.hostListeners, (action, eventName) => {
this._bindDirectiveEvent(eventName, action, current, directiveBinderBuilder);
});
}
if (isPresent(dirMetadata.hostActions)) {
MapWrapper.forEach(dirMetadata.hostActions, (action, actionName) => {
this._bindHostAction(actionName, action, current, directiveBinderBuilder);
});
}
if (isPresent(dirMetadata.hostProperties)) {
MapWrapper.forEach(dirMetadata.hostProperties, (expression, hostPropertyName) => {
this._bindHostProperty(hostPropertyName, expression, current, directiveBinderBuilder);
2015-04-02 14:40:49 -07:00
});
}
if (isPresent(dirMetadata.hostAttributes)) {
MapWrapper.forEach(dirMetadata.hostAttributes, (hostAttrValue, hostAttrName) => {
this._addHostAttribute(hostAttrName, hostAttrValue, current);
});
}
if (isPresent(dirMetadata.readAttributes)) {
ListWrapper.forEach(dirMetadata.readAttributes,
2015-05-18 11:57:20 -07:00
(attrName) => { elementBinder.readAttribute(attrName); });
}
});
}
_bindDirectiveProperty(bindConfig: string, compileElement: CompileElement,
2015-05-20 09:48:15 -07:00
directiveBinderBuilder: DirectiveBuilder) {
// Name of the property on the directive
let dirProperty: string;
// Name of the property on the element
let elProp: string;
let pipes: List<string>;
let assignIndex: number = bindConfig.indexOf(':');
if (assignIndex > -1) {
// canonical syntax: `dirProp: elProp | pipe0 | ... | pipeN`
dirProperty = StringWrapper.substring(bindConfig, 0, assignIndex).trim();
pipes = this._splitBindConfig(StringWrapper.substring(bindConfig, assignIndex + 1));
elProp = ListWrapper.removeAt(pipes, 0);
} else {
// shorthand syntax when the name of the property on the directive and on the element is the
// same, ie `property`
dirProperty = bindConfig;
elProp = bindConfig;
pipes = [];
}
var bindingAst = compileElement.bindElement().propertyBindings.get(dashCaseToCamelCase(elProp));
if (isBlank(bindingAst)) {
var attributeValue = compileElement.attrs().get(camelCaseToDashCase(elProp));
if (isPresent(attributeValue)) {
2015-05-18 11:57:20 -07:00
bindingAst =
this._parser.wrapLiteralPrimitive(attributeValue, compileElement.elementDescription);
}
}
// Bindings are optional, so this binding only needs to be set up if an expression is given.
if (isPresent(bindingAst)) {
directiveBinderBuilder.bindProperty(dirProperty, bindingAst);
}
}
_bindDirectiveEvent(eventName, action, compileElement, directiveBinderBuilder) {
var ast = this._parser.parseAction(action, compileElement.elementDescription);
2015-04-02 15:56:58 +02:00
if (StringWrapper.contains(eventName, EVENT_TARGET_SEPARATOR)) {
var parts = eventName.split(EVENT_TARGET_SEPARATOR);
directiveBinderBuilder.bindEvent(parts[1], ast, parts[0]);
2015-04-02 15:56:58 +02:00
} else {
directiveBinderBuilder.bindEvent(eventName, ast);
2015-04-02 15:56:58 +02:00
}
}
_bindHostAction(actionName, actionExpression, compileElement, directiveBinderBuilder) {
var ast = this._parser.parseAction(actionExpression, compileElement.elementDescription);
directiveBinderBuilder.bindHostAction(actionName, actionExpression, ast);
}
_bindHostProperty(hostPropertyName, expression, compileElement, directiveBinderBuilder) {
var ast = this._parser.parseBinding(expression,
2015-05-18 11:57:20 -07:00
`hostProperties of ${compileElement.elementDescription}`);
directiveBinderBuilder.bindHostProperty(hostPropertyName, ast);
}
_addHostAttribute(attrName, attrValue, compileElement) {
if (StringWrapper.equals(attrName, 'class')) {
2015-05-18 11:57:20 -07:00
ListWrapper.forEach(attrValue.split(' '),
(className) => { DOM.addClass(compileElement.element, className); });
} else if (!DOM.hasAttribute(compileElement.element, attrName)) {
DOM.setAttribute(compileElement.element, attrName, attrValue);
}
}
2015-05-18 11:57:20 -07:00
_splitBindConfig(bindConfig: string) {
return ListWrapper.map(bindConfig.split('|'), (s) => s.trim());
}
}