2015-05-18 11:57:20 -07:00
|
|
|
import {
|
|
|
|
|
isPresent,
|
|
|
|
|
isBlank,
|
|
|
|
|
BaseException,
|
|
|
|
|
assertionsEnabled,
|
|
|
|
|
RegExpWrapper,
|
|
|
|
|
StringWrapper
|
|
|
|
|
} from 'angular2/src/facade/lang';
|
2015-03-23 14:10:55 -07:00
|
|
|
import {List, MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
|
|
|
|
|
import {DOM} from 'angular2/src/dom/dom_adapter';
|
|
|
|
|
import {Parser} from 'angular2/change_detection';
|
|
|
|
|
|
2015-04-02 14:40:49 -07:00
|
|
|
import {SelectorMatcher, CssSelector} from 'angular2/src/render/dom/compiler/selector';
|
2015-03-23 14:10:55 -07:00
|
|
|
|
|
|
|
|
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-05-20 09:48:15 -07:00
|
|
|
import {
|
|
|
|
|
DirectiveBuilder
|
|
|
|
|
} from '../view/proto_view_builder'
|
2015-03-23 14:10:55 -07:00
|
|
|
|
2015-05-20 09:48:15 -07:00
|
|
|
/**
|
|
|
|
|
* Parses the directives on a single element. Assumes ViewSplitter has already created
|
|
|
|
|
* <template> elements for template directives.
|
|
|
|
|
*/
|
|
|
|
|
export class DirectiveParser implements CompileStep {
|
2015-05-18 11:57:20 -07:00
|
|
|
_selectorMatcher: SelectorMatcher;
|
|
|
|
|
_directives: List<DirectiveMetadata>;
|
|
|
|
|
_parser: Parser;
|
2015-03-23 14:10:55 -07:00
|
|
|
|
2015-05-18 11:57:20 -07:00
|
|
|
constructor(parser: Parser, directives: List<DirectiveMetadata>) {
|
2015-03-23 14:10:55 -07:00
|
|
|
this._parser = parser;
|
|
|
|
|
this._selectorMatcher = new SelectorMatcher();
|
|
|
|
|
this._directives = directives;
|
2015-05-18 11:57:20 -07:00
|
|
|
for (var i = 0; i < directives.length; i++) {
|
2015-04-27 15:14:30 -07:00
|
|
|
var directive = directives[i];
|
|
|
|
|
var selector = CssSelector.parse(directive.selector);
|
|
|
|
|
this._ensureComponentOnlyHasElementSelector(selector, directive);
|
2015-03-23 14:10:55 -07:00
|
|
|
this._selectorMatcher.addSelectables(selector, i);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-27 15:14:30 -07:00
|
|
|
_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-04-27 15:14:30 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-18 11:57:20 -07:00
|
|
|
process(parent: CompileElement, current: CompileElement, control: CompileControl) {
|
2015-03-23 14:10:55 -07:00
|
|
|
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++) {
|
2015-03-23 14:10:55 -07:00
|
|
|
cssSelector.addClassName(classList[i]);
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-18 11:57:20 -07:00
|
|
|
MapWrapper.forEach(attrs,
|
|
|
|
|
(attrValue, attrName) => { cssSelector.addAttribute(attrName, attrValue); });
|
2015-03-23 14:10:55 -07:00
|
|
|
|
|
|
|
|
var componentDirective;
|
2015-05-11 17:59:39 -07:00
|
|
|
var foundDirectiveIndices = [];
|
|
|
|
|
var elementBinder = null;
|
2015-03-23 14:10:55 -07:00
|
|
|
this._selectorMatcher.match(cssSelector, (selector, directiveIndex) => {
|
2015-05-11 17:59:39 -07:00
|
|
|
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}`);
|
2015-05-11 17:59:39 -07:00
|
|
|
}
|
|
|
|
|
componentDirective = directive;
|
|
|
|
|
elementBinder.setComponentId(directive.id);
|
|
|
|
|
} else {
|
|
|
|
|
ListWrapper.push(foundDirectiveIndices, directiveIndex);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
ListWrapper.forEach(foundDirectiveIndices, (directiveIndex) => {
|
2015-03-23 14:10:55 -07:00
|
|
|
var directive = this._directives[directiveIndex];
|
2015-04-21 11:47:53 -07:00
|
|
|
var directiveBinderBuilder = elementBinder.bindDirective(directiveIndex);
|
2015-03-23 14:10:55 -07:00
|
|
|
current.compileChildren = current.compileChildren && directive.compileChildren;
|
2015-04-09 21:20:11 +02:00
|
|
|
if (isPresent(directive.properties)) {
|
2015-05-26 15:54:10 +02:00
|
|
|
ListWrapper.forEach(directive.properties, (bindConfig) => {
|
|
|
|
|
this._bindDirectiveProperty(bindConfig, current, directiveBinderBuilder);
|
2015-03-23 14:10:55 -07:00
|
|
|
});
|
|
|
|
|
}
|
2015-04-09 21:20:11 +02:00
|
|
|
if (isPresent(directive.hostListeners)) {
|
|
|
|
|
MapWrapper.forEach(directive.hostListeners, (action, eventName) => {
|
2015-04-21 11:47:53 -07:00
|
|
|
this._bindDirectiveEvent(eventName, action, current, directiveBinderBuilder);
|
2015-03-23 14:10:55 -07:00
|
|
|
});
|
|
|
|
|
}
|
2015-05-11 12:31:16 -07:00
|
|
|
if (isPresent(directive.hostActions)) {
|
|
|
|
|
MapWrapper.forEach(directive.hostActions, (action, actionName) => {
|
|
|
|
|
this._bindHostAction(actionName, action, current, directiveBinderBuilder);
|
|
|
|
|
});
|
|
|
|
|
}
|
2015-04-21 11:47:53 -07:00
|
|
|
if (isPresent(directive.hostProperties)) {
|
|
|
|
|
MapWrapper.forEach(directive.hostProperties, (hostPropertyName, directivePropertyName) => {
|
2015-05-18 11:57:20 -07:00
|
|
|
this._bindHostProperty(hostPropertyName, directivePropertyName, current,
|
|
|
|
|
directiveBinderBuilder);
|
2015-04-02 14:40:49 -07:00
|
|
|
});
|
|
|
|
|
}
|
2015-05-01 13:41:56 +02:00
|
|
|
if (isPresent(directive.hostAttributes)) {
|
|
|
|
|
MapWrapper.forEach(directive.hostAttributes, (hostAttrValue, hostAttrName) => {
|
2015-05-12 16:46:40 +02:00
|
|
|
this._addHostAttribute(hostAttrName, hostAttrValue, current);
|
2015-05-01 13:41:56 +02:00
|
|
|
});
|
|
|
|
|
}
|
2015-04-02 14:40:49 -07:00
|
|
|
if (isPresent(directive.readAttributes)) {
|
2015-05-18 11:57:20 -07:00
|
|
|
ListWrapper.forEach(directive.readAttributes,
|
|
|
|
|
(attrName) => { elementBinder.readAttribute(attrName); });
|
2015-03-23 14:10:55 -07:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-26 15:54:10 +02:00
|
|
|
_bindDirectiveProperty(bindConfig: string, compileElement: CompileElement,
|
2015-05-20 09:48:15 -07:00
|
|
|
directiveBinderBuilder: DirectiveBuilder) {
|
2015-05-26 15:54:10 +02:00
|
|
|
// 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 = [];
|
|
|
|
|
}
|
2015-03-23 14:10:55 -07:00
|
|
|
|
2015-05-18 11:57:20 -07:00
|
|
|
var bindingAst =
|
|
|
|
|
MapWrapper.get(compileElement.bindElement().propertyBindings, dashCaseToCamelCase(elProp));
|
2015-03-23 14:10:55 -07:00
|
|
|
|
|
|
|
|
if (isBlank(bindingAst)) {
|
|
|
|
|
var attributeValue = MapWrapper.get(compileElement.attrs(), camelCaseToDashCase(elProp));
|
|
|
|
|
if (isPresent(attributeValue)) {
|
2015-05-18 11:57:20 -07:00
|
|
|
bindingAst =
|
|
|
|
|
this._parser.wrapLiteralPrimitive(attributeValue, compileElement.elementDescription);
|
2015-03-23 14:10:55 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Bindings are optional, so this binding only needs to be set up if an expression is given.
|
|
|
|
|
if (isPresent(bindingAst)) {
|
|
|
|
|
var fullExpAstWithBindPipes = this._parser.addPipes(bindingAst, pipes);
|
2015-05-18 11:57:20 -07:00
|
|
|
directiveBinderBuilder.bindProperty(dirProperty, fullExpAstWithBindPipes);
|
2015-03-23 14:10:55 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-21 11:47:53 -07:00
|
|
|
_bindDirectiveEvent(eventName, action, compileElement, directiveBinderBuilder) {
|
2015-03-23 14:10:55 -07:00
|
|
|
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);
|
2015-04-21 11:47:53 -07:00
|
|
|
directiveBinderBuilder.bindEvent(parts[1], ast, parts[0]);
|
2015-04-02 15:56:58 +02:00
|
|
|
} else {
|
2015-04-21 11:47:53 -07:00
|
|
|
directiveBinderBuilder.bindEvent(eventName, ast);
|
2015-04-02 15:56:58 +02:00
|
|
|
}
|
2015-05-11 12:31:16 -07:00
|
|
|
}
|
2015-04-21 11:47:53 -07:00
|
|
|
|
2015-05-11 12:31:16 -07:00
|
|
|
_bindHostAction(actionName, actionExpression, compileElement, directiveBinderBuilder) {
|
|
|
|
|
var ast = this._parser.parseAction(actionExpression, compileElement.elementDescription);
|
|
|
|
|
directiveBinderBuilder.bindHostAction(actionName, actionExpression, ast);
|
2015-04-21 11:47:53 -07:00
|
|
|
}
|
|
|
|
|
|
2015-05-18 11:57:20 -07:00
|
|
|
_bindHostProperty(hostPropertyName, directivePropertyName, compileElement,
|
|
|
|
|
directiveBinderBuilder) {
|
2015-04-21 11:47:53 -07:00
|
|
|
var ast = this._parser.parseBinding(directivePropertyName,
|
2015-05-18 11:57:20 -07:00
|
|
|
`hostProperties of ${compileElement.elementDescription}`);
|
2015-04-21 11:47:53 -07:00
|
|
|
directiveBinderBuilder.bindHostProperty(hostPropertyName, ast);
|
2015-03-23 14:10:55 -07:00
|
|
|
}
|
|
|
|
|
|
2015-05-12 16:46:40 +02:00
|
|
|
_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); });
|
2015-05-12 16:46:40 +02:00
|
|
|
} else if (!DOM.hasAttribute(compileElement.element, attrName)) {
|
|
|
|
|
DOM.setAttribute(compileElement.element, attrName, attrValue);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-18 11:57:20 -07:00
|
|
|
_splitBindConfig(bindConfig: string) {
|
2015-03-23 14:10:55 -07:00
|
|
|
return ListWrapper.map(bindConfig.split('|'), (s) => s.trim());
|
|
|
|
|
}
|
|
|
|
|
}
|