chore(packaging): move files to match target file structure

This commit is contained in:
Yegor Jbanov
2015-02-04 23:05:13 -08:00
parent 7ce4f66cdc
commit 3820609f24
149 changed files with 0 additions and 77 deletions

View File

@ -0,0 +1,142 @@
import {ABSTRACT, CONST, normalizeBlank} from 'facade/src/lang';
import {List} from 'facade/src/collection';
import {TemplateConfig} from './template_config';
import {ShadowDomStrategy} from '../compiler/shadow_dom';
@ABSTRACT()
export class Directive {
selector:any; //string;
bind:any;
lightDomServices:any; //List;
implementsTypes:any; //List;
lifecycle:any; //List
@CONST()
constructor({
selector,
bind,
lightDomServices,
implementsTypes,
lifecycle
}:{
selector:string,
bind:any,
lightDomServices:List,
implementsTypes:List,
lifecycle:List
}={})
{
this.selector = selector;
this.lightDomServices = lightDomServices;
this.implementsTypes = implementsTypes;
this.bind = bind;
this.lifecycle = lifecycle;
}
}
export class Component extends Directive {
//TODO: vsavkin: uncomment it once the issue with defining fields in a sublass works
template:any; //TemplateConfig;
lightDomServices:any; //List;
shadowDomServices:any; //List;
componentServices:any; //List;
shadowDom:any; //ShadowDomStrategy;
lifecycle:any; //List
@CONST()
constructor({
selector,
bind,
template,
lightDomServices,
shadowDomServices,
componentServices,
implementsTypes,
shadowDom,
lifecycle
}:{
selector:String,
bind:Object,
template:TemplateConfig,
lightDomServices:List,
shadowDomServices:List,
componentServices:List,
implementsTypes:List,
shadowDom:ShadowDomStrategy,
lifecycle:List
}={})
{
super({
selector: selector,
bind: bind,
lightDomServices: lightDomServices,
implementsTypes: implementsTypes,
lifecycle: lifecycle
});
this.template = template;
this.lightDomServices = lightDomServices;
this.shadowDomServices = shadowDomServices;
this.componentServices = componentServices;
this.shadowDom = shadowDom;
this.lifecycle = lifecycle;
}
}
export class Decorator extends Directive {
compileChildren: boolean;
@CONST()
constructor({
selector,
bind,
lightDomServices,
implementsTypes,
lifecycle,
compileChildren = true,
}:{
selector:string,
bind:any,
lightDomServices:List,
implementsTypes:List,
lifecycle:List,
compileChildren:boolean
}={})
{
this.compileChildren = compileChildren;
super({
selector: selector,
bind: bind,
lightDomServices: lightDomServices,
implementsTypes: implementsTypes,
lifecycle: lifecycle
});
}
}
export class Template extends Directive {
@CONST()
constructor({
selector,
bind,
lightDomServices,
implementsTypes,
lifecycle
}:{
selector:string,
bind:any,
lightDomServices:List,
implementsTypes:List,
lifecycle:List
}={})
{
super({
selector: selector,
bind: bind,
lightDomServices: lightDomServices,
implementsTypes: implementsTypes,
lifecycle: lifecycle
});
}
}
export var onDestroy = "onDestroy";

View File

@ -0,0 +1,14 @@
import {CONST} from 'facade/src/lang';
import {DependencyAnnotation} from 'di/di';
/**
* The directive can inject an emitter function that would emit events onto the
* directive host element.
*/
export class EventEmitter extends DependencyAnnotation {
eventName: string;
@CONST()
constructor(eventName) {
this.eventName = eventName;
}
}

View File

@ -0,0 +1,31 @@
import {ABSTRACT, CONST, Type} from 'facade/src/lang';
import {List} from 'facade/src/collection';
export class TemplateConfig {
url:any; //string;
inline:any; //string;
directives:any; //List<Type>;
formatters:any; //List<Type>;
source:any;//List<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

@ -0,0 +1,22 @@
import {CONST} from 'facade/src/lang';
import {DependencyAnnotation} from 'di/di';
/**
* The directive can only be injected from the current element
* or from its parent.
*/
export class Parent extends DependencyAnnotation {
@CONST()
constructor() {
}
}
/**
* The directive can only be injected from the current element
* or from its ancestor.
*/
export class Ancestor extends DependencyAnnotation {
@CONST()
constructor() {
}
}

126
modules/angular2/src/core/application.js vendored Normal file
View File

@ -0,0 +1,126 @@
import {Injector, bind, OpaqueToken} from 'di/di';
import {Type, FIELD, isBlank, isPresent, BaseException, assertionsEnabled, print} from 'facade/src/lang';
import {DOM, Element} from 'facade/src/dom';
import {Compiler, CompilerCache} from './compiler/compiler';
import {ProtoView} from './compiler/view';
import {Reflector, reflector} from 'reflection/src/reflection';
import {Parser, Lexer, ChangeDetection, dynamicChangeDetection, jitChangeDetection} from 'change_detection/change_detection';
import {TemplateLoader} from './compiler/template_loader';
import {DirectiveMetadataReader} from './compiler/directive_metadata_reader';
import {DirectiveMetadata} from './compiler/directive_metadata';
import {List, ListWrapper} from 'facade/src/collection';
import {PromiseWrapper} from 'facade/src/async';
import {VmTurnZone} from 'core/src/zone/vm_turn_zone';
import {LifeCycle} from 'core/src/life_cycle/life_cycle';
var _rootInjector: Injector;
// Contains everything that is safe to share between applications.
var _rootBindings = [
bind(Reflector).toValue(reflector),
bind(ChangeDetection).toValue(dynamicChangeDetection),
Compiler,
CompilerCache,
TemplateLoader,
DirectiveMetadataReader,
Parser,
Lexer
];
export var appViewToken = new OpaqueToken('AppView');
export var appChangeDetectorToken = new OpaqueToken('AppChangeDetector');
export var appElementToken = new OpaqueToken('AppElement');
export var appComponentAnnotatedTypeToken = new OpaqueToken('AppComponentAnnotatedType');
export var appDocumentToken = new OpaqueToken('AppDocument');
function _injectorBindings(appComponentType) {
return [
bind(appDocumentToken).toValue(DOM.defaultDoc()),
bind(appComponentAnnotatedTypeToken).toFactory((reader) => {
// TODO(rado): inspect annotation here and warn if there are bindings,
// lightDomServices, and other component annotations that are skipped
// for bootstrapping components.
return reader.read(appComponentType);
}, [DirectiveMetadataReader]),
bind(appElementToken).toFactory((appComponentAnnotatedType, appDocument) => {
var selector = appComponentAnnotatedType.annotation.selector;
var element = DOM.querySelector(appDocument, selector);
if (isBlank(element)) {
throw new BaseException(`The app selector "${selector}" did not match any elements`);
}
return element;
}, [appComponentAnnotatedTypeToken, appDocumentToken]),
bind(appViewToken).toAsyncFactory((changeDetection, compiler, injector, appElement,
appComponentAnnotatedType) => {
return compiler.compile(appComponentAnnotatedType.type, null).then(
(protoView) => {
var appProtoView = ProtoView.createRootProtoView(protoView,
appElement, appComponentAnnotatedType, changeDetection.createProtoChangeDetector('root'));
// The light Dom of the app element is not considered part of
// the angular application. Thus the context and lightDomInjector are
// empty.
var view = appProtoView.instantiate(null);
view.hydrate(injector, null, new Object());
return view;
});
}, [ChangeDetection, Compiler, Injector, appElementToken, appComponentAnnotatedTypeToken]),
bind(appChangeDetectorToken).toFactory((rootView) => rootView.changeDetector,
[appViewToken]),
bind(appComponentType).toFactory((rootView) => rootView.elementInjectors[0].getComponent(),
[appViewToken]),
bind(LifeCycle).toFactory((cd) => new LifeCycle(cd, assertionsEnabled()), [appChangeDetectorToken])
];
}
function _createVmZone(givenReporter:Function){
var defaultErrorReporter = (exception, stackTrace) => {
var longStackTrace = ListWrapper.join(stackTrace, "\n\n-----async gap-----\n");
print(`${exception}\n\n${longStackTrace}`);
throw exception;
};
var reporter = isPresent(givenReporter) ? givenReporter : defaultErrorReporter;
var zone = new VmTurnZone({enableLongStackTrace: assertionsEnabled()});
zone.initCallbacks({onErrorHandler: reporter});
return zone;
}
// Multiple calls to this method are allowed. Each application would only share
// _rootInjector, which is not user-configurable by design, thus safe to share.
export function bootstrap(appComponentType: Type, bindings=null, givenBootstrapErrorReporter=null) {
var bootstrapProcess = PromiseWrapper.completer();
var zone = _createVmZone(givenBootstrapErrorReporter);
zone.run(() => {
// TODO(rado): prepopulate template cache, so applications with only
// index.html and main.js are possible.
var appInjector = _createAppInjector(appComponentType, bindings);
PromiseWrapper.then(appInjector.asyncGet(LifeCycle),
(lc) => {
lc.registerWith(zone);
lc.tick(); //the first tick that will bootstrap the app
bootstrapProcess.complete(appInjector);
},
(err) => {
bootstrapProcess.reject(err)
});
});
return bootstrapProcess.promise;
}
function _createAppInjector(appComponentType: Type, bindings: List): Injector {
if (isBlank(_rootInjector)) _rootInjector = new Injector(_rootBindings);
var mergedBindings = isPresent(bindings) ?
ListWrapper.concat(_injectorBindings(appComponentType), bindings) :
_injectorBindings(appComponentType);
return _rootInjector.createChild(mergedBindings);
}

View File

@ -0,0 +1,25 @@
import {ChangeDetector, CHECK_ONCE, DETACHED, CHECK_ALWAYS} from 'change_detection/change_detection';
export class BindingPropagationConfig {
_cd:ChangeDetector;
constructor(cd:ChangeDetector) {
this._cd = cd;
}
shouldBePropagated() {
this._cd.mode = CHECK_ONCE;
}
shouldBePropagatedFromRoot() {
this._cd.markPathToRootAsCheckOnce();
}
shouldNotPropagate() {
this._cd.mode = DETACHED;
}
shouldAlwaysPropagate() {
this._cd.mode = CHECK_ALWAYS;
}
}

View File

@ -0,0 +1,111 @@
import {Type, FIELD, isBlank, isPresent, BaseException, stringify} from 'facade/src/lang';
import {Promise, PromiseWrapper} from 'facade/src/async';
import {List, ListWrapper, MapWrapper} from 'facade/src/collection';
import {DOM, Element} from 'facade/src/dom';
import {ChangeDetection, Parser} from 'change_detection/change_detection';
import {DirectiveMetadataReader} from './directive_metadata_reader';
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 {DirectiveMetadata} from './directive_metadata';
import {Component} from '../annotations/annotations';
import {Content} from './shadow_dom_emulation/content_tag';
/**
* Cache that stores the ProtoView of the template of a component.
* Used to prevent duplicate work and resolve cyclic dependencies.
*/
export class CompilerCache {
_cache:Map;
constructor() {
this._cache = MapWrapper.create();
}
set(component:Type, protoView:ProtoView) {
MapWrapper.set(this._cache, component, protoView);
}
get(component:Type):ProtoView {
var result = MapWrapper.get(this._cache, component);
if (isBlank(result)) {
// need to normalize undefined to null so that type checking passes :-(
return null;
}
return result;
}
clear() {
this._cache = MapWrapper.create();
}
}
/**
* 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 {
_reader: DirectiveMetadataReader;
_parser:Parser;
_compilerCache:CompilerCache;
_changeDetection:ChangeDetection;
constructor(changeDetection:ChangeDetection, templateLoader:TemplateLoader, reader: DirectiveMetadataReader, parser:Parser, cache:CompilerCache) {
this._changeDetection = changeDetection;
this._reader = reader;
this._parser = parser;
this._compilerCache = cache;
}
createSteps(component:DirectiveMetadata):List<CompileStep> {
var dirs = ListWrapper.map(component.componentDirectives, (d) => this._reader.read(d));
return createDefaultSteps(this._changeDetection, this._parser, component, dirs);
}
compile(component:Type, templateRoot:Element = null):Promise<ProtoView> {
var templateCache = null;
// TODO load all components that have urls
// transitively via the _templateLoader and store them in templateCache
return PromiseWrapper.resolve(this.compileAllLoaded(
templateCache, this._reader.read(component), templateRoot)
);
}
// public so that we can compile in sync in performance tests.
compileAllLoaded(templateCache, component:DirectiveMetadata, templateRoot:Element = null):ProtoView {
var rootProtoView = this._compilerCache.get(component.type);
if (isPresent(rootProtoView)) {
return rootProtoView;
}
if (isBlank(templateRoot)) {
// TODO: read out the cache if templateRoot = null. Could contain:
// - templateRoot string
// - precompiled template
// - ProtoView
var annotation:any = component.annotation;
templateRoot = DOM.createTemplate(annotation.template.inline);
}
var pipeline = new CompilePipeline(this.createSteps(component));
var compileElements = pipeline.process(templateRoot);
rootProtoView = compileElements[0].inheritedProtoView;
// Save the rootProtoView before we recurse so that we are able
// to compile components that use themselves in their template.
this._compilerCache.set(component.type, rootProtoView);
for (var i=0; i<compileElements.length; i++) {
var ce = compileElements[i];
if (isPresent(ce.componentDirective)) {
ce.inheritedElementBinder.nestedProtoView = this.compileAllLoaded(templateCache, ce.componentDirective, null);
}
}
return rootProtoView;
}
}

View File

@ -0,0 +1,22 @@
import {Type} from 'facade/src/lang';
import {Directive} from '../annotations/annotations'
import {List} from 'facade/src/collection'
import {ShadowDomStrategy} from './shadow_dom';
/**
* Combination of a type with the Directive annotation
*/
export class DirectiveMetadata {
type:Type;
annotation:Directive;
shadowDomStrategy:ShadowDomStrategy;
componentDirectives:List<Type>;
constructor(type:Type, annotation:Directive, shadowDomStrategy:ShadowDomStrategy,
componentDirectives:List<Type>) {
this.annotation = annotation;
this.type = type;
this.shadowDomStrategy = shadowDomStrategy;
this.componentDirectives = componentDirectives;
}
}

View File

@ -0,0 +1,48 @@
import {Type, isPresent, BaseException, stringify} from 'facade/src/lang';
import {List, ListWrapper} from 'facade/src/collection';
import {Directive, Component} from '../annotations/annotations';
import {DirectiveMetadata} from './directive_metadata';
import {reflector} from 'reflection/src/reflection';
import {ShadowDom, ShadowDomStrategy, ShadowDomNative} from './shadow_dom';
export class DirectiveMetadataReader {
read(type:Type):DirectiveMetadata {
var annotations = reflector.annotations(type);
if (isPresent(annotations)) {
for (var i=0; i<annotations.length; i++) {
var annotation = annotations[i];
if (annotation instanceof Component) {
var shadowDomStrategy = this.parseShadowDomStrategy(annotation);
return new DirectiveMetadata(
type,
annotation,
shadowDomStrategy,
this.componentDirectivesMetadata(annotation, shadowDomStrategy)
);
}
if (annotation instanceof Directive) {
return new DirectiveMetadata(type, annotation, null, null);
}
}
}
throw new BaseException(`No Directive annotation found on ${stringify(type)}`);
}
parseShadowDomStrategy(annotation:Component):ShadowDomStrategy{
return isPresent(annotation.shadowDom) ? annotation.shadowDom : ShadowDomNative;
}
componentDirectivesMetadata(annotation:Component, shadowDomStrategy:ShadowDomStrategy):List<Type> {
var polyDirs = shadowDomStrategy.polyfillDirectives();
var template = annotation.template;
var templateDirs = isPresent(template) && isPresent(template.directives) ? template.directives : [];
var res = [];
res = ListWrapper.concat(res, templateDirs)
res = ListWrapper.concat(res, polyDirs)
return res;
}
}

View File

@ -0,0 +1,30 @@
import {ProtoElementInjector} from './element_injector';
import {FIELD} from 'facade/src/lang';
import {MapWrapper} from 'facade/src/collection';
import {DirectiveMetadata} from './directive_metadata';
import {List, Map} from 'facade/src/collection';
import {ProtoView} from './view';
export class ElementBinder {
protoElementInjector:ProtoElementInjector;
componentDirective:DirectiveMetadata;
templateDirective:DirectiveMetadata;
textNodeIndices:List<int>;
hasElementPropertyBindings:boolean;
nestedProtoView: ProtoView;
events:Map;
constructor(
protoElementInjector: ProtoElementInjector, componentDirective:DirectiveMetadata, templateDirective:DirectiveMetadata) {
this.protoElementInjector = protoElementInjector;
this.componentDirective = componentDirective;
this.templateDirective = templateDirective;
// updated later when events are bound
this.events = null;
// updated later when text nodes are bound
this.textNodeIndices = null;
// updated later when element properties are bound
this.hasElementPropertyBindings = false;
// updated later, so we are able to resolve cycles
this.nestedProtoView = null;
}
}

View File

@ -0,0 +1,602 @@
import {FIELD, isPresent, isBlank, Type, int, BaseException} from 'facade/src/lang';
import {Math} from 'facade/src/math';
import {List, ListWrapper, MapWrapper} from 'facade/src/collection';
import {Injector, Key, Dependency, bind, Binding, NoProviderError, ProviderError, CyclicDependencyError} from 'di/di';
import {Parent, Ancestor} from 'core/src/annotations/visibility';
import {EventEmitter} from 'core/src/annotations/events';
import {onDestroy} from 'core/src/annotations/annotations';
import {View, ProtoView} from 'core/src/compiler/view';
import {LightDom, SourceLightDom, DestinationLightDom} from 'core/src/compiler/shadow_dom_emulation/light_dom';
import {ViewPort} from 'core/src/compiler/viewport';
import {NgElement} from 'core/src/dom/element';
import {Directive} from 'core/src/annotations/annotations'
import {BindingPropagationConfig} from 'core/src/compiler/binding_propagation_config'
var _MAX_DIRECTIVE_CONSTRUCTION_COUNTER = 10;
var MAX_DEPTH = Math.pow(2, 30) - 1;
var _undefined = new Object();
var _staticKeys;
class StaticKeys {
viewId:number;
ngElementId:number;
viewPortId:number;
destinationLightDomId:number;
sourceLightDomId:number;
bindingPropagationConfigId:number;
constructor() {
//TODO: vsavkin Key.annotate(Key.get(View), 'static')
this.viewId = Key.get(View).id;
this.ngElementId = Key.get(NgElement).id;
this.viewPortId = Key.get(ViewPort).id;
this.destinationLightDomId = Key.get(DestinationLightDom).id;
this.sourceLightDomId = Key.get(SourceLightDom).id;
this.bindingPropagationConfigId = Key.get(BindingPropagationConfig).id;
}
static instance() {
if (isBlank(_staticKeys)) _staticKeys = new StaticKeys();
return _staticKeys;
}
}
class TreeNode {
_parent:TreeNode;
_head:TreeNode;
_tail:TreeNode;
_next:TreeNode;
constructor(parent:TreeNode) {
this._parent = parent;
this._head = null;
this._tail = null;
this._next = null;
if (isPresent(parent)) parent._addChild(this);
}
/**
* Adds a child to the parent node. The child MUST NOT be a part of a tree.
*/
_addChild(child:TreeNode) {
if (isPresent(this._tail)) {
this._tail._next = child;
this._tail = child;
} else {
this._tail = this._head = child;
}
}
get parent() {
return this._parent;
}
set parent(node:TreeNode) {
this._parent = node;
}
get children() {
var res = [];
var child = this._head;
while (child != null) {
ListWrapper.push(res, child);
child = child._next;
}
return res;
}
}
export class DirectiveDependency extends Dependency {
depth:int;
eventEmitterName:string;
constructor(key:Key, asPromise:boolean, lazy:boolean, properties:List, depth:int, eventEmitterName: string) {
super(key, asPromise, lazy, properties);
this.depth = depth;
this.eventEmitterName = eventEmitterName;
}
static createFrom(d:Dependency):Dependency {
return new DirectiveDependency(d.key, d.asPromise, d.lazy,
d.properties, DirectiveDependency._depth(d.properties),
DirectiveDependency._eventEmitterName(d.properties));
}
static _depth(properties):int {
if (properties.length == 0) return 0;
if (ListWrapper.any(properties, p => p instanceof Parent)) return 1;
if (ListWrapper.any(properties, p => p instanceof Ancestor)) return MAX_DEPTH;
return 0;
}
static _eventEmitterName(properties):string {
for (var i = 0; i < properties.length; i++) {
if (properties[i] instanceof EventEmitter) {
return properties[i].eventName;
}
}
return null;
}
}
export class DirectiveBinding extends Binding {
callOnDestroy:boolean;
constructor(key:Key, factory:Function, dependencies:List, providedAsPromise:boolean, callOnDestroy:boolean) {
super(key, factory, dependencies, providedAsPromise);
this.callOnDestroy = callOnDestroy;
}
static createFromBinding(b:Binding, annotation:Directive):Binding {
var deps = ListWrapper.map(b.dependencies, DirectiveDependency.createFrom);
var callOnDestroy = isPresent(annotation) && isPresent(annotation.lifecycle) ?
ListWrapper.contains(annotation.lifecycle, onDestroy) :
false;
return new DirectiveBinding(b.key, b.factory, deps, b.providedAsPromise, callOnDestroy);
}
static createFromType(type:Type, annotation:Directive):Binding {
var binding = bind(type).toClass(type);
return DirectiveBinding.createFromBinding(binding, annotation);
}
static _hasEventEmitter(eventName: string, binding: DirectiveBinding) {
return ListWrapper.any(binding.dependencies, (d) => (d.eventEmitterName == eventName));
}
}
// TODO(rado): benchmark and consider rolling in as ElementInjector fields.
export class PreBuiltObjects {
view:View;
element:NgElement;
viewPort:ViewPort;
lightDom:LightDom;
bindingPropagationConfig:BindingPropagationConfig;
constructor(view, element:NgElement, viewPort:ViewPort, lightDom:LightDom,
bindingPropagationConfig:BindingPropagationConfig) {
this.view = view;
this.element = element;
this.viewPort = viewPort;
this.lightDom = lightDom;
this.bindingPropagationConfig = bindingPropagationConfig;
}
}
/**
Difference between di.Injector and ElementInjector
di.Injector:
- imperative based (can create child injectors imperativly)
- Lazy loading of code
- Component/App Level services which are usually not DOM Related.
ElementInjector:
- ProtoBased (Injector structure fixed at compile time)
- understands @Ancestor, @Parent, @Child, @Descendent
- Fast
- Query mechanism for children
- 1:1 to DOM structure.
PERF BENCHMARK: http://www.williambrownstreet.net/blog/2014/04/faster-angularjs-rendering-angularjs-and-reactjs/
*/
export class ProtoElementInjector {
_binding0:Binding;
_binding1:Binding;
_binding2:Binding;
_binding3:Binding;
_binding4:Binding;
_binding5:Binding;
_binding6:Binding;
_binding7:Binding;
_binding8:Binding;
_binding9:Binding;
_binding0IsComponent:boolean;
_keyId0:int;
_keyId1:int;
_keyId2:int;
_keyId3:int;
_keyId4:int;
_keyId5:int;
_keyId6:int;
_keyId7:int;
_keyId8:int;
_keyId9:int;
parent:ProtoElementInjector;
index:int;
view:View;
distanceToParent:number;
constructor(parent:ProtoElementInjector, index:int, bindings:List, firstBindingIsComponent:boolean = false, distanceToParent:number = 0) {
this.parent = parent;
this.index = index;
this.distanceToParent = distanceToParent;
this._binding0IsComponent = firstBindingIsComponent;
this._binding0 = null; this._keyId0 = null;
this._binding1 = null; this._keyId1 = null;
this._binding2 = null; this._keyId2 = null;
this._binding3 = null; this._keyId3 = null;
this._binding4 = null; this._keyId4 = null;
this._binding5 = null; this._keyId5 = null;
this._binding6 = null; this._keyId6 = null;
this._binding7 = null; this._keyId7 = null;
this._binding8 = null; this._keyId8 = null;
this._binding9 = null; this._keyId9 = null;
var length = bindings.length;
if (length > 0) {this._binding0 = this._createBinding(bindings[0]); this._keyId0 = this._binding0.key.id;}
if (length > 1) {this._binding1 = this._createBinding(bindings[1]); this._keyId1 = this._binding1.key.id;}
if (length > 2) {this._binding2 = this._createBinding(bindings[2]); this._keyId2 = this._binding2.key.id;}
if (length > 3) {this._binding3 = this._createBinding(bindings[3]); this._keyId3 = this._binding3.key.id;}
if (length > 4) {this._binding4 = this._createBinding(bindings[4]); this._keyId4 = this._binding4.key.id;}
if (length > 5) {this._binding5 = this._createBinding(bindings[5]); this._keyId5 = this._binding5.key.id;}
if (length > 6) {this._binding6 = this._createBinding(bindings[6]); this._keyId6 = this._binding6.key.id;}
if (length > 7) {this._binding7 = this._createBinding(bindings[7]); this._keyId7 = this._binding7.key.id;}
if (length > 8) {this._binding8 = this._createBinding(bindings[8]); this._keyId8 = this._binding8.key.id;}
if (length > 9) {this._binding9 = this._createBinding(bindings[9]); this._keyId9 = this._binding9.key.id;}
if (length > 10) {
throw 'Maximum number of directives per element has been reached.';
}
}
instantiate(parent:ElementInjector, host:ElementInjector, eventCallbacks):ElementInjector {
return new ElementInjector(this, parent, host, eventCallbacks);
}
_createBinding(bindingOrType) {
if (bindingOrType instanceof DirectiveBinding) {
return bindingOrType;
} else {
var b = bind(bindingOrType).toClass(bindingOrType);
return DirectiveBinding.createFromBinding(b, null);
}
}
get hasBindings():boolean {
return isPresent(this._binding0);
}
hasEventEmitter(eventName: string) {
var p = this;
if (isPresent(p._binding0) && DirectiveBinding._hasEventEmitter(eventName, p._binding0)) return true;
if (isPresent(p._binding1) && DirectiveBinding._hasEventEmitter(eventName, p._binding1)) return true;
if (isPresent(p._binding2) && DirectiveBinding._hasEventEmitter(eventName, p._binding2)) return true;
if (isPresent(p._binding3) && DirectiveBinding._hasEventEmitter(eventName, p._binding3)) return true;
if (isPresent(p._binding4) && DirectiveBinding._hasEventEmitter(eventName, p._binding4)) return true;
if (isPresent(p._binding5) && DirectiveBinding._hasEventEmitter(eventName, p._binding5)) return true;
if (isPresent(p._binding6) && DirectiveBinding._hasEventEmitter(eventName, p._binding6)) return true;
if (isPresent(p._binding7) && DirectiveBinding._hasEventEmitter(eventName, p._binding7)) return true;
if (isPresent(p._binding8) && DirectiveBinding._hasEventEmitter(eventName, p._binding8)) return true;
if (isPresent(p._binding9) && DirectiveBinding._hasEventEmitter(eventName, p._binding9)) return true;
return false;
}
}
export class ElementInjector extends TreeNode {
_proto:ProtoElementInjector;
_lightDomAppInjector:Injector;
_shadowDomAppInjector:Injector;
_host:ElementInjector;
_obj0:any;
_obj1:any;
_obj2:any;
_obj3:any;
_obj4:any;
_obj5:any;
_obj6:any;
_obj7:any;
_obj8:any;
_obj9:any;
_preBuiltObjects;
_constructionCounter;
_eventCallbacks;
constructor(proto:ProtoElementInjector, parent:ElementInjector, host:ElementInjector, eventCallbacks: Map) {
super(parent);
if (isPresent(parent) && isPresent(host)) {
throw new BaseException('Only either parent or host is allowed');
}
this._host = null; // needed to satisfy Dart
if (isPresent(parent)) {
this._host = parent._host;
} else {
this._host = host;
}
this._proto = proto;
//we cannot call clearDirectives because fields won't be detected
this._preBuiltObjects = null;
this._lightDomAppInjector = null;
this._shadowDomAppInjector = null;
this._eventCallbacks = eventCallbacks;
this._obj0 = null;
this._obj1 = null;
this._obj2 = null;
this._obj3 = null;
this._obj4 = null;
this._obj5 = null;
this._obj6 = null;
this._obj7 = null;
this._obj8 = null;
this._obj9 = null;
this._constructionCounter = 0;
}
clearDirectives() {
this._preBuiltObjects = null;
this._lightDomAppInjector = null;
this._shadowDomAppInjector = null;
var p = this._proto;
if (isPresent(p._binding0) && p._binding0.callOnDestroy) {this._obj0.onDestroy();}
if (isPresent(p._binding1) && p._binding1.callOnDestroy) {this._obj1.onDestroy();}
if (isPresent(p._binding2) && p._binding2.callOnDestroy) {this._obj2.onDestroy();}
if (isPresent(p._binding3) && p._binding3.callOnDestroy) {this._obj3.onDestroy();}
if (isPresent(p._binding4) && p._binding4.callOnDestroy) {this._obj4.onDestroy();}
if (isPresent(p._binding5) && p._binding5.callOnDestroy) {this._obj5.onDestroy();}
if (isPresent(p._binding6) && p._binding6.callOnDestroy) {this._obj6.onDestroy();}
if (isPresent(p._binding7) && p._binding7.callOnDestroy) {this._obj7.onDestroy();}
if (isPresent(p._binding8) && p._binding8.callOnDestroy) {this._obj8.onDestroy();}
if (isPresent(p._binding9) && p._binding9.callOnDestroy) {this._obj9.onDestroy();}
this._obj0 = null;
this._obj1 = null;
this._obj2 = null;
this._obj3 = null;
this._obj4 = null;
this._obj5 = null;
this._obj6 = null;
this._obj7 = null;
this._obj8 = null;
this._obj9 = null;
this._constructionCounter = 0;
}
instantiateDirectives(lightDomAppInjector:Injector, shadowDomAppInjector:Injector, preBuiltObjects:PreBuiltObjects) {
this._checkShadowDomAppInjector(shadowDomAppInjector);
this._preBuiltObjects = preBuiltObjects;
this._lightDomAppInjector = lightDomAppInjector;
this._shadowDomAppInjector = shadowDomAppInjector;
var p = this._proto;
if (isPresent(p._keyId0)) this._getDirectiveByKeyId(p._keyId0);
if (isPresent(p._keyId1)) this._getDirectiveByKeyId(p._keyId1);
if (isPresent(p._keyId2)) this._getDirectiveByKeyId(p._keyId2);
if (isPresent(p._keyId3)) this._getDirectiveByKeyId(p._keyId3);
if (isPresent(p._keyId4)) this._getDirectiveByKeyId(p._keyId4);
if (isPresent(p._keyId5)) this._getDirectiveByKeyId(p._keyId5);
if (isPresent(p._keyId6)) this._getDirectiveByKeyId(p._keyId6);
if (isPresent(p._keyId7)) this._getDirectiveByKeyId(p._keyId7);
if (isPresent(p._keyId8)) this._getDirectiveByKeyId(p._keyId8);
if (isPresent(p._keyId9)) this._getDirectiveByKeyId(p._keyId9);
}
_checkShadowDomAppInjector(shadowDomAppInjector:Injector) {
if (this._proto._binding0IsComponent && isBlank(shadowDomAppInjector)) {
throw new BaseException('A shadowDomAppInjector is required as this ElementInjector contains a component');
} else if (!this._proto._binding0IsComponent && isPresent(shadowDomAppInjector)) {
throw new BaseException('No shadowDomAppInjector allowed as there is not component stored in this ElementInjector');
}
}
get(token) {
return this._getByKey(Key.get(token), 0, null);
}
hasDirective(type:Type):boolean {
return this._getDirectiveByKeyId(Key.get(type).id) !== _undefined;
}
hasPreBuiltObject(type:Type):boolean {
var pb = this._getPreBuiltObjectByKeyId(Key.get(type).id);
return pb !== _undefined && isPresent(pb);
}
forElement(el):boolean {
return this._preBuiltObjects.element.domElement === el;
}
getComponent() {
if (this._proto._binding0IsComponent) {
return this._obj0;
} else {
throw new BaseException('There is not component stored in this ElementInjector');
}
}
directParent(): ElementInjector {
return this._proto.distanceToParent < 2 ? this.parent : null;
}
_isComponentKey(key:Key) {
return this._proto._binding0IsComponent && key.id === this._proto._keyId0;
}
_new(binding:Binding) {
if (this._constructionCounter++ > _MAX_DIRECTIVE_CONSTRUCTION_COUNTER) {
throw new CyclicDependencyError(binding.key);
}
var factory = binding.factory;
var deps = binding.dependencies;
var length = deps.length;
var d0,d1,d2,d3,d4,d5,d6,d7,d8,d9;
try {
d0 = length > 0 ? this._getByDependency(deps[0], binding.key) : null;
d1 = length > 1 ? this._getByDependency(deps[1], binding.key) : null;
d2 = length > 2 ? this._getByDependency(deps[2], binding.key) : null;
d3 = length > 3 ? this._getByDependency(deps[3], binding.key) : null;
d4 = length > 4 ? this._getByDependency(deps[4], binding.key) : null;
d5 = length > 5 ? this._getByDependency(deps[5], binding.key) : null;
d6 = length > 6 ? this._getByDependency(deps[6], binding.key) : null;
d7 = length > 7 ? this._getByDependency(deps[7], binding.key) : null;
d8 = length > 8 ? this._getByDependency(deps[8], binding.key) : null;
d9 = length > 9 ? this._getByDependency(deps[9], binding.key) : null;
} catch(e) {
if (e instanceof ProviderError) e.addKey(binding.key);
throw e;
}
var obj;
switch(length) {
case 0: obj = factory(); break;
case 1: obj = factory(d0); break;
case 2: obj = factory(d0, d1); break;
case 3: obj = factory(d0, d1, d2); break;
case 4: obj = factory(d0, d1, d2, d3); break;
case 5: obj = factory(d0, d1, d2, d3, d4); break;
case 6: obj = factory(d0, d1, d2, d3, d4, d5); break;
case 7: obj = factory(d0, d1, d2, d3, d4, d5, d6); break;
case 8: obj = factory(d0, d1, d2, d3, d4, d5, d6, d7); break;
case 9: obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8); break;
case 10: obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9); break;
default: throw `Directive ${binding.key.token} can only have up to 10 dependencies.`;
}
return obj;
}
_getByDependency(dep:DirectiveDependency, requestor:Key) {
if (isPresent(dep.eventEmitterName)) return this._buildEventEmitter(dep);
return this._getByKey(dep.key, dep.depth, requestor);
}
_buildEventEmitter(dep) {
var view = this._getPreBuiltObjectByKeyId(StaticKeys.instance().viewId);
if (isPresent(this._eventCallbacks)) {
var callback = MapWrapper.get(this._eventCallbacks, dep.eventEmitterName);
if (isPresent(callback)) {
var locals = MapWrapper.create();
return ProtoView.buildInnerCallback(callback, view, locals);
}
}
return (_) => {};
}
/*
* It is fairly easy to annotate keys with metadata.
* For example, key.metadata = 'directive'.
*
* This would allows to do the lookup more efficiently.
*
* for example
* we would lookup pre built objects only when metadata = 'preBuilt'
* we would lookup directives only when metadata = 'directive'
*
* Write benchmarks before doing this optimization.
*/
_getByKey(key:Key, depth:number, requestor:Key) {
var ei = this;
if (! this._shouldIncludeSelf(depth)) {
depth -= ei._proto.distanceToParent;
ei = ei._parent;
}
while (ei != null && depth >= 0) {
var preBuiltObj = ei._getPreBuiltObjectByKeyId(key.id);
if (preBuiltObj !== _undefined) return preBuiltObj;
var dir = ei._getDirectiveByKeyId(key.id);
if (dir !== _undefined) return dir;
depth -= ei._proto.distanceToParent;
ei = ei._parent;
}
if (isPresent(this._host) && this._host._isComponentKey(key)) {
return this._host.getComponent();
} else {
return this._appInjector(requestor).get(key);
}
}
_appInjector(requestor:Key) {
if (isPresent(requestor) && this._isComponentKey(requestor)) {
return this._shadowDomAppInjector;
} else {
return this._lightDomAppInjector;
}
}
_shouldIncludeSelf(depth:int) {
return depth === 0;
}
_getPreBuiltObjectByKeyId(keyId:int) {
var staticKeys = StaticKeys.instance();
if (keyId === staticKeys.viewId) return this._preBuiltObjects.view;
if (keyId === staticKeys.ngElementId) return this._preBuiltObjects.element;
if (keyId === staticKeys.viewPortId) return this._preBuiltObjects.viewPort;
if (keyId === staticKeys.bindingPropagationConfigId) return this._preBuiltObjects.bindingPropagationConfig;
if (keyId === staticKeys.destinationLightDomId) {
var p:ElementInjector = this.directParent();
return isPresent(p) ? p._preBuiltObjects.lightDom : null;
}
if (keyId === staticKeys.sourceLightDomId) {
return this._host._preBuiltObjects.lightDom;
}
//TODO add other objects as needed
return _undefined;
}
_getDirectiveByKeyId(keyId:int) {
var p = this._proto;
if (p._keyId0 === keyId) {if (isBlank(this._obj0)){this._obj0 = this._new(p._binding0);} return this._obj0;}
if (p._keyId1 === keyId) {if (isBlank(this._obj1)){this._obj1 = this._new(p._binding1);} return this._obj1;}
if (p._keyId2 === keyId) {if (isBlank(this._obj2)){this._obj2 = this._new(p._binding2);} return this._obj2;}
if (p._keyId3 === keyId) {if (isBlank(this._obj3)){this._obj3 = this._new(p._binding3);} return this._obj3;}
if (p._keyId4 === keyId) {if (isBlank(this._obj4)){this._obj4 = this._new(p._binding4);} return this._obj4;}
if (p._keyId5 === keyId) {if (isBlank(this._obj5)){this._obj5 = this._new(p._binding5);} return this._obj5;}
if (p._keyId6 === keyId) {if (isBlank(this._obj6)){this._obj6 = this._new(p._binding6);} return this._obj6;}
if (p._keyId7 === keyId) {if (isBlank(this._obj7)){this._obj7 = this._new(p._binding7);} return this._obj7;}
if (p._keyId8 === keyId) {if (isBlank(this._obj8)){this._obj8 = this._new(p._binding8);} return this._obj8;}
if (p._keyId9 === keyId) {if (isBlank(this._obj9)){this._obj9 = this._new(p._binding9);} return this._obj9;}
return _undefined;
}
getAtIndex(index:int) {
if (index == 0) return this._obj0;
if (index == 1) return this._obj1;
if (index == 2) return this._obj2;
if (index == 3) return this._obj3;
if (index == 4) return this._obj4;
if (index == 5) return this._obj5;
if (index == 6) return this._obj6;
if (index == 7) return this._obj7;
if (index == 8) return this._obj8;
if (index == 9) return this._obj9;
throw new OutOfBoundsAccess(index);
}
hasInstances() {
return this._constructionCounter > 0;
}
hasEventEmitter(eventName: string) {
return this._proto.hasEventEmitter(eventName);
}
}
class OutOfBoundsAccess extends Error {
message:string;
constructor(index) {
this.message = `Index ${index} is out-of-bounds.`;
}
toString() {
return this.message;
}
}

View File

@ -0,0 +1,5 @@
export class OnChange {
onChange(changes) {
throw "OnChange.onChange is not implemented";
}
}

View File

@ -0,0 +1,59 @@
import {isBlank} from 'facade/src/lang';
import {List, ListWrapper} from 'facade/src/collection';
import {DOM, Element} from 'facade/src/dom';
import {CompileElement} from './compile_element';
import {CompileStep} from './compile_step';
/**
* Controls the processing order of elements.
* Right now it only allows to add a parent element.
*/
export class CompileControl {
_steps:List<CompileStep>;
_currentStepIndex:number;
_parent:CompileElement;
_results;
_additionalChildren;
constructor(steps) {
this._steps = steps;
this._currentStepIndex = 0;
this._parent = null;
this._results = null;
this._additionalChildren = 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._currentStepIndex = i;
step.process(parent, current, this);
parent = this._parent;
}
ListWrapper.push(results, current);
this._currentStepIndex = previousStepIndex;
this._parent = previousParent;
var localAdditionalChildren = this._additionalChildren;
this._additionalChildren = null;
return localAdditionalChildren;
}
addParent(newElement:CompileElement) {
this.internalProcess(this._results, this._currentStepIndex+1, this._parent, newElement);
this._parent = newElement;
}
addChild(element:CompileElement) {
if (isBlank(this._additionalChildren)) {
this._additionalChildren = ListWrapper.create();
}
ListWrapper.push(this._additionalChildren, element);
}
}

View File

@ -0,0 +1,159 @@
import {List, Map, ListWrapper, MapWrapper} from 'facade/src/collection';
import {Element, DOM} from 'facade/src/dom';
import {int, isBlank, isPresent, Type} from 'facade/src/lang';
import {DirectiveMetadata} from '../directive_metadata';
import {Decorator} from '../../annotations/annotations';
import {Component} from '../../annotations/annotations';
import {Template} from '../../annotations/annotations';
import {ElementBinder} from '../element_binder';
import {ProtoElementInjector} from '../element_injector';
import {ProtoView} from '../view';
import {AST} from 'change_detection/change_detection';
/**
* 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 {
element:Element;
_attrs:Map;
_classList:List;
textNodeBindings:Map;
propertyBindings:Map;
eventBindings:Map;
/// Store directive name to template name mapping.
/// Directive name is what the directive exports the variable as
/// Template name is how it is reffered to it in template
variableBindings:Map;
decoratorDirectives:List<DirectiveMetadata>;
templateDirective:DirectiveMetadata;
componentDirective:DirectiveMetadata;
_allDirectives:List<DirectiveMetadata>;
isViewRoot:boolean;
hasBindings:boolean;
inheritedProtoView:ProtoView;
inheritedProtoElementInjector:ProtoElementInjector;
inheritedElementBinder:ElementBinder;
distanceToParentInjector:number;
compileChildren: boolean;
constructor(element:Element) {
this.element = element;
this._attrs = null;
this._classList = null;
this.textNodeBindings = null;
this.propertyBindings = null;
this.eventBindings = null;
this.variableBindings = null;
this.decoratorDirectives = null;
this.templateDirective = null;
this.componentDirective = null;
this._allDirectives = 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;
this.distanceToParentInjector = 0;
this.compileChildren = true;
}
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:AST) {
if (isBlank(this.textNodeBindings)) {
this.textNodeBindings = MapWrapper.create();
}
MapWrapper.set(this.textNodeBindings, indexInParent, expression);
}
addPropertyBinding(property:string, expression:AST) {
if (isBlank(this.propertyBindings)) {
this.propertyBindings = MapWrapper.create();
}
MapWrapper.set(this.propertyBindings, property, expression);
}
addVariableBinding(directiveName:string, templateName:string) {
if (isBlank(this.variableBindings)) {
this.variableBindings = MapWrapper.create();
}
MapWrapper.set(this.variableBindings, templateName, directiveName);
}
addEventBinding(eventName:string, expression:AST) {
if (isBlank(this.eventBindings)) {
this.eventBindings = MapWrapper.create();
}
MapWrapper.set(this.eventBindings, eventName, expression);
}
addDirective(directive:DirectiveMetadata) {
var annotation = directive.annotation;
this._allDirectives = null;
if (annotation instanceof Decorator) {
if (isBlank(this.decoratorDirectives)) {
this.decoratorDirectives = ListWrapper.create();
}
ListWrapper.push(this.decoratorDirectives, directive);
if (!annotation.compileChildren) {
this.compileChildren = false;
}
} else if (annotation instanceof Template) {
this.templateDirective = directive;
} else if (annotation instanceof Component) {
this.componentDirective = directive;
}
}
getAllDirectives(): List<DirectiveMetadata> {
if (this._allDirectives === null) {
// Collect all the directives
// When present the component directive must be first
var directives = ListWrapper.create();
if (isPresent(this.componentDirective)) {
ListWrapper.push(directives, this.componentDirective);
}
if (isPresent(this.templateDirective)) {
ListWrapper.push(directives, this.templateDirective);
}
if (isPresent(this.decoratorDirectives)) {
directives = ListWrapper.concat(directives, this.decoratorDirectives);
}
this._allDirectives = directives;
}
return this._allDirectives;
}
}

View File

@ -0,0 +1,47 @@
import {isPresent} from 'facade/src/lang';
import {List, ListWrapper} from 'facade/src/collection';
import {Element, Node, DOM} from 'facade/src/dom';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
import {CompileStep} from './compile_step';
import {DirectiveMetadata} from '../directive_metadata';
/**
* CompilePipeline for executing CompileSteps recursively for
* all elements in a template.
*/
export class CompilePipeline {
_control:CompileControl;
constructor(steps:List<CompileStep>) {
this._control = new CompileControl(steps);
}
process(rootElement:Element):List {
var results = ListWrapper.create();
this._process(results, null, new CompileElement(rootElement));
return results;
}
_process(results, parent:CompileElement, current:CompileElement) {
var additionalChildren = this._control.internalProcess(results, 0, parent, current);
if (current.compileChildren) {
var node = DOM.templateAwareRoot(current.element).firstChild;
while (isPresent(node)) {
// compiliation can potentially move the node, so we need to store the
// next sibling before recursing.
var nextNode = DOM.nextSibling(node);
if (node.nodeType === Node.ELEMENT_NODE) {
this._process(results, current, new CompileElement(node));
}
node = nextNode;
}
}
if (isPresent(additionalChildren)) {
for (var i=0; i<additionalChildren.length; i++) {
this._process(results, current, additionalChildren[i]);
}
}
}
}

View File

@ -0,0 +1,11 @@
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
import {DirectiveMetadata} from '../directive_metadata';
/**
* 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,38 @@
import {ChangeDetection, Parser} from 'change_detection/change_detection';
import {List} from 'facade/src/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';
import {DirectiveMetadata} from 'core/src/compiler/directive_metadata';
import {stringify} from 'facade/src/lang';
/**
* Default steps used for compiling a template.
* Takes in an HTMLElement and produces the ProtoViews,
* ProtoElementInjectors and ElementBinders in the end.
*/
export function createDefaultSteps(
changeDetection:ChangeDetection,
parser:Parser,
compiledComponent: DirectiveMetadata,
directives: List<DirectiveMetadata>) {
var compilationUnit = stringify(compiledComponent.type);
return [
new ViewSplitter(parser, compilationUnit),
new PropertyBindingParser(parser, compilationUnit),
new DirectiveParser(directives),
new TextInterpolationParser(parser, compilationUnit),
new ElementBindingMarker(),
new ProtoViewBuilder(changeDetection),
new ProtoElementInjectorBuilder(),
new ElementBinderBuilder()
];
}

View File

@ -0,0 +1,82 @@
import {isPresent, BaseException} from 'facade/src/lang';
import {List, MapWrapper} from 'facade/src/collection';
import {TemplateElement} from 'facade/src/dom';
import {SelectorMatcher} from '../selector';
import {CssSelector} from '../selector';
import {DirectiveMetadata} from '../directive_metadata';
import {Template} from '../../annotations/annotations';
import {Component} from '../../annotations/annotations';
import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
/**
* Parses the directives on a single element. Assumes ViewSplitter has already created
* <template> elements for template directives.
*
* Fills:
* - CompileElement#decoratorDirectives
* - CompileElement#templateDirecitve
* - CompileElement#componentDirective.
*
* Reads:
* - CompileElement#propertyBindings (to find directives contained
* in the property bindings)
* - CompileElement#variableBindings (to find directives contained
* in the variable bindings)
*/
export class DirectiveParser extends CompileStep {
_selectorMatcher:SelectorMatcher;
constructor(directives:List<DirectiveMetadata>) {
this._selectorMatcher = new SelectorMatcher();
for (var i=0; i<directives.length; i++) {
var directiveMetadata = directives[i];
this._selectorMatcher.addSelectable(
CssSelector.parse(directiveMetadata.annotation.selector),
directiveMetadata
);
}
}
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);
});
if (isPresent(current.propertyBindings)) {
MapWrapper.forEach(current.propertyBindings, (expression, prop) => {
cssSelector.addAttribute(prop, expression.source);
});
}
if (isPresent(current.variableBindings)) {
MapWrapper.forEach(current.variableBindings, (value, name) => {
cssSelector.addAttribute(name, value);
});
}
// Note: We assume that the ViewSplitter already did its work, i.e. template directive should
// only be present on <template> elements any more!
var isTemplateElement = current.element instanceof TemplateElement;
this._selectorMatcher.match(cssSelector, (directive) => {
if (directive.annotation instanceof Template) {
if (!isTemplateElement) {
throw new BaseException('Template directives need to be placed on <template> elements or elements with template attribute!');
} else if (isPresent(current.templateDirective)) {
throw new BaseException('Only one template directive per element is allowed!');
}
} else if (isTemplateElement) {
throw new BaseException('Only template directives are allowed on <template> elements!');
} else if ((directive.annotation instanceof Component) && isPresent(current.componentDirective)) {
throw new BaseException('Only one component directive per element is allowed!');
}
current.addDirective(directive);
});
}
}

View File

@ -0,0 +1,145 @@
import {int, isPresent, isBlank, Type, BaseException, StringWrapper, stringify} from 'facade/src/lang';
import {Element, DOM} from 'facade/src/dom';
import {ListWrapper, List, MapWrapper, StringMapWrapper} from 'facade/src/collection';
import {reflector} from 'reflection/src/reflection';
import {Parser, ProtoChangeDetector} from 'change_detection/change_detection';
import {Component, Directive} from '../../annotations/annotations';
import {DirectiveMetadata} from '../directive_metadata';
import {ProtoView, ElementPropertyMemento, DirectivePropertyMemento} from '../view';
import {ProtoElementInjector} from '../element_injector';
import {ElementBinder} from '../element_binder';
import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
const CLASS_PREFIX = 'class.';
var classSettersCache = StringMapWrapper.create();
function classSetterFactory(className:string) {
var setterFn = StringMapWrapper.get(classSettersCache, className);
if (isBlank(setterFn)) {
setterFn = function(element:Element, value) {
if (value) {
DOM.addClass(element, className);
} else {
DOM.removeClass(element, className);
}
};
StringMapWrapper.set(classSettersCache, className, setterFn);
}
return setterFn;
}
/**
* Creates the ElementBinders and adds watches to the
* ProtoChangeDetector.
*
* Fills:
* - CompileElement#inheritedElementBinder
*
* Reads:
* - (in parent) CompileElement#inheritedElementBinder
* - CompileElement#hasBindings
* - CompileElement#inheritedProtoView
* - CompileElement#inheritedProtoElementInjector
* - CompileElement#textNodeBindings
* - CompileElement#propertyBindings
* - CompileElement#eventBindings
* - 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 {
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
var elementBinder = null;
if (current.hasBindings) {
var protoView = current.inheritedProtoView;
elementBinder = protoView.bindElement(current.inheritedProtoElementInjector,
current.componentDirective, current.templateDirective);
if (isPresent(current.textNodeBindings)) {
this._bindTextNodes(protoView, current);
}
if (isPresent(current.propertyBindings)) {
this._bindElementProperties(protoView, current);
}
if (isPresent(current.eventBindings)) {
this._bindEvents(protoView, current);
}
this._bindDirectiveProperties(current.getAllDirectives(), current);
} else if (isPresent(parent)) {
elementBinder = parent.inheritedElementBinder;
}
current.inheritedElementBinder = elementBinder;
}
_bindTextNodes(protoView, compileElement) {
MapWrapper.forEach(compileElement.textNodeBindings, (expression, indexInParent) => {
protoView.bindTextNode(indexInParent, expression);
});
}
_bindElementProperties(protoView, compileElement) {
MapWrapper.forEach(compileElement.propertyBindings, (expression, property) => {
var setterFn;
if (StringWrapper.startsWith(property, CLASS_PREFIX)) {
setterFn = classSetterFactory(StringWrapper.substring(property, CLASS_PREFIX.length));
} else if (DOM.hasProperty(compileElement.element, property)) {
setterFn = reflector.setter(property);
}
if (isPresent(setterFn)) {
protoView.bindElementProperty(expression.ast, property, setterFn);
}
});
}
_bindEvents(protoView, compileElement) {
MapWrapper.forEach(compileElement.eventBindings, (expression, eventName) => {
protoView.bindEvent(eventName, expression);
});
}
_bindDirectiveProperties(directives: List<DirectiveMetadata>,
compileElement: CompileElement) {
var protoView = compileElement.inheritedProtoView;
for (var directiveIndex = 0; directiveIndex < directives.length; directiveIndex++) {
var directive = ListWrapper.get(directives, directiveIndex);
var annotation = directive.annotation;
if (isBlank(annotation.bind)) continue;
StringMapWrapper.forEach(annotation.bind, function (dirProp, elProp) {
var expression = isPresent(compileElement.propertyBindings) ?
MapWrapper.get(compileElement.propertyBindings, elProp) :
null;
if (isBlank(expression)) {
throw new BaseException("No element binding found for property '" + elProp
+ "' which is required by directive '" + stringify(directive.type) + "'");
}
var len = dirProp.length;
var dirBindingName = dirProp;
var isContentWatch = dirProp[len - 2] === '[' && dirProp[len - 1] === ']';
if (isContentWatch) dirBindingName = dirProp.substring(0, len - 2);
protoView.bindDirectiveProperty(
directiveIndex,
expression,
dirBindingName,
reflector.setter(dirBindingName),
isContentWatch
);
});
}
}
}

View File

@ -0,0 +1,44 @@
import {isPresent} from 'facade/src/lang';
import {MapWrapper} from 'facade/src/collection';
import {DOM} from 'facade/src/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#variableBindings
* - CompileElement#eventBindings
* - 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.variableBindings) && MapWrapper.size(current.variableBindings)>0) ||
(isPresent(current.eventBindings) && MapWrapper.size(current.eventBindings)>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,76 @@
import {isPresent, isBlank, RegExpWrapper, BaseException} from 'facade/src/lang';
import {MapWrapper} from 'facade/src/collection';
import {TemplateElement} from 'facade/src/dom';
import {Parser, AST, ExpressionWithSource} from 'change_detection/change_detection';
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_NAME_REGEXP = RegExpWrapper.create('^(?:(?:(bind)|(var)|(on))-(.+))|\\[([^\\]]+)\\]|\\(([^\\)]+)\\)');
/**
* Parses the property bindings on a single element.
*
* Fills:
* - CompileElement#propertyBindings
* - CompileElement#eventBindings
* - CompileElement#variableBindings
*/
export class PropertyBindingParser extends CompileStep {
_parser:Parser;
_compilationUnit:any;
constructor(parser:Parser, compilationUnit:any) {
this._parser = parser;
this._compilationUnit = compilationUnit;
}
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
var attrs = current.attrs();
MapWrapper.forEach(attrs, (attrValue, attrName) => {
var bindParts = RegExpWrapper.firstMatch(BIND_NAME_REGEXP, attrName);
if (isPresent(bindParts)) {
if (isPresent(bindParts[1])) {
// match: bind-prop
current.addPropertyBinding(bindParts[4], this._parseBinding(attrValue));
} else if (isPresent(bindParts[2])) {
// match: let-prop
// Note: We assume that the ViewSplitter already did its work, i.e. template directive should
// only be present on <template> elements any more!
if (!(current.element instanceof TemplateElement)) {
throw new BaseException('var-* is only allowed on <template> elements!');
}
current.addVariableBinding(bindParts[4], attrValue);
} else if (isPresent(bindParts[3])) {
// match: on-prop
current.addEventBinding(bindParts[4], this._parseAction(attrValue));
} else if (isPresent(bindParts[5])) {
// match: [prop]
current.addPropertyBinding(bindParts[5], this._parseBinding(attrValue));
} else if (isPresent(bindParts[6])) {
// match: (prop)
current.addEventBinding(bindParts[6], this._parseBinding(attrValue));
}
} else {
var ast = this._parseInterpolation(attrValue);
if (isPresent(ast)) {
current.addPropertyBinding(attrName, ast);
}
}
});
}
_parseInterpolation(input:string):AST {
return this._parser.parseInterpolation(input, this._compilationUnit);
}
_parseBinding(input:string):AST {
return this._parser.parseBinding(input, this._compilationUnit);
}
_parseAction(input:string):AST {
return this._parser.parseAction(input, this._compilationUnit);
}
}

View File

@ -0,0 +1,72 @@
import {isPresent, isBlank} from 'facade/src/lang';
import {ListWrapper} from 'facade/src/collection';
import {Key} from 'di/di';
import {ProtoElementInjector, ComponentKeyMetaData, DirectiveBinding} from '../element_injector';
import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
import {DirectiveMetadata} from '../directive_metadata';
/**
* Creates the ProtoElementInjectors.
*
* Fills:
* - CompileElement#inheritedProtoElementInjector
* - CompileElement#distanceToParentInjector
*
* Reads:
* - (in parent) CompileElement#inheritedProtoElementInjector
* - (in parent) CompileElement#distanceToParentInjector
* - 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, firstBindingIsComponent, distance) {
return new ProtoElementInjector(parent, index, directives, firstBindingIsComponent, distance);
}
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
var distanceToParentInjector = this._getDistanceToParentInjector(parent, current);
var parentProtoElementInjector = this._getParentProtoElementInjector(parent, current);
var injectorBindings = ListWrapper.map(current.getAllDirectives(), this._createBinding);
// 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;
var hasComponent = isPresent(current.componentDirective);
current.inheritedProtoElementInjector = this.internalCreateProtoElementInjector(
parentProtoElementInjector, protoView.elementBinders.length, injectorBindings,
hasComponent, distanceToParentInjector
);
current.distanceToParentInjector = 0;
} else {
current.inheritedProtoElementInjector = parentProtoElementInjector;
current.distanceToParentInjector = distanceToParentInjector;
}
}
_getDistanceToParentInjector(parent, current) {
return isPresent(parent) ? parent.distanceToParentInjector + 1 : 0;
}
_getParentProtoElementInjector(parent, current) {
if (isPresent(parent) && !current.isViewRoot) {
return parent.inheritedProtoElementInjector;
}
return null;
}
_createBinding(d:DirectiveMetadata): DirectiveBinding {
return DirectiveBinding.createFromType(d.type, d.annotation);
}
}

View File

@ -0,0 +1,50 @@
import {isPresent, BaseException} from 'facade/src/lang';
import {ListWrapper, MapWrapper} from 'facade/src/collection';
import {ProtoView} from '../view';
import {ChangeDetection} from 'change_detection/change_detection';
import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
/**
* Creates ProtoViews and forwards variable bindings from parent to children.
*
* Fills:
* - (in parent): CompileElement#inheritedElementBinder.nestedProtoView
* - CompileElement#inheritedProtoView
*
* Reads:
* - (in parent): CompileElement#inheritedProtoView
* - (in parent): CompileElement#variableBindings
* - CompileElement#isViewRoot
*/
export class ProtoViewBuilder extends CompileStep {
changeDetection:ChangeDetection;
constructor(changeDetection:ChangeDetection) {
this.changeDetection = changeDetection;
}
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
var inheritedProtoView = null;
if (current.isViewRoot) {
var protoChangeDetector = this.changeDetection.createProtoChangeDetector('dummy');
inheritedProtoView = new ProtoView(current.element, protoChangeDetector);
if (isPresent(parent)) {
if (isPresent(parent.inheritedElementBinder.nestedProtoView)) {
throw new BaseException('Only one nested view per element is allowed');
}
parent.inheritedElementBinder.nestedProtoView = inheritedProtoView;
if (isPresent(parent.variableBindings)) {
MapWrapper.forEach(parent.variableBindings, (mappedName, varName) => {
inheritedProtoView.bindVariable(varName, mappedName);
});
}
}
} else if (isPresent(parent)) {
inheritedProtoView = parent.inheritedProtoView;
}
current.inheritedProtoView = inheritedProtoView;
}
}

View File

@ -0,0 +1,45 @@
import {RegExpWrapper, StringWrapper, isPresent} from 'facade/src/lang';
import {Node, DOM} from 'facade/src/dom';
import {Parser} from 'change_detection/change_detection';
import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
/**
* Parses interpolations in direct text child nodes of the current element.
*
* Fills:
* - CompileElement#textNodeBindings
*/
export class TextInterpolationParser extends CompileStep {
_parser:Parser;
_compilationUnit:any;
constructor(parser:Parser, compilationUnit:any) {
this._parser = parser;
this._compilationUnit = compilationUnit;
}
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
if (!current.compileChildren) {
return;
}
var element = current.element;
var childNodes = DOM.templateAwareRoot(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) {
var ast = this._parser.parseInterpolation(node.nodeValue, this._compilationUnit);
if (isPresent(ast)) {
DOM.setText(node, ' ');
pipelineElement.addTextNodeBinding(nodeIndex, ast);
}
}
}

View File

@ -0,0 +1,113 @@
import {isBlank, isPresent, BaseException} from 'facade/src/lang';
import {DOM, TemplateElement} from 'facade/src/dom';
import {MapWrapper, ListWrapper} from 'facade/src/collection';
import {Parser} from 'change_detection/change_detection';
import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
import {StringWrapper} from 'facade/src/lang';
import {$BANG} from 'change_detection/src/parser/lexer';
/**
* Splits views at `<template>` elements or elements with `template` attribute:
* For `<template>` elements:
* - moves the content into a new and disconnected `<template>` element
* that is marked as view root.
*
* For elements with a `template` attribute:
* - replaces the element with an empty `<template>` element,
* parses the content of the `template` attribute and adds the information to that
* `<template>` element. Marks the elements as view root.
*
* Note: In both cases the root of the nested view is disconnected from its parent element.
* This is needed for browsers that don't support the `<template>` element
* as we want to do locate elements with bindings using `getElementsByClassName` later on,
* which should not descend into the nested view.
*
* Fills:
* - CompileElement#isViewRoot
* - CompileElement#variableBindings
* - CompileElement#propertyBindings
*/
export class ViewSplitter extends CompileStep {
_parser:Parser;
_compilationUnit:any;
constructor(parser:Parser, compilationUnit:any) {
this._parser = parser;
this._compilationUnit = compilationUnit;
}
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
if (isBlank(parent)) {
current.isViewRoot = true;
} else {
if (current.element instanceof TemplateElement) {
if (!current.isViewRoot) {
var viewRoot = new CompileElement(DOM.createTemplate(''));
var currentElement:TemplateElement = current.element;
var viewRootElement:TemplateElement = viewRoot.element;
this._moveChildNodes(currentElement.content, viewRootElement.content);
viewRoot.isViewRoot = true;
control.addChild(viewRoot);
}
} else {
var attrs = current.attrs();
var templateBindings = MapWrapper.get(attrs, 'template');
var hasTemplateBinding = isPresent(templateBindings);
// look for template shortcuts such as !if="condition" and treat them as template="if condition"
MapWrapper.forEach(attrs, (attrValue, attrName) => {
if (StringWrapper.charCodeAt(attrName, 0) == $BANG) {
var key = StringWrapper.substring(attrName, 1); // remove the bang
if (hasTemplateBinding) {
// 2nd template binding detected
throw new BaseException(`Only one template directive per element is allowed: ` +
`${templateBindings} and ${key} cannot be used simultaneously!`);
} else {
templateBindings = (attrValue.length == 0) ? key : key + ' ' + attrValue;
hasTemplateBinding = true;
}
}
});
if (hasTemplateBinding) {
var newParent = new CompileElement(DOM.createTemplate(''));
current.isViewRoot = true;
this._parseTemplateBindings(templateBindings, newParent);
this._addParentElement(current.element, newParent.element);
control.addParent(newParent);
current.element.remove();
}
}
}
}
_moveChildNodes(source, target) {
while (isPresent(source.firstChild)) {
DOM.appendChild(target, source.firstChild);
}
}
_addParentElement(currentElement, newParentElement) {
DOM.insertBefore(currentElement, newParentElement);
DOM.appendChild(newParentElement, currentElement);
}
_parseTemplateBindings(templateBindings:string, compileElement:CompileElement) {
var bindings = this._parser.parseTemplateBindings(templateBindings, this._compilationUnit);
for (var i=0; i<bindings.length; i++) {
var binding = bindings[i];
if (binding.keyIsVar) {
compileElement.addVariableBinding(binding.key, binding.name);
} else if (isPresent(binding.expression)) {
compileElement.addPropertyBinding(binding.key, binding.expression);
} else {
compileElement.element.setAttribute(binding.key, '');
}
}
}
}

View File

@ -0,0 +1,249 @@
import {List, Map, ListWrapper, MapWrapper} from 'facade/src/collection';
import {isPresent, isBlank, RegExpWrapper, RegExpMatcherWrapper, StringWrapper} from 'facade/src/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 {
element:string;
classNames:List;
attrs:List;
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 {
_elementMap:Map;
_elementPartialMap:Map;
_classMap:Map;
_classPartialMap:Map;
_attrValueMap:Map;
_attrValuePartialMap:Map;
constructor() {
this._elementMap = MapWrapper.create();
this._elementPartialMap = MapWrapper.create();
this._classMap = MapWrapper.create();
this._classPartialMap = MapWrapper.create();
this._attrValueMap = MapWrapper.create();
this._attrValuePartialMap = MapWrapper.create();
}
/**
* Add an object that can be found later on by calling `match`.
* @param cssSelector A css selector
* @param selectable An opaque object that will be given to the callback of the `match` function
*/
addSelectable(cssSelector:CssSelector, selectable) {
var matcher = this;
var element = cssSelector.element;
var classNames = cssSelector.classNames;
var attrs = cssSelector.attrs;
if (isPresent(element)) {
var isTerminal = attrs.length === 0 && classNames.length === 0;
if (isTerminal) {
this._addTerminal(matcher._elementMap, element, selectable);
} else {
matcher = this._addPartial(matcher._elementPartialMap, element);
}
}
if (isPresent(classNames)) {
for (var index = 0; index<classNames.length; index++) {
var isTerminal = attrs.length === 0 && index === classNames.length - 1;
var className = classNames[index];
if (isTerminal) {
this._addTerminal(matcher._classMap, className, selectable);
} else {
matcher = this._addPartial(matcher._classPartialMap, className);
}
}
}
if (isPresent(attrs)) {
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 = MapWrapper.get(map, attrName)
if (isBlank(valuesMap)) {
valuesMap = MapWrapper.create();
MapWrapper.set(map, attrName, valuesMap);
}
if (isTerminal) {
this._addTerminal(valuesMap, attrValue, selectable);
} else {
matcher = this._addPartial(valuesMap, attrValue);
}
}
}
}
_addTerminal(map:Map<string,string>, name:string, selectable) {
var terminalList = MapWrapper.get(map, name)
if (isBlank(terminalList)) {
terminalList = ListWrapper.create();
MapWrapper.set(map, name, terminalList);
}
ListWrapper.push(terminalList, selectable);
}
_addPartial(map:Map<string,string>, name:string) {
var matcher = MapWrapper.get(map, name)
if (isBlank(matcher)) {
matcher = new SelectorMatcher();
MapWrapper.set(map, name, matcher);
}
return matcher;
}
/**
* Find the objects that have been added via `addSelectable`
* whose css selector is contained in the given css selector.
* @param cssSelector A css selector
* @param matchedCallback This callback will be called with the object handed into `addSelectable`
*/
match(cssSelector:CssSelector, matchedCallback:Function) {
var element = cssSelector.element;
var classNames = cssSelector.classNames;
var attrs = cssSelector.attrs;
this._matchTerminal(this._elementMap, element, matchedCallback);
this._matchPartial(this._elementPartialMap, element, cssSelector, matchedCallback);
if (isPresent(classNames)) {
for (var index = 0; index<classNames.length; index++) {
var className = classNames[index];
this._matchTerminal(this._classMap, className, matchedCallback);
this._matchPartial(this._classPartialMap, className, cssSelector, matchedCallback);
}
}
if (isPresent(attrs)) {
for (var index = 0; index<attrs.length;) {
var attrName = attrs[index++];
var attrValue = attrs[index++];
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 = MapWrapper.get(this._attrValuePartialMap, attrName)
this._matchPartial(valuesMap, attrValue, cssSelector, matchedCallback);
}
}
}
_matchTerminal(map:Map<string,string> = null, name, matchedCallback) {
if (isBlank(map) || isBlank(name)) {
return;
}
var selectables = MapWrapper.get(map, name)
if (isBlank(selectables)) {
return;
}
for (var index=0; index<selectables.length; index++) {
matchedCallback(selectables[index]);
}
}
_matchPartial(map:Map<string,string> = null, name, cssSelector, matchedCallback) {
if (isBlank(map) || isBlank(name)) {
return;
}
var nestedSelector = MapWrapper.get(map, name)
if (isBlank(nestedSelector)) {
return;
}
// TODO(perf): get rid of recursion and measure again
// TODO(perf): don't pass the whole selector into the recursion,
// but only the not processed parts
nestedSelector.match(cssSelector, matchedCallback);
}
}

View File

@ -0,0 +1,9 @@
library angular.core.compiler.shadow_dom;
//TODO: merge this file with shadow_dom.es6 when the traspiler support creating const globals
import './shadow_dom_strategy.dart';
export './shadow_dom_strategy.dart';
const ShadowDomEmulated = const EmulatedShadowDomStrategy();
const ShadowDomNative = const NativeShadowDomStrategy();

View File

@ -0,0 +1,5 @@
import {EmulatedShadowDomStrategy, NativeShadowDomStrategy} from './shadow_dom_strategy';
export * from './shadow_dom_strategy';
export var ShadowDomEmulated = new EmulatedShadowDomStrategy();
export var ShadowDomNative = new NativeShadowDomStrategy();

View File

@ -0,0 +1,99 @@
import {Decorator} from '../../annotations/annotations';
import {SourceLightDom, DestinationLightDom, LightDom} from './light_dom';
import {Inject} from 'di/di';
import {Element, Node, DOM} from 'facade/src/dom';
import {isPresent} from 'facade/src/lang';
import {List, ListWrapper} from 'facade/src/collection';
import {NgElement} from 'core/src/dom/element';
var _scriptTemplate = DOM.createScriptTag('type', 'ng/content')
class ContentStrategy {
nodes: List<Node>;
insert(nodes:List<Nodes>){}
}
/**
* An implementation of the content tag that is used by transcluding components.
* It is used when the content tag is not a direct child of another component,
* and thus does not affect redistribution.
*/
class RenderedContent extends ContentStrategy {
beginScript:Element;
endScript:Element;
constructor(contentEl:Element) {
this._replaceContentElementWithScriptTags(contentEl);
this.nodes = [];
}
// Inserts the nodes in between the start and end scripts.
// Previous content is removed.
insert(nodes:List<Node>) {
this.nodes = nodes;
DOM.insertAllBefore(this.endScript, nodes);
this._removeNodesUntil(ListWrapper.isEmpty(nodes) ? this.endScript : nodes[0]);
}
// Replaces the content tag with a pair of script tags
_replaceContentElementWithScriptTags(contentEl:Element) {
this.beginScript = DOM.clone(_scriptTemplate);
this.endScript = DOM.clone(_scriptTemplate);
DOM.insertBefore(contentEl, this.beginScript);
DOM.insertBefore(contentEl, this.endScript);
DOM.removeChild(DOM.parentElement(contentEl), contentEl);
}
_removeNodesUntil(node:Node) {
var p = DOM.parentElement(this.beginScript);
for (var next = DOM.nextSibling(this.beginScript);
next !== node;
next = DOM.nextSibling(this.beginScript)) {
DOM.removeChild(p, next);
}
}
}
/**
* An implementation of the content tag that is used by transcluding components.
* It is used when the content tag is a direct child of another component,
* and thus does not get rendered but only affect the distribution of its parent component.
*/
class IntermediateContent extends ContentStrategy {
destinationLightDom:LightDom;
constructor(destinationLightDom:LightDom) {
this.destinationLightDom = destinationLightDom;
this.nodes = [];
}
insert(nodes:List<Node>) {
this.nodes = nodes;
this.destinationLightDom.redistribute();
}
}
@Decorator({
selector: 'content'
})
export class Content {
select:string;
_strategy:ContentStrategy;
constructor(@Inject(DestinationLightDom) destinationLightDom, contentEl:NgElement) {
this.select = contentEl.getAttribute('select');
this._strategy = isPresent(destinationLightDom) ?
new IntermediateContent(destinationLightDom) :
new RenderedContent(contentEl.domElement);
}
nodes():List<Node> {
return this._strategy.nodes;
}
insert(nodes:List<Node>) {
this._strategy.insert(nodes);
}
}

View File

@ -0,0 +1,131 @@
import {Element, Node, DOM} from 'facade/src/dom';
import {List, ListWrapper} from 'facade/src/collection';
import {isBlank, isPresent} from 'facade/src/lang';
import {View} from '../view';
import {ElementInjector} from '../element_injector';
import {ViewPort} from '../viewport';
import {Content} from './content_tag';
export class SourceLightDom {}
export class DestinationLightDom {}
class _Root {
node:Node;
injector:ElementInjector;
constructor(node, injector) {
this.node = node;
this.injector = injector;
}
}
// TODO: LightDom should implement SourceLightDom and DestinationLightDom
// once interfaces are supported
export class LightDom {
// The light DOM of the element is enclosed inside the lightDomView
lightDomView:View;
// The shadow DOM
shadowDomView:View;
// The nodes of the light DOM
nodes:List<Node>;
roots:List<_Root>;
constructor(lightDomView:View, shadowDomView:View, element:Element) {
this.lightDomView = lightDomView;
this.shadowDomView = shadowDomView;
this.nodes = DOM.childNodesAsList(element);
this.roots = null;
}
redistribute() {
var tags = this.contentTags();
if (tags.length > 0) {
redistributeNodes(tags, this.expandedDomNodes());
}
}
contentTags(): List<Content> {
return this._collectAllContentTags(this.shadowDomView, []);
}
// Collects the Content directives from the view and all its child views
_collectAllContentTags(view: View, acc:List<Content>):List<Content> {
var eis = view.elementInjectors;
for (var i = 0; i < eis.length; ++i) {
var ei = eis[i];
if (isBlank(ei)) continue;
if (ei.hasDirective(Content)) {
ListWrapper.push(acc, ei.get(Content));
} else if (ei.hasPreBuiltObject(ViewPort)) {
var vp = ei.get(ViewPort);
ListWrapper.forEach(vp.contentTagContainers(), (view) => {
this._collectAllContentTags(view, acc);
});
}
}
return acc;
}
// Collects the nodes of the light DOM by merging:
// - nodes from enclosed ViewPorts,
// - nodes from enclosed content tags,
// - plain DOM nodes
expandedDomNodes():List {
var res = [];
var roots = this._roots();
for (var i = 0; i < roots.length; ++i) {
var root = roots[i];
var ei = root.injector;
if (isPresent(ei) && ei.hasPreBuiltObject(ViewPort)) {
var vp = root.injector.get(ViewPort);
res = ListWrapper.concat(res, vp.nodes());
} else if (isPresent(ei) && ei.hasDirective(Content)) {
var content = root.injector.get(Content);
res = ListWrapper.concat(res, content.nodes());
} else {
ListWrapper.push(res, root.node);
}
}
return res;
}
// Returns a list of Roots for all the nodes of the light DOM.
// The Root object contains the DOM node and its corresponding injector (could be null).
_roots() {
if (isPresent(this.roots)) return this.roots;
var viewInj = this.lightDomView.elementInjectors;
this.roots = ListWrapper.map(this.nodes, (n) =>
new _Root(n, ListWrapper.find(viewInj, (inj) => inj.forElement(n))));
return this.roots;
}
}
// Projects the light DOM into the shadow DOM
function redistributeNodes(contents:List<Content>, nodes:List<Node>) {
for (var i = 0; i < contents.length; ++i) {
var content = contents[i];
var select = content.select;
var matchSelector = (n) => DOM.elementMatches(n, select);
if (isBlank(select)) {
content.insert(nodes);
ListWrapper.clear(nodes);
} else {
var matchingNodes = ListWrapper.filter(nodes, matchSelector);
content.insert(matchingNodes);
ListWrapper.removeAll(nodes, matchingNodes);
}
}
}

View File

@ -0,0 +1,50 @@
import {CONST} from 'facade/src/lang';
import {DOM, Element} from 'facade/src/dom';
import {List} from 'facade/src/collection';
import {View} from './view';
import {Content} from './shadow_dom_emulation/content_tag';
import {LightDom} from './shadow_dom_emulation/light_dom';
export class ShadowDomStrategy {
@CONST() constructor() {}
attachTemplate(el:Element, view:View){}
constructLightDom(lightDomView:View, shadowDomView:View, el:Element){}
polyfillDirectives():List<Type>{ return null; };
}
export class EmulatedShadowDomStrategy extends ShadowDomStrategy {
@CONST() constructor() {}
attachTemplate(el:Element, view:View){
DOM.clearNodes(el);
moveViewNodesIntoParent(el, view);
}
constructLightDom(lightDomView:View, shadowDomView:View, el:Element){
return new LightDom(lightDomView, shadowDomView, el);
}
polyfillDirectives():List<Type> {
return [Content];
}
}
export class NativeShadowDomStrategy extends ShadowDomStrategy {
@CONST() constructor() {}
attachTemplate(el:Element, view:View){
moveViewNodesIntoParent(el.createShadowRoot(), view);
}
constructLightDom(lightDomView:View, shadowDomView:View, el:Element){
return null;
}
polyfillDirectives():List<Type> {
return [];
}
}
function moveViewNodesIntoParent(parent, view) {
for (var i = 0; i < view.nodes.length; ++i) {
DOM.appendChild(parent, view.nodes[i]);
}
}

View File

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

View File

@ -0,0 +1,589 @@
import {DOM, Element, Node, Text, DocumentFragment, TemplateElement} from 'facade/src/dom';
import {ListWrapper, MapWrapper, StringMapWrapper, List} from 'facade/src/collection';
import {AST, ContextWithVariableBindings, ChangeDispatcher, ProtoChangeDetector, ChangeDetector, ChangeRecord}
from 'change_detection/change_detection';
import {ProtoElementInjector, ElementInjector, PreBuiltObjects} from './element_injector';
import {BindingPropagationConfig} from './binding_propagation_config';
import {ElementBinder} from './element_binder';
import {DirectiveMetadata} from './directive_metadata';
import {SetterFn} from 'reflection/src/types';
import {FIELD, IMPLEMENTS, int, isPresent, isBlank, BaseException} from 'facade/src/lang';
import {Injector} from 'di/di';
import {NgElement} from 'core/src/dom/element';
import {ViewPort} from './viewport';
import {OnChange} from './interfaces';
import {Content} from './shadow_dom_emulation/content_tag';
import {LightDom, DestinationLightDom} from './shadow_dom_emulation/light_dom';
const NG_BINDING_CLASS = 'ng-binding';
const NG_BINDING_CLASS_SELECTOR = '.ng-binding';
// TODO(tbosch): Cannot use `const` because of Dart.
var NO_FORMATTERS = MapWrapper.create();
/**
* Const of making objects: http://jsperf.com/instantiate-size-of-object
*/
@IMPLEMENTS(ChangeDispatcher)
export class View {
/// This list matches the _nodes list. It is sparse, since only Elements have ElementInjector
rootElementInjectors:List<ElementInjector>;
elementInjectors:List<ElementInjector>;
bindElements:List<Element>;
textNodes:List<Text>;
changeDetector:ChangeDetector;
/// When the view is part of render tree, the DocumentFragment is empty, which is why we need
/// to keep track of the nodes.
nodes:List<Node>;
componentChildViews: List<View>;
viewPorts: List<ViewPort>;
preBuiltObjects: List<PreBuiltObjects>;
proto: ProtoView;
context: any;
contextWithLocals:ContextWithVariableBindings;
constructor(proto:ProtoView, nodes:List<Node>, protoChangeDetector:ProtoChangeDetector, protoContextLocals:Map) {
this.proto = proto;
this.nodes = nodes;
this.changeDetector = protoChangeDetector.instantiate(this, NO_FORMATTERS);
this.elementInjectors = null;
this.rootElementInjectors = null;
this.textNodes = null;
this.bindElements = null;
this.componentChildViews = null;
this.viewPorts = null;
this.preBuiltObjects = null;
this.context = null;
this.contextWithLocals = (MapWrapper.size(protoContextLocals) > 0)
? new ContextWithVariableBindings(null, MapWrapper.clone(protoContextLocals))
: null;
}
init(elementInjectors:List, rootElementInjectors:List, textNodes: List, bindElements:List, viewPorts:List, preBuiltObjects:List, componentChildViews:List) {
this.elementInjectors = elementInjectors;
this.rootElementInjectors = rootElementInjectors;
this.textNodes = textNodes;
this.bindElements = bindElements;
this.viewPorts = viewPorts;
this.preBuiltObjects = preBuiltObjects;
this.componentChildViews = componentChildViews;
}
setLocal(contextName: string, value) {
if (!this.hydrated()) throw new BaseException('Cannot set locals on dehydrated view.');
if (!MapWrapper.contains(this.proto.variableBindings, contextName)) {
return;
}
var templateName = MapWrapper.get(this.proto.variableBindings, contextName);
this.context.set(templateName, value);
}
hydrated() {
return isPresent(this.context);
}
_hydrateContext(newContext) {
if (isPresent(this.contextWithLocals)) {
this.contextWithLocals.parent = newContext;
this.context = this.contextWithLocals;
} else {
this.context = newContext;
}
// TODO(tbosch): if we have a contextWithLocals we actually only need to
// set the contextWithLocals once. Would it be faster to always use a contextWithLocals
// even if we don't have locals and not update the recordRange here?
this.changeDetector.setContext(this.context);
}
_dehydrateContext() {
if (isPresent(this.contextWithLocals)) {
this.contextWithLocals.clearValues();
}
this.context = null;
}
/**
* A dehydrated view is a state of the view that allows it to be moved around
* the view tree, without incurring the cost of recreating the underlying
* injectors and watch records.
*
* A dehydrated view has the following properties:
*
* - all element injectors are empty.
* - all appInjectors are released.
* - all viewports are empty.
* - all context locals are set to null.
* - the view context is null.
*
* A call to hydrate/dehydrate does not attach/detach the view from the view
* tree.
*/
hydrate(appInjector: Injector, hostElementInjector: ElementInjector,
context: Object) {
if (this.hydrated()) throw new BaseException('The view is already hydrated.');
this._hydrateContext(context);
// viewPorts
for (var i = 0; i < this.viewPorts.length; i++) {
this.viewPorts[i].hydrate(appInjector, hostElementInjector);
}
var binders = this.proto.elementBinders;
var componentChildViewIndex = 0;
for (var i = 0; i < binders.length; ++i) {
var componentDirective = binders[i].componentDirective;
var shadowDomAppInjector = null;
// shadowDomAppInjector
if (isPresent(componentDirective)) {
var services = componentDirective.annotation.componentServices;
if (isPresent(services))
shadowDomAppInjector = appInjector.createChild(services);
else {
shadowDomAppInjector = appInjector;
}
} else {
shadowDomAppInjector = null;
}
// elementInjectors
var elementInjector = this.elementInjectors[i];
if (isPresent(elementInjector)) {
elementInjector.instantiateDirectives(appInjector, shadowDomAppInjector, this.preBuiltObjects[i]);
}
if (isPresent(componentDirective)) {
this.componentChildViews[componentChildViewIndex++].hydrate(shadowDomAppInjector,
elementInjector, elementInjector.getComponent());
}
}
// this should be moved into DOM write queue
for (var i = 0; i < binders.length; ++i) {
var componentDirective = binders[i].componentDirective;
if (isPresent(componentDirective)) {
var lightDom = this.preBuiltObjects[i].lightDom;
if (isPresent(lightDom)) {
lightDom.redistribute();
}
}
}
}
dehydrate() {
// Note: preserve the opposite order of the hydration process.
// componentChildViews
for (var i = 0; i < this.componentChildViews.length; i++) {
this.componentChildViews[i].dehydrate();
}
// elementInjectors
for (var i = 0; i < this.elementInjectors.length; i++) {
if (isPresent(this.elementInjectors[i])) {
this.elementInjectors[i].clearDirectives();
}
}
// viewPorts
if (isPresent(this.viewPorts)) {
for (var i = 0; i < this.viewPorts.length; i++) {
this.viewPorts[i].dehydrate();
}
}
this._dehydrateContext();
}
onRecordChange(groupMemento, records:List) {
this._invokeMementos(records);
if (groupMemento instanceof DirectivePropertyGroupMemento) {
this._notifyDirectiveAboutChanges(groupMemento, records);
}
}
_invokeMementos(records:List) {
for(var i = 0; i < records.length; ++i) {
this._invokeMementoFor(records[i]);
}
}
_notifyDirectiveAboutChanges(groupMemento, records:List) {
var dir = groupMemento.directive(this.elementInjectors);
if (dir instanceof OnChange) {
dir.onChange(this._collectChanges(records));
}
}
// dispatch to element injector or text nodes based on context
_invokeMementoFor(record:ChangeRecord) {
var memento = record.bindingMemento;
if (memento instanceof DirectivePropertyMemento) {
// we know that it is DirectivePropertyMemento
var directiveMemento:DirectivePropertyMemento = memento;
directiveMemento.invoke(record, this.elementInjectors);
} else if (memento instanceof ElementPropertyMemento) {
var elementMemento:ElementPropertyMemento = memento;
elementMemento.invoke(record, this.bindElements);
} else {
// we know it refers to _textNodes.
var textNodeIndex:number = memento;
DOM.setText(this.textNodes[textNodeIndex], record.currentValue);
}
}
_collectChanges(records:List) {
var changes = StringMapWrapper.create();
for(var i = 0; i < records.length; ++i) {
var record = records[i];
var propertyUpdate = new PropertyUpdate(record.currentValue, record.previousValue);
StringMapWrapper.set(changes, record.bindingMemento._setterName, propertyUpdate);
}
return changes;
}
}
export class ProtoView {
element:Element;
elementBinders:List<ElementBinder>;
protoChangeDetector:ProtoChangeDetector;
variableBindings: Map;
protoContextLocals:Map;
textNodesWithBindingCount:int;
elementsWithBindingCount:int;
instantiateInPlace:boolean;
rootBindingOffset:int;
isTemplateElement:boolean;
constructor(
template:Element,
protoChangeDetector:ProtoChangeDetector) {
this.element = template;
this.elementBinders = [];
this.variableBindings = MapWrapper.create();
this.protoContextLocals = MapWrapper.create();
this.protoChangeDetector = protoChangeDetector;
this.textNodesWithBindingCount = 0;
this.elementsWithBindingCount = 0;
this.instantiateInPlace = false;
this.rootBindingOffset = (isPresent(this.element) && DOM.hasClass(this.element, NG_BINDING_CLASS))
? 1 : 0;
this.isTemplateElement = this.element instanceof TemplateElement;
}
// TODO(rado): hostElementInjector should be moved to hydrate phase.
instantiate(hostElementInjector: ElementInjector):View {
var rootElementClone = this.instantiateInPlace ? this.element : DOM.clone(this.element);
var elementsWithBindingsDynamic;
if (this.isTemplateElement) {
elementsWithBindingsDynamic = DOM.querySelectorAll(rootElementClone.content, NG_BINDING_CLASS_SELECTOR);
} else {
elementsWithBindingsDynamic= DOM.getElementsByClassName(rootElementClone, NG_BINDING_CLASS);
}
var elementsWithBindings = ListWrapper.createFixedSize(elementsWithBindingsDynamic.length);
for (var i = 0; i < elementsWithBindingsDynamic.length; ++i) {
elementsWithBindings[i] = elementsWithBindingsDynamic[i];
}
var viewNodes;
if (this.isTemplateElement) {
var childNode = DOM.firstChild(rootElementClone.content);
viewNodes = []; // TODO(perf): Should be fixed size, since we could pre-compute in in ProtoView
// Note: An explicit loop is the fastest way to convert a DOM array into a JS array!
while(childNode != null) {
ListWrapper.push(viewNodes, childNode);
childNode = DOM.nextSibling(childNode);
}
} else {
viewNodes = [rootElementClone];
}
var view = new View(this, viewNodes, this.protoChangeDetector, this.protoContextLocals);
var binders = this.elementBinders;
var elementInjectors = ListWrapper.createFixedSize(binders.length);
var rootElementInjectors = [];
var textNodes = [];
var elementsWithPropertyBindings = [];
var preBuiltObjects = ListWrapper.createFixedSize(binders.length);
var viewPorts = [];
var componentChildViews = [];
for (var i = 0; i < binders.length; i++) {
var binder = binders[i];
var element;
if (i === 0 && this.rootBindingOffset === 1) {
element = rootElementClone;
} else {
element = elementsWithBindings[i - this.rootBindingOffset];
}
var elementInjector = null;
// elementInjectors and rootElementInjectors
var protoElementInjector = binder.protoElementInjector;
if (isPresent(protoElementInjector)) {
if (isPresent(protoElementInjector.parent)) {
var parentElementInjector = elementInjectors[protoElementInjector.parent.index];
elementInjector = protoElementInjector.instantiate(parentElementInjector, null, binder.events);
} else {
elementInjector = protoElementInjector.instantiate(null, hostElementInjector, binder.events);
ListWrapper.push(rootElementInjectors, elementInjector);
}
}
elementInjectors[i] = elementInjector;
if (binder.hasElementPropertyBindings) {
ListWrapper.push(elementsWithPropertyBindings, element);
}
// textNodes
var textNodeIndices = binder.textNodeIndices;
if (isPresent(textNodeIndices)) {
var childNode = DOM.firstChild(DOM.templateAwareRoot(element));
for (var j = 0, k = 0; j < textNodeIndices.length; j++) {
for(var index = textNodeIndices[j]; k < index; k++) {
childNode = DOM.nextSibling(childNode);
}
ListWrapper.push(textNodes, childNode);
}
}
// componentChildViews
var lightDom = null;
var bindingPropagationConfig = null;
if (isPresent(binder.componentDirective)) {
var childView = binder.nestedProtoView.instantiate(elementInjector);
view.changeDetector.addChild(childView.changeDetector);
lightDom = binder.componentDirective.shadowDomStrategy.constructLightDom(view, childView, element);
binder.componentDirective.shadowDomStrategy.attachTemplate(element, childView);
bindingPropagationConfig = new BindingPropagationConfig(view.changeDetector);
ListWrapper.push(componentChildViews, childView);
}
// viewPorts
var viewPort = null;
if (isPresent(binder.templateDirective)) {
var destLightDom = this._parentElementLightDom(protoElementInjector, preBuiltObjects);
viewPort = new ViewPort(view, element, binder.nestedProtoView, elementInjector, destLightDom);
ListWrapper.push(viewPorts, viewPort);
}
// preBuiltObjects
if (isPresent(elementInjector)) {
preBuiltObjects[i] = new PreBuiltObjects(view, new NgElement(element), viewPort,
lightDom, bindingPropagationConfig);
}
// events
if (isPresent(binder.events)) {
MapWrapper.forEach(binder.events, (expr, eventName) => {
if (isBlank(elementInjector) || !elementInjector.hasEventEmitter(eventName)) {
ProtoView._addNativeEventListener(element, eventName, expr, view);
}
});
}
}
view.init(elementInjectors, rootElementInjectors, textNodes, elementsWithPropertyBindings,
viewPorts, preBuiltObjects, componentChildViews);
return view;
}
static _addNativeEventListener(element: Element, eventName: string, expr: AST, view: View) {
var locals = MapWrapper.create();
var innerCallback = ProtoView.buildInnerCallback(expr, view, locals);
DOM.on(element, eventName, (event) => {
if (event.target === element) {
innerCallback(event);
}
});
}
static buildInnerCallback(expr:AST, view:View, locals: Map) {
return (event) => {
// Most of the time the event will be fired only when the view is
// in the live document. However, in a rare circumstance the
// view might get dehydrated, in between the event queuing up and
// firing.
if (view.hydrated()) {
MapWrapper.set(locals, '\$event', event);
var context = new ContextWithVariableBindings(view.context, locals);
expr.eval(context);
}
}
}
_parentElementLightDom(protoElementInjector:ProtoElementInjector, preBuiltObjects:List):LightDom {
var p = protoElementInjector.parent;
return isPresent(p) ? preBuiltObjects[p.index].lightDom : null;
}
bindVariable(contextName:string, templateName:string) {
MapWrapper.set(this.variableBindings, contextName, templateName);
MapWrapper.set(this.protoContextLocals, templateName, null);
}
bindElement(protoElementInjector:ProtoElementInjector,
componentDirective:DirectiveMetadata = null, templateDirective:DirectiveMetadata = null):ElementBinder {
var elBinder = new ElementBinder(protoElementInjector, componentDirective, templateDirective);
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];
if (isBlank(elBinder.textNodeIndices)) {
elBinder.textNodeIndices = ListWrapper.create();
}
ListWrapper.push(elBinder.textNodeIndices, indexInParent);
var memento = this.textNodesWithBindingCount++;
this.protoChangeDetector.addAst(expression, memento, memento);
}
/**
* Adds an element property binding for the last created ElementBinder via bindElement
*/
bindElementProperty(expression:AST, setterName:string, setter:SetterFn) {
var elBinder = this.elementBinders[this.elementBinders.length-1];
if (!elBinder.hasElementPropertyBindings) {
elBinder.hasElementPropertyBindings = true;
this.elementsWithBindingCount++;
}
var memento = new ElementPropertyMemento(this.elementsWithBindingCount-1, setterName, setter);
this.protoChangeDetector.addAst(expression, memento, memento);
}
/**
* Adds an event binding for the last created ElementBinder via bindElement
*/
bindEvent(eventName:string, expression:AST) {
var elBinder = this.elementBinders[this.elementBinders.length-1];
if (isBlank(elBinder.events)) {
elBinder.events = MapWrapper.create();
}
MapWrapper.set(elBinder.events, eventName, expression);
}
/**
* Adds a directive property binding for the last created ElementBinder via bindElement
*/
bindDirectiveProperty(
directiveIndex:number,
expression:AST,
setterName:string,
setter:SetterFn,
isContentWatch: boolean) {
var expMemento = new DirectivePropertyMemento(
this.elementBinders.length-1,
directiveIndex,
setterName,
setter
);
var groupMemento = DirectivePropertyGroupMemento.get(expMemento);
this.protoChangeDetector.addAst(expression, expMemento, groupMemento, isContentWatch);
}
// Create a rootView as if the compiler encountered <rootcmp></rootcmp>,
// and the component template is already compiled into protoView.
// Used for bootstrapping.
static createRootProtoView(protoView: ProtoView,
insertionElement, rootComponentAnnotatedType: DirectiveMetadata,
protoChangeDetector:ProtoChangeDetector
): ProtoView {
DOM.addClass(insertionElement, 'ng-binding');
var rootProtoView = new ProtoView(insertionElement, protoChangeDetector);
rootProtoView.instantiateInPlace = true;
var binder = rootProtoView.bindElement(
new ProtoElementInjector(null, 0, [rootComponentAnnotatedType.type], true));
binder.componentDirective = rootComponentAnnotatedType;
binder.nestedProtoView = protoView;
return rootProtoView;
}
}
export class ElementPropertyMemento {
_elementIndex:int;
_setterName:string;
_setter:SetterFn;
constructor(elementIndex:int, setterName:string, setter:SetterFn) {
this._elementIndex = elementIndex;
this._setterName = setterName;
this._setter = setter;
}
invoke(record:ChangeRecord, bindElements:List<Element>) {
var element:Element = bindElements[this._elementIndex];
this._setter(element, record.currentValue);
}
}
export class DirectivePropertyMemento {
_elementInjectorIndex:int;
_directiveIndex:int;
_setterName:string;
_setter:SetterFn;
constructor(
elementInjectorIndex:number,
directiveIndex:number,
setterName:string,
setter:SetterFn) {
this._elementInjectorIndex = elementInjectorIndex;
this._directiveIndex = directiveIndex;
this._setterName = setterName;
this._setter = setter;
}
invoke(record:ChangeRecord, elementInjectors:List<ElementInjector>) {
var elementInjector:ElementInjector = elementInjectors[this._elementInjectorIndex];
var directive = elementInjector.getAtIndex(this._directiveIndex);
this._setter(directive, record.currentValue);
}
}
var _groups = MapWrapper.create();
class DirectivePropertyGroupMemento {
_elementInjectorIndex:number;
_directiveIndex:number;
constructor(elementInjectorIndex:number, directiveIndex:number) {
this._elementInjectorIndex = elementInjectorIndex;
this._directiveIndex = directiveIndex;
}
static get(memento:DirectivePropertyMemento) {
var elementInjectorIndex = memento._elementInjectorIndex;
var directiveIndex = memento._directiveIndex;
var id = elementInjectorIndex * 100 + directiveIndex;
if (!MapWrapper.contains(_groups, id)) {
MapWrapper.set(_groups, id, new DirectivePropertyGroupMemento(elementInjectorIndex, directiveIndex));
}
return MapWrapper.get(_groups, id);
}
directive(elementInjectors:List<ElementInjector>) {
var elementInjector:ElementInjector = elementInjectors[this._elementInjectorIndex];
return elementInjector.getAtIndex(this._directiveIndex);
}
}
class PropertyUpdate {
currentValue;
previousValue;
constructor(currentValue, previousValue) {
this.currentValue = currentValue;
this.previousValue = previousValue;
}
}

View File

@ -0,0 +1,151 @@
import {View, ProtoView} from './view';
import {DOM, Node, Element} from 'facade/src/dom';
import {ListWrapper, MapWrapper, List} from 'facade/src/collection';
import {BaseException} from 'facade/src/lang';
import {Injector} from 'di/di';
import {ElementInjector} from 'core/src/compiler/element_injector';
import {isPresent, isBlank} from 'facade/src/lang';
export class ViewPort {
parentView: View;
templateElement: Element;
defaultProtoView: ProtoView;
_views: List<View>;
_lightDom: any;
elementInjector: ElementInjector;
appInjector: Injector;
hostElementInjector: ElementInjector;
constructor(parentView: View, templateElement: Element, defaultProtoView: ProtoView,
elementInjector: ElementInjector, lightDom = null) {
this.parentView = parentView;
this.templateElement = templateElement;
this.defaultProtoView = defaultProtoView;
this.elementInjector = elementInjector;
this._lightDom = lightDom;
// The order in this list matches the DOM order.
this._views = [];
this.appInjector = null;
this.hostElementInjector = null;
}
hydrate(appInjector: Injector, hostElementInjector: ElementInjector) {
this.appInjector = appInjector;
this.hostElementInjector = hostElementInjector;
}
dehydrate() {
this.appInjector = null;
this.hostElementInjector = null;
this.clear();
}
clear() {
for (var i = this._views.length - 1; i >= 0; i--) {
this.remove(i);
}
}
get(index: number): View {
return this._views[index];
}
get length() {
return this._views.length;
}
_siblingToInsertAfter(index: number) {
if (index == 0) return this.templateElement;
return ListWrapper.last(this._views[index - 1].nodes);
}
hydrated() {
return isPresent(this.appInjector);
}
// TODO(rado): profile and decide whether bounds checks should be added
// to the methods below.
create(atIndex=-1): View {
if (!this.hydrated()) throw new BaseException(
'Cannot create views on a dehydrated view port');
// TODO(rado): replace with viewFactory.
var newView = this.defaultProtoView.instantiate(this.hostElementInjector);
newView.hydrate(this.appInjector, this.hostElementInjector, this.parentView.context);
return this.insert(newView, atIndex);
}
insert(view, atIndex=-1): View {
if (atIndex == -1) atIndex = this._views.length;
ListWrapper.insert(this._views, atIndex, view);
if (isBlank(this._lightDom)) {
ViewPort.moveViewNodesAfterSibling(this._siblingToInsertAfter(atIndex), view);
} else {
this._lightDom.redistribute();
}
this.parentView.changeDetector.addChild(view.changeDetector);
this._linkElementInjectors(view);
return view;
}
remove(atIndex=-1) {
if (atIndex == -1) atIndex = this._views.length - 1;
var view = this.detach(atIndex);
view.dehydrate();
// view is intentionally not returned to the client.
}
/**
* The method can be used together with insert to implement a view move, i.e.
* moving the dom nodes while the directives in the view stay intact.
*/
detach(atIndex=-1): View {
if (atIndex == -1) atIndex = this._views.length - 1;
var detachedView = this.get(atIndex);
ListWrapper.removeAt(this._views, atIndex);
if (isBlank(this._lightDom)) {
ViewPort.removeViewNodesFromParent(this.templateElement.parentNode, detachedView);
} else {
this._lightDom.redistribute();
}
detachedView.changeDetector.remove();
this._unlinkElementInjectors(detachedView);
return detachedView;
}
contentTagContainers() {
return this._views;
}
nodes():List<Node> {
var r = [];
for (var i = 0; i < this._views.length; ++i) {
r = ListWrapper.concat(r, this._views[i].nodes);
}
return r;
}
_linkElementInjectors(view) {
for (var i = 0; i < view.rootElementInjectors.length; ++i) {
view.rootElementInjectors[i].parent = this.elementInjector;
}
}
_unlinkElementInjectors(view) {
for (var i = 0; i < view.rootElementInjectors.length; ++i) {
view.rootElementInjectors[i].parent = null;
}
}
static moveViewNodesAfterSibling(sibling, view) {
for (var i = view.nodes.length - 1; i >= 0; --i) {
DOM.insertAfter(sibling, view.nodes[i]);
}
}
static removeViewNodesFromParent(parent, view) {
for (var i = view.nodes.length - 1; i >= 0; --i) {
DOM.removeChild(parent, view.nodes[i]);
}
}
}

View File

@ -0,0 +1,13 @@
import {DOM, Element} from 'facade/src/dom';
import {normalizeBlank} from 'facade/src/lang';
export class NgElement {
domElement:Element;
constructor(domElement:Element) {
this.domElement = domElement;
}
getAttribute(name:string) {
return normalizeBlank(DOM.getAttribute(this.domElement, name));
}
}

View File

@ -0,0 +1,35 @@
import {FIELD, print} from 'facade/src/lang';
import {ChangeDetector} from 'change_detection/change_detection';
import {VmTurnZone} from 'core/src/zone/vm_turn_zone';
import {ListWrapper} from 'facade/src/collection';
export class LifeCycle {
_changeDetector:ChangeDetector;
_enforceNoNewChanges:boolean;
constructor(changeDetector:ChangeDetector, enforceNoNewChanges:boolean = false) {
this._changeDetector = changeDetector;
this._enforceNoNewChanges = enforceNoNewChanges;
}
registerWith(zone:VmTurnZone) {
// temporary error handler, we should inject one
var errorHandler = (exception, stackTrace) => {
var longStackTrace = ListWrapper.join(stackTrace, "\n\n-----async gap-----\n");
print(`${exception}\n\n${longStackTrace}`);
throw exception;
};
zone.initCallbacks({
onErrorHandler: errorHandler,
onTurnDone: () => this.tick()
});
}
tick() {
this._changeDetector.detectChanges();
if (this._enforceNoNewChanges) {
this._changeDetector.checkNoChanges();
}
}
}

View File

@ -0,0 +1,105 @@
library angular.zone;
import 'dart:async' as async;
import 'package:stack_trace/stack_trace.dart' show Chain;
class VmTurnZone {
Function _onTurnStart;
Function _onTurnDone;
Function _onScheduleMicrotask;
Function _onErrorHandler;
async.Zone _outerZone;
async.Zone _innerZone;
int _nestedRunCounter;
VmTurnZone({bool enableLongStackTrace}) {
_nestedRunCounter = 0;
_outerZone = async.Zone.current;
_innerZone = _createInnerZoneWithErrorHandling(enableLongStackTrace);
}
initCallbacks({Function onTurnStart, Function onTurnDone, Function onScheduleMicrotask, Function onErrorHandler}) {
this._onTurnStart = onTurnStart;
this._onTurnDone = onTurnDone;
this._onScheduleMicrotask = onScheduleMicrotask;
this._onErrorHandler = onErrorHandler;
}
dynamic run(fn()) => _innerZone.run(fn);
dynamic runOutsideAngular(fn()) => _outerZone.run(fn);
async.Zone _createInnerZoneWithErrorHandling(bool enableLongStackTrace) {
if (enableLongStackTrace) {
return Chain.capture(() {
return _createInnerZone(async.Zone.current);
}, onError: this._onErrorWithLongStackTrace);
} else {
return async.runZoned(() {
return _createInnerZone(async.Zone.current);
}, onError: this._onErrorWithoutLongStackTrace);
}
}
async.Zone _createInnerZone(async.Zone zone) {
return zone.fork(specification: new async.ZoneSpecification(
run: _onRun,
runUnary: _onRunUnary,
scheduleMicrotask: _onMicrotask
));
}
dynamic _onRunBase(async.Zone self, async.ZoneDelegate delegate, async.Zone zone, fn()) {
_nestedRunCounter++;
try {
if (_nestedRunCounter == 1 && _onTurnStart != null) delegate.run(zone, _onTurnStart);
return fn();
} catch (e, s) {
if (_onErrorHandler != null && _nestedRunCounter == 1) {
_onErrorHandler(e, [s.toString()]);
} else {
rethrow;
}
} finally {
_nestedRunCounter--;
if (_nestedRunCounter == 0 && _onTurnDone != null) _finishTurn(zone, delegate);
}
}
dynamic _onRun(async.Zone self, async.ZoneDelegate delegate, async.Zone zone, fn()) =>
_onRunBase(self, delegate, zone, () => delegate.run(zone, fn));
dynamic _onRunUnary(async.Zone self, async.ZoneDelegate delegate, async.Zone zone, fn(args), args) =>
_onRunBase(self, delegate, zone, () => delegate.runUnary(zone, fn, args));
void _finishTurn(zone, delegate) {
delegate.run(zone, _onTurnDone);
}
_onMicrotask(async.Zone self, async.ZoneDelegate delegate, async.Zone zone, fn) {
if (this._onScheduleMicrotask != null) {
_onScheduleMicrotask(fn);
} else {
delegate.scheduleMicrotask(zone, fn);
}
}
_onErrorWithLongStackTrace(exception, Chain chain) {
final traces = chain.terse.traces.map((t) => t.toString()).toList();
_onError(exception, traces, chain.traces[0]);
}
_onErrorWithoutLongStackTrace(exception, StackTrace trace) {
_onError(exception, [trace.toString()], trace);
}
_onError(exception, List<String> traces, StackTrace singleTrace) {
if (_onErrorHandler != null) {
_onErrorHandler(exception, traces);
} else {
_outerZone.handleUncaughtError(exception, singleTrace);
}
}
}

View File

@ -0,0 +1,89 @@
import {List, ListWrapper, StringMapWrapper} from 'facade/src/collection';
import {normalizeBlank, isPresent} from 'facade/src/lang';
export class VmTurnZone {
_outerZone;
_innerZone;
_onTurnStart:Function;
_onTurnDone:Function;
_onErrorHandler:Function;
_nestedRunCounter:number;
constructor({enableLongStackTrace}) {
this._nestedRunCounter = 0;
this._onTurnStart = null;
this._onTurnDone = null;
this._onErrorHandler = null;
this._outerZone = window.zone;
this._innerZone = this._createInnerZone(this._outerZone, enableLongStackTrace);
}
initCallbacks({onTurnStart, onTurnDone, onScheduleMicrotask, onErrorHandler} = {}) {
this._onTurnStart = normalizeBlank(onTurnStart);
this._onTurnDone = normalizeBlank(onTurnDone);
this._onErrorHandler = normalizeBlank(onErrorHandler);
}
run(fn) {
return this._innerZone.run(fn);
}
runOutsideAngular(fn) {
return this._outerZone.run(fn);
}
_createInnerZone(zone, enableLongStackTrace) {
var vmTurnZone = this;
var errorHandling;
if (enableLongStackTrace) {
errorHandling = StringMapWrapper.merge(Zone.longStackTraceZone, {
onError: function (e) {
vmTurnZone._onError(this, e)
}
});
} else {
errorHandling = {
onError: function (e) {
vmTurnZone._onError(this, e)
}
};
}
return zone.fork(errorHandling).fork({
beforeTask: () => {this._beforeTask()},
afterTask: () => {this._afterTask()}
});
}
_beforeTask(){
this._nestedRunCounter ++;
if(this._nestedRunCounter === 1 && this._onTurnStart) {
this._onTurnStart();
}
}
_afterTask(){
this._nestedRunCounter --;
if(this._nestedRunCounter === 0 && this._onTurnDone) {
this._onTurnDone();
}
}
_onError(zone, e) {
if (isPresent(this._onErrorHandler)) {
var trace = [normalizeBlank(e.stack)];
while (zone && zone.constructedAtException) {
trace.push(zone.constructedAtException.get());
zone = zone.parent;
}
this._onErrorHandler(e, trace);
} else {
throw e;
}
}
}