feat(compiler): initial version of the compiler.

Supports:
- binds text nodes, element properties and directive properties
- locates decorator, component and template directives.
- inline templates of components

The compiler is built using a pipeline design,
see core/src/compiler/pipeline package.

Integration tests to show how the compiler, change_detection and DI work
together:
core/test/compiler/integration_spec.js
This commit is contained in:
Tobias Bosch
2014-11-11 17:33:47 -08:00
parent 62efb56b0a
commit 7a70f8f92d
53 changed files with 2877 additions and 386 deletions

View File

@ -1,10 +1,11 @@
import {Directive} from './directive';
import {ABSTRACT, CONST} from 'facade/lang';
import {CONST} from 'facade/lang';
export class Component extends Directive {
@CONST()
constructor({
selector,
bind,
lightDomServices,
implementsTypes,
template,
@ -12,15 +13,17 @@ export class Component extends Directive {
componentServices
}:{
selector:String,
bind:Object,
template:TemplateConfig,
lightDomServices:DomServicesFunction,
shadowDomServices:DomServicesFunction,
componentServices:ComponentServicesFunction,
implementsTypes:Array<Type>
})
}={})
{
super({
selector: selector,
bind: bind,
lightDomServices: lightDomServices,
implementsTypes: implementsTypes});
this.template = template;

View File

@ -0,0 +1,25 @@
import {Directive} from './directive';
import {CONST} from 'facade/lang';
export class Decorator extends Directive {
@CONST()
constructor({
selector,
bind,
lightDomServices,
implementsTypes
}:{
selector:String,
bind:Object,
lightDomServices:ElementServicesFunction,
implementsTypes:Array<Type>
}={})
{
super({
selector: selector,
bind: bind,
lightDomServices: lightDomServices,
implementsTypes: implementsTypes
});
}
}

View File

@ -8,10 +8,12 @@ export class Directive {
@CONST()
constructor({
selector,
bind,
lightDomServices,
implementsTypes
}:{
selector:String,
bind:Object,
lightDomServices:ElementServicesFunction,
implementsTypes:Array<Type>
})
@ -19,5 +21,6 @@ export class Directive {
this.selector = selector;
this.lightDomServices = lightDomServices;
this.implementsTypes = implementsTypes;
this.bind = bind;
}
}

View File

@ -0,0 +1,25 @@
import {Directive} from './directive';
import {CONST} from 'facade/lang';
export class Template extends Directive {
@CONST()
constructor({
selector,
bind,
lightDomServices,
implementsTypes
}:{
selector:String,
bind:Object,
lightDomServices:ElementServicesFunction,
implementsTypes:Array<Type>
}={})
{
super({
selector: selector,
bind: bind,
lightDomServices: lightDomServices,
implementsTypes: implementsTypes
});
}
}

View File

@ -5,17 +5,20 @@ export class TemplateConfig {
@CONST()
constructor({
url,
inline,
directives,
formatters,
source
}: {
url: String,
inline: String,
directives: List<Type>,
formatters: List<Type>,
source: List<TemplateConfig>
})
{
this.url = url;
this.inline = inline;
this.directives = directives;
this.formatters = formatters;
this.source = source;

View File

@ -1,8 +1,11 @@
import {Type, FIELD} from 'facade/lang';
import {Directive} from '../annotations/directive'
/**
* Combination of a type with the Directive annotation
*/
export class AnnotatedType {
constructor(annotation:Directive, type:Type) {
constructor(type:Type, annotation:Directive) {
this.annotation = annotation;
this.type = type;
}

View File

@ -1,16 +0,0 @@
import {Type} from 'facade/lang';
import {Directive} from '../annotations/directive'
/**
* Interface representing a way of extracting [Directive] annotations from
* [Type]. This interface has three native implementations:
*
* 1) JavaScript native implementation
* 2) Dart reflective implementation
* 3) Dart transformer generated implementation
*/
export class AnnotationsExtractor {
extract(type:Type):Directive {
return null;
}
}

View File

@ -1,28 +1,71 @@
import {Type} from 'facade/lang';
import {Promise} from 'facade/async';
import {Element} from 'facade/dom';
//import {ProtoView} from './view';
import {Type, FIELD, isBlank, isPresent} from 'facade/lang';
import {Promise, PromiseWrapper} from 'facade/async';
import {List, ListWrapper} from 'facade/collection';
import {DOM, Element} from 'facade/dom';
import {Parser} from 'change_detection/parser/parser';
import {ClosureMap} from 'change_detection/parser/closure_map';
import {Reflector} from './reflector';
import {ProtoView} from './view';
import {CompilePipeline} from './pipeline/compile_pipeline';
import {CompileElement} from './pipeline/compile_element';
import {createDefaultSteps} from './pipeline/default_steps';
import {TemplateLoader} from './template_loader';
import {FIELD} from 'facade/lang';
import {AnnotatedType} from './annotated_type';
/**
* The compiler loads and translates the html templates of components into
* nested ProtoViews. To decompose its functionality it uses
* the CompilePipeline and the CompileSteps.
*/
export class Compiler {
@FIELD('final _templateLoader:TemplateLoader')
constructor(templateLoader:TemplateLoader) {
constructor(templateLoader:TemplateLoader, reflector: Reflector, parser:Parser, closureMap:ClosureMap) {
this._templateLoader = templateLoader;
this._reflector = reflector;
this._parser = parser;
this._closureMap = closureMap;
}
/**
* # Why promise?
* - compilation will load templates. Instantiating views before templates are loaded will
* complicate the Directive code. BENEFIT: view instantiation become synchrnous.
* # Why result that is independent of injector?
* - don't know about injector in deserialization
* - compile does not need the injector, only the ViewFactory does
*/
compile(component:Type, element:Element/* = null*/):Promise/*<ProtoView>*/ {
return null;
createSteps(component:AnnotatedType):List<CompileStep> {
var directives = component.annotation.template.directives;
var annotatedDirectives = ListWrapper.create();
for (var i=0; i<directives.length; i++) {
ListWrapper.push(annotatedDirectives, this._reflector.annotatedType(directives[i]));
}
return createDefaultSteps(this._parser, this._closureMap, annotatedDirectives);
}
compile(component:Type, templateRoot:Element = null):Promise<ProtoView> {
// TODO load all components transitively from the cache first
var cache = null;
return PromiseWrapper.resolve(this._compileAllCached(
this._reflector.annotatedType(component),
cache,
templateRoot)
);
}
_compileAllCached(component:AnnotatedType, cache, templateRoot:Element = null):ProtoView {
if (isBlank(templateRoot)) {
// TODO: read out the cache if templateRoot = null. Could contain:
// - templateRoot string
// - precompiled template
// - ProtoView
templateRoot = DOM.createTemplate(component.annotation.template.inline);
}
var pipeline = new CompilePipeline(this.createSteps(component));
var compileElements = pipeline.process(templateRoot);
var rootProtoView = compileElements[0].inheritedProtoView;
// TODO: put the rootProtoView into the cache to support recursive templates!
for (var i=0; i<compileElements.length; i++) {
var ce = compileElements[i];
if (isPresent(ce.componentDirective)) {
ce.inheritedElementBinder.nestedProtoView = this._compileAllCached(ce.componentDirective, cache, null);
}
}
return rootProtoView;
}
}

View File

@ -1,15 +1,21 @@
import {ProtoElementInjector} from './element_injector';
import {FIELD} from 'facade/lang';
import {List} from 'facade/collection';
// Comment out as dartanalyzer does not look into @FIELD
// import {List} from 'facade/collection';
// import {ProtoView} from './view';
export class ElementBinder {
@FIELD('final protoElementInjector:ProtoElementInjector')
@FIELD('final textNodeIndices:List<int>')
@FIELD('final hasElementPropertyBindings:bool')
constructor(protoElementInjector: ProtoElementInjector,
textNodeIndices:List, hasElementPropertyBindings:boolean) {
this.protoElementInjector = protoElementInjector;
this.textNodeIndices = textNodeIndices;
this.hasElementPropertyBindings = hasElementPropertyBindings;
@FIELD('hasElementPropertyBindings:bool')
@FIELD('nestedProtoView:ProtoView')
constructor(protoElementInjector: ProtoElementInjector) {
this.protoElementInjector = protoElementInjector;
// updated later when text nodes are bound
this.textNodeIndices = [];
// updated later when element properties are bound
this.hasElementPropertyBindings = false;
// updated later, so we are able to resolve cycles
this.nestedProtoView = null;
}
}

View File

@ -3,8 +3,9 @@ import {Math} from 'facade/math';
import {List, ListWrapper} from 'facade/collection';
import {Injector, Key, Dependency, bind, Binding, NoProviderError, ProviderError, CyclicDependencyError} from 'di/di';
import {Parent, Ancestor} from 'core/annotations/visibility';
import {View} from './view';
import {StaticKeys} from './static_keys';
// Comment out as dartanalyzer does not look into @FIELD
// import {View} from './view';
var _MAX_DIRECTIVE_CONSTRUCTION_COUNTER = 10;

View File

@ -0,0 +1,47 @@
import {ListWrapper} from 'facade/collection';
import {DOM} from 'facade/dom';
import {CompileElement} from './compile_element';
/**
* Controls the processing order of elements.
* Right now it only allows to add a parent element.
*/
export class CompileControl {
constructor(steps) {
this._steps = steps;
this._currentStepIndex = 0;
this._parent = null;
this._current = null;
this._results = null;
}
// only public so that it can be used by compile_pipeline
internalProcess(results, startStepIndex, parent:CompileElement, current:CompileElement) {
this._results = results;
var previousStepIndex = this._currentStepIndex;
var previousParent = this._parent;
for (var i=startStepIndex; i<this._steps.length; i++) {
var step = this._steps[i];
this._parent = parent;
this._current = current;
this._currentStepIndex = i;
step.process(parent, current, this);
parent = this._parent;
}
ListWrapper.push(results, current);
this._currentStepIndex = previousStepIndex;
this._parent = previousParent;
}
addParent(newElement:CompileElement) {
var currEl = this._current.element;
var newEl = newElement.element;
DOM.parentElement(currEl).insertBefore(newEl, currEl);
DOM.appendChild(newEl, currEl);
this.internalProcess(this._results, this._currentStepIndex+1, this._parent, newElement);
this._parent = newElement;
}
}

View File

@ -0,0 +1,90 @@
import {List, Map, ListWrapper, MapWrapper} from 'facade/collection';
import {Element, DOM} from 'facade/dom';
import {int, isBlank, isPresent} from 'facade/lang';
import {AnnotatedType} from '../annotated_type';
import {Decorator} from '../../annotations/decorator';
import {Component} from '../../annotations/component';
import {Template} from '../../annotations/template';
/**
* Collects all data that is needed to process an element
* in the compile process. Fields are filled
* by the CompileSteps starting out with the pure HTMLElement.
*/
export class CompileElement {
constructor(element:Element) {
this.element = element;
this._attrs = null;
this._classList = null;
this.textNodeBindings = null;
this.propertyBindings = null;
this.decoratorDirectives = null;
this.templateDirective = null;
this.componentDirective = null;
this.isViewRoot = false;
this.hasBindings = false;
// inherited down to children if they don't have
// an own protoView
this.inheritedProtoView = null;
// inherited down to children if they don't have
// an own protoElementInjector
this.inheritedProtoElementInjector = null;
// inherited down to children if they don't have
// an own elementBinder
this.inheritedElementBinder = null;
}
refreshAttrs() {
this._attrs = null;
}
attrs():Map<string,string> {
if (isBlank(this._attrs)) {
this._attrs = DOM.attributeMap(this.element);
}
return this._attrs;
}
refreshClassList() {
this._classList = null;
}
classList():List<string> {
if (isBlank(this._classList)) {
this._classList = ListWrapper.create();
var elClassList = DOM.classList(this.element);
for (var i = 0; i < elClassList.length; i++) {
ListWrapper.push(this._classList, elClassList[i]);
}
}
return this._classList;
}
addTextNodeBinding(indexInParent:int, expression:string) {
if (isBlank(this.textNodeBindings)) {
this.textNodeBindings = MapWrapper.create();
}
MapWrapper.set(this.textNodeBindings, indexInParent, expression);
}
addPropertyBinding(property:string, expression:string) {
if (isBlank(this.propertyBindings)) {
this.propertyBindings = MapWrapper.create();
}
MapWrapper.set(this.propertyBindings, property, expression);
}
addDirective(directive:AnnotatedType) {
var annotation = directive.annotation;
if (annotation instanceof Decorator) {
if (isBlank(this.decoratorDirectives)) {
this.decoratorDirectives = ListWrapper.create();
}
ListWrapper.push(this.decoratorDirectives, directive);
} else if (annotation instanceof Template) {
this.templateDirective = directive;
} else if (annotation instanceof Component) {
this.componentDirective = directive;
}
}
}

View File

@ -0,0 +1,39 @@
import {List, ListWrapper} from 'facade/collection';
import {Element, TemplateElement, Node} from 'facade/dom';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
import {CompileStep} from './compile_step';
import {AnnotatedType} from '../annotated_type';
/**
* CompilePipeline for executing CompileSteps recursively for
* all elements in a template.
*/
export class CompilePipeline {
constructor(steps:List<CompileStep>) {
this._control = new CompileControl(steps);
}
process(rootElement:Element):List {
var results = ListWrapper.create();
this._process(results, null, rootElement);
return results;
}
_process(results, parent:CompileElement, element:Element) {
var current = new CompileElement(element);
this._control.internalProcess(results, 0, parent, current);
var childNodes;
if (element instanceof TemplateElement) {
childNodes = element.content.childNodes;
} else {
childNodes = element.childNodes;
}
for (var i=0; i<childNodes.length; i++) {
var node = childNodes[i];
if (node.nodeType === Node.ELEMENT_NODE) {
this._process(results, current, node);
}
}
}
}

View File

@ -0,0 +1,11 @@
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
import {AnnotatedType} from '../annotated_type';
/**
* One part of the compile process.
* Is guaranteed to be called in depth first order
*/
export class CompileStep {
process(parent:CompileElement, current:CompileElement, control:CompileControl) {}
}

View File

@ -0,0 +1,33 @@
import {Parser} from 'change_detection/parser/parser';
import {ClosureMap} from 'change_detection/parser/closure_map';
import {List} from 'facade/collection';
import {PropertyBindingParser} from './property_binding_parser';
import {TextInterpolationParser} from './text_interpolation_parser';
import {DirectiveParser} from './directive_parser';
import {ViewSplitter} from './view_splitter';
import {ElementBindingMarker} from './element_binding_marker';
import {ProtoViewBuilder} from './proto_view_builder';
import {ProtoElementInjectorBuilder} from './proto_element_injector_builder';
import {ElementBinderBuilder} from './element_binder_builder';
/**
* Default steps used for compiling a template.
* Takes in an HTMLElement and produces the ProtoViews,
* ProtoElementInjectors and ElementBinders in the end.
*/
export function createDefaultSteps(
parser:Parser, closureMap:ClosureMap,
directives: List<AnnotatedType>
) {
return [
new PropertyBindingParser(),
new TextInterpolationParser(),
new DirectiveParser(directives),
new ViewSplitter(),
new ElementBindingMarker(),
new ProtoViewBuilder(),
new ProtoElementInjectorBuilder(),
new ElementBinderBuilder(parser, closureMap)
];
}

View File

@ -0,0 +1,66 @@
import {isPresent, BaseException} from 'facade/lang';
import {List, MapWrapper} from 'facade/collection';
import {SelectorMatcher} from '../selector';
import {CssSelector} from '../selector';
import {AnnotatedType} from '../annotated_type';
import {Template} from '../../annotations/template';
import {Component} from '../../annotations/component';
import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
import {Reflector} from '../reflector';
/**
* Parses the directives on a single element.
*
* Fills:
* - CompileElement#decoratorDirectives
* - CompileElement#templateDirecitve
* - CompileElement#componentDirective.
*
* Reads:
* - CompileElement#propertyBindings (to find directives contained
* in the property bindings)
*/
export class DirectiveParser extends CompileStep {
constructor(directives:List<AnnotatedType>) {
this._selectorMatcher = new SelectorMatcher();
for (var i=0; i<directives.length; i++) {
var annotatedType = directives[i];
this._selectorMatcher.addSelectable(
CssSelector.parse(annotatedType.annotation.selector),
annotatedType
);
}
}
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
var attrs = current.attrs();
var classList = current.classList();
var cssSelector = new CssSelector();
cssSelector.setElement(current.element.nodeName);
for (var i=0; i < classList.length; i++) {
cssSelector.addClassName(classList[i]);
}
MapWrapper.forEach(attrs, (attrValue, attrName) => {
cssSelector.addAttribute(attrName, attrValue);
});
// Allow to find directives even though the attribute is bound
if (isPresent(current.propertyBindings)) {
MapWrapper.forEach(current.propertyBindings, (expression, boundProp) => {
cssSelector.addAttribute(boundProp, expression);
});
}
this._selectorMatcher.match(cssSelector, (directive) => {
if (isPresent(current.templateDirective) && (directive.annotation instanceof Template)) {
throw new BaseException('Only one template directive per element is allowed!');
}
if (isPresent(current.componentDirective) && (directive.annotation instanceof Component)) {
throw new BaseException('Only one component directive per element is allowed!');
}
current.addDirective(directive);
});
}
}

View File

@ -0,0 +1,123 @@
import {int, isPresent, isBlank, Type, BaseException, stringify} from 'facade/lang';
import {Element} from 'facade/dom';
import {ListWrapper, List, MapWrapper, StringMapWrapper} from 'facade/collection';
import {Parser} from 'change_detection/parser/parser';
import {ClosureMap} from 'change_detection/parser/closure_map';
import {ProtoWatchGroup} from 'change_detection/watch_group';
import {Directive} from '../../annotations/directive';
import {Component} from '../../annotations/component';
import {AnnotatedType} from '../annotated_type';
import {ProtoView, ElementPropertyMemento, DirectivePropertyMemento} from '../view';
import {ProtoElementInjector} from '../element_injector';
import {ElementBinder} from '../element_binder';
import {Reflector} from '../reflector';
import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
/**
* Creates the ElementBinders and adds watches to the
* ProtoWatchGroup.
*
* Fills:
* - CompileElement#inheritedElementBinder
*
* Reads:
* - (in parent) CompileElement#inheritedElementBinder
* - CompileElement#hasBindings
* - CompileElement#isViewRoot
* - CompileElement#inheritedViewRoot
* - CompileElement#inheritedProtoElementInjector
* - CompileElement#textNodeBindings
* - CompileElement#propertyBindings
* - CompileElement#decoratorDirectives
* - CompileElement#componentDirective
* - CompileElement#templateDirective
*
* Note: This actually only needs the CompileElements with the flags
* `hasBindings` and `isViewRoot`,
* and only needs the actual HTMLElement for the ones
* with the flag `isViewRoot`.
*/
export class ElementBinderBuilder extends CompileStep {
constructor(parser:Parser, closureMap:ClosureMap) {
this._parser = parser;
this._closureMap = closureMap;
}
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
var elementBinder;
if (current.hasBindings) {
var protoView = current.inheritedProtoView;
elementBinder = protoView.bindElement(current.inheritedProtoElementInjector);
if (isPresent(current.textNodeBindings)) {
this._bindTextNodes(protoView, current.textNodeBindings);
}
if (isPresent(current.propertyBindings)) {
this._bindElementProperties(protoView, current.propertyBindings);
}
this._bindDirectiveProperties(this._collectDirectives(current), current);
} else if (isPresent(parent)) {
elementBinder = parent.inheritedElementBinder;
}
current.inheritedElementBinder = elementBinder;
}
_bindTextNodes(protoView, textNodeBindings) {
MapWrapper.forEach(textNodeBindings, (expression, indexInParent) => {
protoView.bindTextNode(indexInParent, this._parser.parseBinding(expression));
});
}
_bindElementProperties(protoView, propertyBindings) {
MapWrapper.forEach(propertyBindings, (expression, property) => {
protoView.bindElementProperty(property, this._parser.parseBinding(expression));
});
}
_collectDirectives(pipelineElement) {
var directives;
if (isPresent(pipelineElement.decoratorDirectives)) {
directives = ListWrapper.clone(pipelineElement.decoratorDirectives);
} else {
directives = [];
}
if (isPresent(pipelineElement.templateDirective)) {
ListWrapper.push(directives, pipelineElement.templateDirective);
}
if (isPresent(pipelineElement.componentDirective)) {
ListWrapper.push(directives, pipelineElement.componentDirective);
}
return directives;
}
_bindDirectiveProperties(typesWithAnnotations, pipelineElement) {
var protoView = pipelineElement.inheritedProtoView;
var directiveIndex = 0;
ListWrapper.forEach(typesWithAnnotations, (typeWithAnnotation) => {
var annotation = typeWithAnnotation.annotation;
if (isBlank(annotation.bind)) {
return;
}
StringMapWrapper.forEach(annotation.bind, (dirProp, elProp) => {
var expression = isPresent(pipelineElement.propertyBindings) ?
MapWrapper.get(pipelineElement.propertyBindings, elProp) :
null;
if (isBlank(expression)) {
throw new BaseException('No element binding found for property '+elProp
+' which is required by directive '+stringify(typeWithAnnotation.type));
}
protoView.bindDirectiveProperty(
directiveIndex++,
this._parser.parseBinding(expression),
dirProp,
this._closureMap.setter(dirProp)
);
});
});
}
}

View File

@ -0,0 +1,40 @@
import {isPresent} from 'facade/lang';
import {MapWrapper} from 'facade/collection';
import {DOM} from 'facade/dom';
import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
const NG_BINDING_CLASS = 'ng-binding';
/**
* Marks elements that have bindings with a css class
* and sets the CompileElement.hasBindings flag.
*
* Fills:
* - CompileElement#hasBindings
*
* Reads:
* - CompileElement#textNodeBindings
* - CompileElement#propertyBindings
* - CompileElement#decoratorDirectives
* - CompileElement#componentDirective
* - CompileElement#templateDirective
*/
export class ElementBindingMarker extends CompileStep {
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
var hasBindings =
(isPresent(current.textNodeBindings) && MapWrapper.size(current.textNodeBindings)>0) ||
(isPresent(current.propertyBindings) && MapWrapper.size(current.propertyBindings)>0) ||
(isPresent(current.decoratorDirectives) && current.decoratorDirectives.length > 0) ||
isPresent(current.templateDirective) ||
isPresent(current.componentDirective);
if (hasBindings) {
var element = current.element;
DOM.addClass(element, NG_BINDING_CLASS);
current.hasBindings = true;
}
}
}

View File

@ -0,0 +1,31 @@
import {isPresent, isBlank, RegExpWrapper} from 'facade/lang';
import {MapWrapper} from 'facade/collection';
import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
// TODO(tbosch): Cannot make this const/final right now because of the transpiler...
var BIND_DASH_REGEXP = RegExpWrapper.create('bind-((?:[^-]|-(?!-))+)(?:--(.+))?');
var PROP_BIND_REGEXP = RegExpWrapper.create('\\[([^|]+)(?:\\|(.+))?\\]');
/**
* Parses the property bindings on a single element.
*
* Fills:
* - CompileElement#propertyBindings
*/
export class PropertyBindingParser extends CompileStep {
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
var attrs = current.attrs();
MapWrapper.forEach(attrs, (attrValue, attrName) => {
var parts = RegExpWrapper.firstMatch(BIND_DASH_REGEXP, attrName);
if (isBlank(parts)) {
parts = RegExpWrapper.firstMatch(PROP_BIND_REGEXP, attrName);
}
if (isPresent(parts)) {
current.addPropertyBinding(parts[1], attrValue);
}
});
}
}

View File

@ -0,0 +1,74 @@
import {isPresent,} from 'facade/lang';
import {ListWrapper} from 'facade/collection';
import {ProtoElementInjector} from '../element_injector';
import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
/**
* Creates the ProtoElementInjectors.
*
* Fills:
* - CompileElement#inheriteProtoElementInjector
*
* Reads:
* - (in parent) CompileElement#inheriteProtoElementInjector
* - CompileElement#isViewRoot
* - CompileElement#inheritedProtoView
* - CompileElement#decoratorDirectives
* - CompileElement#componentDirective
* - CompileElement#templateDirective
*/
export class ProtoElementInjectorBuilder extends CompileStep {
// public so that we can overwrite it in tests
internalCreateProtoElementInjector(parent, index, directives) {
return new ProtoElementInjector(parent, index, directives);
}
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
var inheritedProtoElementInjector = null;
var parentProtoElementInjector = this._getParentProtoElementInjector(parent, current);
var injectorBindings = this._collectDirectiveTypes(current);
// TODO: add lightDomServices as well,
// but after the directives as we rely on that order
// in the element_binder_builder.
if (injectorBindings.length > 0) {
var protoView = current.inheritedProtoView;
inheritedProtoElementInjector = this.internalCreateProtoElementInjector(
parentProtoElementInjector, protoView.elementBinders.length, injectorBindings
);
} else {
inheritedProtoElementInjector = parentProtoElementInjector;
}
current.inheritedProtoElementInjector = inheritedProtoElementInjector;
}
_getParentProtoElementInjector(parent, current) {
var parentProtoElementInjector = null;
if (current.isViewRoot) {
parentProtoElementInjector = null;
} else if (isPresent(parent)) {
parentProtoElementInjector = parent.inheritedProtoElementInjector;
}
return parentProtoElementInjector;
}
_collectDirectiveTypes(pipelineElement) {
var directiveTypes = [];
if (isPresent(pipelineElement.decoratorDirectives)) {
for (var i=0; i<pipelineElement.decoratorDirectives.length; i++) {
ListWrapper.push(directiveTypes, pipelineElement.decoratorDirectives[i].type);
}
}
if (isPresent(pipelineElement.templateDirective)) {
ListWrapper.push(directiveTypes, pipelineElement.templateDirective.type);
}
if (isPresent(pipelineElement.componentDirective)) {
ListWrapper.push(directiveTypes, pipelineElement.componentDirective.type);
}
return directiveTypes;
}
}

View File

@ -0,0 +1,36 @@
import {isPresent, BaseException} from 'facade/lang';
import {ListWrapper} from 'facade/collection';
import {ProtoView} from '../view';
import {ProtoWatchGroup} from 'change_detection/watch_group';
import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
/**
* Fills:
* - (in parent): CompileElement#inheritedElementBinder.nestedProtoView
* - CompileElement#inhertiedViewRoot
*
* Reads:
* - (in parent): CompileElement#inhertiedViewRoot
* - CompileElement#isViewRoot
*/
export class ProtoViewBuilder extends CompileStep {
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
var inheritedProtoView = null;
if (current.isViewRoot) {
inheritedProtoView = new ProtoView(current.element, new ProtoWatchGroup());
if (isPresent(parent)) {
if (isPresent(parent.inheritedElementBinder.nestedProtoView)) {
throw new BaseException('Only one nested view per element is allowed');
}
parent.inheritedElementBinder.nestedProtoView = inheritedProtoView;
}
} else if (isPresent(parent)) {
inheritedProtoView = parent.inheritedProtoView;
}
current.inheritedProtoView = inheritedProtoView;
}
}

View File

@ -0,0 +1,54 @@
import {RegExpWrapper, StringWrapper} from 'facade/lang';
import {TemplateElement, Node, DOM} from 'facade/dom';
import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
// TODO(tbosch): Cannot make this const/final right now because of the transpiler...
var INTERPOLATION_REGEXP = RegExpWrapper.create('\\{\\{(.*?)\\}\\}');
/**
* Parses interpolations in direct text child nodes of the current element.
*
* Fills:
* - CompileElement#textNodeBindings
*/
export class TextInterpolationParser extends CompileStep {
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
var element = current.element;
var childNodes;
if (element instanceof TemplateElement) {
childNodes = element.content.childNodes;
} else {
childNodes = element.childNodes;
}
for (var i=0; i<childNodes.length; i++) {
var node = childNodes[i];
if (node.nodeType === Node.TEXT_NODE) {
this._parseTextNode(current, node, i);
}
}
}
_parseTextNode(pipelineElement, node, nodeIndex) {
// TODO: escape fixed string quotes
// TODO: add braces around the expression
// TODO: suppress empty strings
// TODO: add stringify formatter
var parts = StringWrapper.split(node.nodeValue, INTERPOLATION_REGEXP);
if (parts.length > 1) {
for (var i=0; i<parts.length; i++) {
if (i%2 === 0) {
// fixed string
parts[i] = "'" + parts[i] + "'";
} else {
// expression
parts[i] = "" + parts[i] + "";
}
}
DOM.setText(node, ' ');
pipelineElement.addTextNodeBinding(nodeIndex, parts.join('+'));
}
}
}

View File

@ -0,0 +1,62 @@
import {isBlank, isPresent} from 'facade/lang';
import {DOM} from 'facade/dom';
import {MapWrapper, StringMapWrapper} from 'facade/collection';
import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
/**
* Splits views at template directives:
* Replaces the element with an empty <template> element that contains the
* template directive and all property bindings needed for the template directive.
*
* Fills:
* - CompileElement#isViewRoot
*
* Updates:
* - CompileElement#templateDirective
* - CompileElement#propertyBindings
*
* Reads:
* - CompileElement#templateDirective
* - CompileElement#propertyBindings
*/
export class ViewSplitter extends CompileStep {
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
var element = current.element;
if (isPresent(current.templateDirective)) {
var templateElement = DOM.createTemplate('');
var templateBoundProperties = MapWrapper.create();
var nonTemplateBoundProperties = MapWrapper.create();
this._splitElementPropertyBindings(current, templateBoundProperties, nonTemplateBoundProperties);
var newParentElement = new CompileElement(templateElement);
newParentElement.propertyBindings = templateBoundProperties;
newParentElement.templateDirective = current.templateDirective;
control.addParent(newParentElement);
// disconnect child view from their parent view
element.remove();
current.templateDirective = null;
current.propertyBindings = nonTemplateBoundProperties;
current.isViewRoot = true;
} else if (isBlank(parent)) {
current.isViewRoot = true;
}
}
_splitElementPropertyBindings(compileElement, templateBoundProperties, nonTemplateBoundProperties) {
var dirBindings = compileElement.templateDirective.annotation.bind;
if (isPresent(dirBindings) && isPresent(compileElement.propertyBindings)) {
MapWrapper.forEach(compileElement.propertyBindings, (expr, elProp) => {
if (isPresent(StringMapWrapper.get(dirBindings, elProp))) {
MapWrapper.set(templateBoundProperties, elProp, expr);
} else {
MapWrapper.set(nonTemplateBoundProperties, elProp, expr);
}
});
}
}
}

View File

@ -0,0 +1,27 @@
library facade.di.reflector;
import 'dart:mirrors';
import '../annotations/directive.dart';
import './annotated_type.dart';
import 'package:facade/lang.dart';
/**
* Interface representing a way of extracting [Directive] annotations from
* [Type]. This interface has three native implementations:
*
* 1) JavaScript native implementation
* 2) Dart reflective implementation
* 3) Dart transformer generated implementation
*/
class Reflector {
AnnotatedType annotatedType(Type type) {
var directiveAnnotations = reflectType(type).metadata
.map( (im) => im.reflectee)
.where( (annotation) => annotation is Directive);
if (directiveAnnotations.isEmpty) {
throw new BaseException('No Directive annotation found on '+stringify(type));
}
return new AnnotatedType(type, directiveAnnotations.first);
}
}

View File

@ -0,0 +1,26 @@
import {Type, isPresent, BaseException} from 'facade/lang';
import {Directive} from '../annotations/directive';
import {AnnotatedType} from './annotated_type';
/**
* Interface representing a way of extracting [Directive] annotations from
* [Type]. This interface has three native implementations:
*
* 1) JavaScript native implementation
* 2) Dart reflective implementation
* 3) Dart transformer generated implementation
*/
export class Reflector {
annotatedType(type:Type):AnnotatedType {
var annotations = type.annotations;
if (annotations) {
for (var i=0; i<annotations.length; i++) {
var annotation = annotations[i];
if (annotation instanceof Directive) {
return new AnnotatedType(type, annotation);
}
}
}
throw new BaseException('No Directive annotation found on ' + type.name);
}
}

View File

@ -1,30 +1,107 @@
import {List, ListWrapper, StringMapWrapper} from 'facade/collection';
import {RegExpWrapper, RegExpMatcherWrapper, CONST, isPresent, isBlank} from 'facade/lang';
import {List, Map, ListWrapper, MapWrapper} from 'facade/collection';
import {isPresent, isBlank, RegExpWrapper, RegExpMatcherWrapper, StringWrapper} from 'facade/lang';
const _EMPTY_ATTR_VALUE = '';
// TODO: Can't use `const` here as
// in Dart this is not transpiled into `final` yet...
var _SELECTOR_REGEXP =
RegExpWrapper.create('^([-\\w]+)|' + // "tag"
'(?:\\.([-\\w]+))|' + // ".class"
'(?:\\[([-\\w*]+)(?:=([^\\]]*))?\\])'); // "[name]", "[name=value]" or "[name*=value]"
/**
* A css selector contains an element name,
* css classes and attribute/value pairs with the purpose
* of selecting subsets out of them.
*/
export class CssSelector {
static parse(selector:string):CssSelector {
var cssSelector = new CssSelector();
var matcher = RegExpWrapper.matcher(_SELECTOR_REGEXP, selector);
var match;
while (isPresent(match = RegExpMatcherWrapper.next(matcher))) {
if (isPresent(match[1])) {
cssSelector.setElement(match[1]);
}
if (isPresent(match[2])) {
cssSelector.addClassName(match[2]);
}
if (isPresent(match[3])) {
cssSelector.addAttribute(match[3], match[4]);
}
}
return cssSelector;
}
constructor() {
this.element = null;
this.classNames = ListWrapper.create();
this.attrs = ListWrapper.create();
}
setElement(element:string = null) {
if (isPresent(element)) {
element = element.toLowerCase();
}
this.element = element;
}
addAttribute(name:string, value:string = _EMPTY_ATTR_VALUE) {
ListWrapper.push(this.attrs, name.toLowerCase());
if (isPresent(value)) {
value = value.toLowerCase();
} else {
value = _EMPTY_ATTR_VALUE;
}
ListWrapper.push(this.attrs, value);
}
addClassName(name:string) {
ListWrapper.push(this.classNames, name.toLowerCase());
}
toString():string {
var res = '';
if (isPresent(this.element)) {
res += this.element;
}
if (isPresent(this.classNames)) {
for (var i=0; i<this.classNames.length; i++) {
res += '.' + this.classNames[i];
}
}
if (isPresent(this.attrs)) {
for (var i=0; i<this.attrs.length;) {
var attrName = this.attrs[i++];
var attrValue = this.attrs[i++]
res += '[' + attrName;
if (attrValue.length > 0) {
res += '=' + attrValue;
}
res += ']';
}
}
return res;
}
}
/**
* Reads a list of CssSelectors and allows to calculate which ones
* are contained in a given CssSelector.
*/
export class SelectorMatcher {
/* TODO: Add these fields when the transpiler supports fields
_elementMap:Map<String, List>;
_elementPartialMap:Map<String, Selector>;
_classMap:Map<String, List>;
_classPartialMap:Map<String, Selector>;
_attrValueMap:Map<String, Map<String, List>>;
_attrValuePartialMap:Map<String, Map<String, Selector>>;
*/
constructor() {
this._selectables = ListWrapper.create();
this._elementMap = StringMapWrapper.create();
this._elementPartialMap = StringMapWrapper.create();
this._elementMap = MapWrapper.create();
this._elementPartialMap = MapWrapper.create();
this._classMap = StringMapWrapper.create();
this._classPartialMap = StringMapWrapper.create();
this._classMap = MapWrapper.create();
this._classPartialMap = MapWrapper.create();
this._attrValueMap = StringMapWrapper.create();
this._attrValuePartialMap = StringMapWrapper.create();
this._attrValueMap = MapWrapper.create();
this._attrValuePartialMap = MapWrapper.create();
}
/**
@ -60,16 +137,15 @@ export class SelectorMatcher {
}
if (isPresent(attrs)) {
for (var index = 0; index<attrs.length; index++) {
var isTerminal = index === attrs.length - 1;
var attr = attrs[index];
var attrName = attr.name;
var attrValue = isPresent(attr.value) ? attr.value : _EMPTY_ATTR_VALUE;
for (var index = 0; index<attrs.length; ) {
var isTerminal = index === attrs.length - 2;
var attrName = attrs[index++];
var attrValue = attrs[index++];
var map = isTerminal ? matcher._attrValueMap : matcher._attrValuePartialMap;
var valuesMap = StringMapWrapper.get(map, attrName)
var valuesMap = MapWrapper.get(map, attrName)
if (isBlank(valuesMap)) {
valuesMap = StringMapWrapper.create();
StringMapWrapper.set(map, attrName, valuesMap);
valuesMap = MapWrapper.create();
MapWrapper.set(map, attrName, valuesMap);
}
if (isTerminal) {
this._addTerminal(valuesMap, attrValue, selectable);
@ -79,21 +155,21 @@ export class SelectorMatcher {
}
}
}
// TODO: map:StringMap when we have a StringMap type...
_addTerminal(map, name:string, selectable) {
var terminalList = StringMapWrapper.get(map, name)
_addTerminal(map:Map<string,string>, name:string, selectable) {
var terminalList = MapWrapper.get(map, name)
if (isBlank(terminalList)) {
terminalList = ListWrapper.create();
StringMapWrapper.set(map, name, terminalList);
MapWrapper.set(map, name, terminalList);
}
ListWrapper.push(terminalList, selectable);
}
// TODO: map:StringMap when we have a StringMap type...
_addPartial(map, name:string) {
var matcher = StringMapWrapper.get(map, name)
_addPartial(map:Map<string,string>, name:string) {
var matcher = MapWrapper.get(map, name)
if (isBlank(matcher)) {
matcher = new SelectorMatcher();
StringMapWrapper.set(map, name, matcher);
MapWrapper.set(map, name, matcher);
}
return matcher;
}
@ -121,25 +197,27 @@ export class SelectorMatcher {
}
if (isPresent(attrs)) {
for (var index = 0; index<attrs.length; index++) {
var attr = attrs[index];
var attrName = attr.name;
var attrValue = isPresent(attr.value) ? attr.value : _EMPTY_ATTR_VALUE;
for (var index = 0; index<attrs.length;) {
var attrName = attrs[index++];
var attrValue = attrs[index++];
var valuesMap = StringMapWrapper.get(this._attrValueMap, attrName)
var valuesMap = MapWrapper.get(this._attrValueMap, attrName);
if (!StringWrapper.equals(attrValue, _EMPTY_ATTR_VALUE)) {
this._matchTerminal(valuesMap, _EMPTY_ATTR_VALUE, matchedCallback);
}
this._matchTerminal(valuesMap, attrValue, matchedCallback);
valuesMap = StringMapWrapper.get(this._attrValuePartialMap, attrName)
valuesMap = MapWrapper.get(this._attrValuePartialMap, attrName)
this._matchPartial(valuesMap, attrValue, cssSelector, matchedCallback);
}
}
}
// TODO: map:StringMap when we have a StringMap type...
_matchTerminal(map, name, matchedCallback) {
_matchTerminal(map:Map<string,string> = null, name, matchedCallback) {
if (isBlank(map) || isBlank(name)) {
return;
}
var selectables = StringMapWrapper.get(map, name)
var selectables = MapWrapper.get(map, name)
if (isBlank(selectables)) {
return;
}
@ -147,12 +225,12 @@ export class SelectorMatcher {
matchedCallback(selectables[index]);
}
}
// TODO: map:StringMap when we have a StringMap type...
_matchPartial(map, name, cssSelector, matchedCallback) {
_matchPartial(map:Map<string,string> = null, name, cssSelector, matchedCallback) {
if (isBlank(map) || isBlank(name)) {
return;
}
var nestedSelector = StringMapWrapper.get(map, name)
var nestedSelector = MapWrapper.get(map, name)
if (isBlank(nestedSelector)) {
return;
}
@ -162,71 +240,3 @@ export class SelectorMatcher {
nestedSelector.match(cssSelector, matchedCallback);
}
}
export class Attr {
@CONST()
constructor(name:string, value:string = null) {
this.name = name;
this.value = value;
}
}
// TODO: Can't use `const` here as
// in Dart this is not transpiled into `final` yet...
var _SELECTOR_REGEXP =
RegExpWrapper.create('^([-\\w]+)|' + // "tag"
'(?:\\.([-\\w]+))|' + // ".class"
'(?:\\[([-\\w*]+)(?:=([^\\]]*))?\\])'); // "[name]", "[name=value]" or "[name*=value]"
export class CssSelector {
static parse(selector:string):CssSelector {
var element = null;
var classNames = ListWrapper.create();
var attrs = ListWrapper.create();
selector = selector.toLowerCase();
var matcher = RegExpWrapper.matcher(_SELECTOR_REGEXP, selector);
var match;
while (isPresent(match = RegExpMatcherWrapper.next(matcher))) {
if (isPresent(match[1])) {
element = match[1];
}
if (isPresent(match[2])) {
ListWrapper.push(classNames, match[2]);
}
if (isPresent(match[3])) {
ListWrapper.push(attrs, new Attr(match[3], match[4]));
}
}
return new CssSelector(element, classNames, attrs);
}
// TODO: do a toLowerCase() for all arguments
@CONST()
constructor(element:string, classNames:List<string>, attrs:List<Attr>) {
this.element = element;
this.classNames = classNames;
this.attrs = attrs;
}
toString():string {
var res = '';
if (isPresent(this.element)) {
res += this.element;
}
if (isPresent(this.classNames)) {
for (var i=0; i<this.classNames.length; i++) {
res += '.' + this.classNames[i];
}
}
if (isPresent(this.attrs)) {
for (var i=0; i<this.attrs.length; i++) {
var attr = this.attrs[i];
res += '[' + attr.name;
if (isPresent(attr.value)) {
res += '=' + attr.value;
}
res += ']';
}
}
return res;
}
}

View File

@ -1,11 +1,11 @@
import {Promise} from 'facade/async';
//import {Document} from 'facade/dom';
/**
* Strategy to load component templates.
*/
export class TemplateLoader {
constructor() {}
load(url:String):Promise/*<Document>*/ {
load(url:String):Promise<Document> {
return null;
}
}

View File

@ -2,22 +2,21 @@ import {DOM, Element, Node, Text, DocumentFragment, TemplateElement} from 'facad
import {ListWrapper} from 'facade/collection';
import {ProtoWatchGroup, WatchGroup, WatchGroupDispatcher} from 'change_detection/watch_group';
import {Record} from 'change_detection/record';
import {AST} from 'change_detection/parser/ast';
import {ProtoElementInjector, ElementInjector} from './element_injector';
// Seems like we are stripping the generics part of List and dartanalyzer
// complains about ElementBinder being unused. Comment back in once it makes it
// into the generated code.
// import {ElementBinder} from './element_binder';
import {ElementBinder} from './element_binder';
import {SetterFn} from 'change_detection/parser/closure_map';
import {FIELD, IMPLEMENTS, int, isPresent, isBlank} from 'facade/lang';
import {List} from 'facade/collection';
import {Injector} from 'di/di';
const NG_BINDING_CLASS = 'ng-binding';
/***
* Const of making objects: http://jsperf.com/instantiate-size-of-object
*/
@IMPLEMENTS(WatchGroupDispatcher)
export class View {
@FIELD('final fragment:DocumentFragment')
/// This list matches the _nodes list. It is sparse, since only Elements have ElementInjector
@FIELD('final rootElementInjectors:List<ElementInjector>')
@FIELD('final elementInjectors:List<ElementInjector>')
@ -28,12 +27,11 @@ export class View {
/// to keep track of the nodes.
@FIELD('final nodes:List<Node>')
@FIELD('final onChangeDispatcher:OnChangeDispatcher')
constructor(fragment:DocumentFragment, elementInjector:List,
constructor(nodes:List<Node>, elementInjectors:List,
rootElementInjectors:List, textNodes:List, bindElements:List,
protoWatchGroup:ProtoWatchGroup, context) {
this.fragment = fragment;
this.nodes = ListWrapper.clone(fragment.childNodes);
this.elementInjectors = elementInjector;
this.nodes = nodes;
this.elementInjectors = elementInjectors;
this.rootElementInjectors = rootElementInjectors;
this.onChangeDispatcher = null;
this.textNodes = textNodes;
@ -60,27 +58,26 @@ export class View {
}
export class ProtoView {
@FIELD('final _template:TemplateElement')
@FIELD('final _elementBinders:List<ElementBinder>')
@FIELD('final _protoWatchGroup:ProtoWatchGroup')
@FIELD('final _useRootElement:bool')
@FIELD('final element:Element')
@FIELD('final elementBinders:List<ElementBinder>')
@FIELD('final protoWatchGroup:ProtoWatchGroup')
constructor(
template:TemplateElement,
elementBinders:List,
protoWatchGroup:ProtoWatchGroup,
useRootElement:boolean) {
this._template = template;
this._elementBinders = elementBinders;
this._protoWatchGroup = protoWatchGroup;
// not implemented
this._useRootElement = useRootElement;
template:Element,
protoWatchGroup:ProtoWatchGroup) {
this.element = template;
this.elementBinders = [];
this.protoWatchGroup = protoWatchGroup;
this.textNodesWithBindingCount = 0;
this.elementsWithBindingCount = 0;
}
instantiate(context, appInjector:Injector):View {
var fragment = DOM.clone(this._template.content);
var elements = DOM.querySelectorAll(fragment, ".ng-binding");
var binders = this._elementBinders;
var clone = DOM.clone(this.element);
var elements = ListWrapper.clone(DOM.getElementsByClassName(clone, NG_BINDING_CLASS));
if (DOM.hasClass(clone, NG_BINDING_CLASS)) {
ListWrapper.insert(elements, 0, clone);
}
var binders = this.elementBinders;
/**
* TODO: vsavkin: benchmark
@ -92,16 +89,77 @@ export class ProtoView {
var bindElements = ProtoView._bindElements(elements, binders);
ProtoView._instantiateDirectives(elementInjectors, appInjector);
return new View(fragment, elementInjectors, rootElementInjectors, textNodes,
bindElements, this._protoWatchGroup, context);
var viewNodes;
if (clone instanceof TemplateElement) {
viewNodes = ListWrapper.clone(clone.content.childNodes);
} else {
viewNodes = [clone];
}
return new View(viewNodes, elementInjectors, rootElementInjectors, textNodes,
bindElements, this.protoWatchGroup, context);
}
bindElement(protoElementInjector:ProtoElementInjector):ElementBinder {
var elBinder = new ElementBinder(protoElementInjector);
ListWrapper.push(this.elementBinders, elBinder);
return elBinder;
}
/**
* Adds a text node binding for the last created ElementBinder via bindElement
*/
bindTextNode(indexInParent:int, expression:AST) {
var elBinder = this.elementBinders[this.elementBinders.length-1];
ListWrapper.push(elBinder.textNodeIndices, indexInParent);
this.protoWatchGroup.watch(expression, this.textNodesWithBindingCount++);
}
/**
* Adds an element property binding for the last created ElementBinder via bindElement
*/
bindElementProperty(propertyName:string, expression:AST) {
var elBinder = this.elementBinders[this.elementBinders.length-1];
if (!elBinder.hasElementPropertyBindings) {
elBinder.hasElementPropertyBindings = true;
this.elementsWithBindingCount++;
}
this.protoWatchGroup.watch(expression,
new ElementPropertyMemento(
this.elementsWithBindingCount-1,
propertyName
)
);
}
/**
* Adds a directive property binding for the last created ElementBinder via bindElement
*/
bindDirectiveProperty(
directiveIndex:number,
expression:AST,
setterName:string,
setter:SetterFn) {
this.protoWatchGroup.watch(
expression,
new DirectivePropertyMemento(
this.elementBinders.length-1,
directiveIndex,
setterName,
setter
)
);
}
static _createElementInjectors(elements, binders) {
var injectors = ListWrapper.createFixedSize(binders.length);
for (var i = 0; i < binders.length; ++i) {
var proto = binders[i].protoElementInjector;
var parentElementInjector = isPresent(proto.parent) ? injectors[proto.parent.index] : null;
injectors[i] = ProtoView._createElementInjector(elements[i], parentElementInjector, proto);
if (isPresent(proto)) {
var parentElementInjector = isPresent(proto.parent) ? injectors[proto.parent.index] : null;
injectors[i] = ProtoView._createElementInjector(elements[i], parentElementInjector, proto);
} else {
injectors[i] = null;
}
}
return injectors;
}
@ -115,7 +173,7 @@ export class ProtoView {
static _createElementInjector(element, parent:ElementInjector, proto:ProtoElementInjector) {
//TODO: vsavkin: pass element to `proto.instantiate()` once https://github.com/angular/angular/pull/98 is merged
return proto.hasBindings ? proto.instantiate(parent, null) : null;
return proto.instantiate(parent, null);
}
static _rootElementInjectors(injectors) {
@ -150,7 +208,7 @@ export class ProtoView {
export class ElementPropertyMemento {
@FIELD('final _elementIndex:int')
@FIELD('final _propertyIndex:string')
@FIELD('final _propertyName:string')
constructor(elementIndex:int, propertyName:string) {
this._elementIndex = elementIndex;
this._propertyName = propertyName;