chore(packaging): move files to match target file structure
This commit is contained in:
142
modules/angular2/src/core/annotations/annotations.js
vendored
Normal file
142
modules/angular2/src/core/annotations/annotations.js
vendored
Normal 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";
|
14
modules/angular2/src/core/annotations/events.js
vendored
Normal file
14
modules/angular2/src/core/annotations/events.js
vendored
Normal 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;
|
||||
}
|
||||
}
|
31
modules/angular2/src/core/annotations/template_config.js
vendored
Normal file
31
modules/angular2/src/core/annotations/template_config.js
vendored
Normal 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;
|
||||
}
|
||||
}
|
22
modules/angular2/src/core/annotations/visibility.js
vendored
Normal file
22
modules/angular2/src/core/annotations/visibility.js
vendored
Normal 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
126
modules/angular2/src/core/application.js
vendored
Normal 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);
|
||||
}
|
25
modules/angular2/src/core/compiler/binding_propagation_config.js
vendored
Normal file
25
modules/angular2/src/core/compiler/binding_propagation_config.js
vendored
Normal 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;
|
||||
}
|
||||
}
|
111
modules/angular2/src/core/compiler/compiler.js
vendored
Normal file
111
modules/angular2/src/core/compiler/compiler.js
vendored
Normal 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;
|
||||
}
|
||||
}
|
22
modules/angular2/src/core/compiler/directive_metadata.js
vendored
Normal file
22
modules/angular2/src/core/compiler/directive_metadata.js
vendored
Normal 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;
|
||||
}
|
||||
}
|
48
modules/angular2/src/core/compiler/directive_metadata_reader.js
vendored
Normal file
48
modules/angular2/src/core/compiler/directive_metadata_reader.js
vendored
Normal 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;
|
||||
}
|
||||
}
|
30
modules/angular2/src/core/compiler/element_binder.js
vendored
Normal file
30
modules/angular2/src/core/compiler/element_binder.js
vendored
Normal 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;
|
||||
}
|
||||
}
|
602
modules/angular2/src/core/compiler/element_injector.js
vendored
Normal file
602
modules/angular2/src/core/compiler/element_injector.js
vendored
Normal 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;
|
||||
}
|
||||
}
|
5
modules/angular2/src/core/compiler/interfaces.js
vendored
Normal file
5
modules/angular2/src/core/compiler/interfaces.js
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
export class OnChange {
|
||||
onChange(changes) {
|
||||
throw "OnChange.onChange is not implemented";
|
||||
}
|
||||
}
|
59
modules/angular2/src/core/compiler/pipeline/compile_control.js
vendored
Normal file
59
modules/angular2/src/core/compiler/pipeline/compile_control.js
vendored
Normal 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);
|
||||
}
|
||||
}
|
159
modules/angular2/src/core/compiler/pipeline/compile_element.js
vendored
Normal file
159
modules/angular2/src/core/compiler/pipeline/compile_element.js
vendored
Normal 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;
|
||||
}
|
||||
}
|
47
modules/angular2/src/core/compiler/pipeline/compile_pipeline.js
vendored
Normal file
47
modules/angular2/src/core/compiler/pipeline/compile_pipeline.js
vendored
Normal 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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
modules/angular2/src/core/compiler/pipeline/compile_step.js
vendored
Normal file
11
modules/angular2/src/core/compiler/pipeline/compile_step.js
vendored
Normal 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) {}
|
||||
}
|
38
modules/angular2/src/core/compiler/pipeline/default_steps.js
vendored
Normal file
38
modules/angular2/src/core/compiler/pipeline/default_steps.js
vendored
Normal 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()
|
||||
];
|
||||
}
|
82
modules/angular2/src/core/compiler/pipeline/directive_parser.js
vendored
Normal file
82
modules/angular2/src/core/compiler/pipeline/directive_parser.js
vendored
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
145
modules/angular2/src/core/compiler/pipeline/element_binder_builder.js
vendored
Normal file
145
modules/angular2/src/core/compiler/pipeline/element_binder_builder.js
vendored
Normal 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
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
44
modules/angular2/src/core/compiler/pipeline/element_binding_marker.js
vendored
Normal file
44
modules/angular2/src/core/compiler/pipeline/element_binding_marker.js
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
76
modules/angular2/src/core/compiler/pipeline/property_binding_parser.js
vendored
Normal file
76
modules/angular2/src/core/compiler/pipeline/property_binding_parser.js
vendored
Normal 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);
|
||||
}
|
||||
}
|
72
modules/angular2/src/core/compiler/pipeline/proto_element_injector_builder.js
vendored
Normal file
72
modules/angular2/src/core/compiler/pipeline/proto_element_injector_builder.js
vendored
Normal 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);
|
||||
}
|
||||
}
|
50
modules/angular2/src/core/compiler/pipeline/proto_view_builder.js
vendored
Normal file
50
modules/angular2/src/core/compiler/pipeline/proto_view_builder.js
vendored
Normal 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;
|
||||
}
|
||||
}
|
45
modules/angular2/src/core/compiler/pipeline/text_interpolation_parser.js
vendored
Normal file
45
modules/angular2/src/core/compiler/pipeline/text_interpolation_parser.js
vendored
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
113
modules/angular2/src/core/compiler/pipeline/view_splitter.js
vendored
Normal file
113
modules/angular2/src/core/compiler/pipeline/view_splitter.js
vendored
Normal 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, '');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
249
modules/angular2/src/core/compiler/selector.js
vendored
Normal file
249
modules/angular2/src/core/compiler/selector.js
vendored
Normal 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);
|
||||
}
|
||||
}
|
9
modules/angular2/src/core/compiler/shadow_dom.dart
Normal file
9
modules/angular2/src/core/compiler/shadow_dom.dart
Normal 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();
|
5
modules/angular2/src/core/compiler/shadow_dom.es6
Normal file
5
modules/angular2/src/core/compiler/shadow_dom.es6
Normal 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();
|
99
modules/angular2/src/core/compiler/shadow_dom_emulation/content_tag.js
vendored
Normal file
99
modules/angular2/src/core/compiler/shadow_dom_emulation/content_tag.js
vendored
Normal 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);
|
||||
}
|
||||
}
|
131
modules/angular2/src/core/compiler/shadow_dom_emulation/light_dom.js
vendored
Normal file
131
modules/angular2/src/core/compiler/shadow_dom_emulation/light_dom.js
vendored
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
50
modules/angular2/src/core/compiler/shadow_dom_strategy.js
vendored
Normal file
50
modules/angular2/src/core/compiler/shadow_dom_strategy.js
vendored
Normal 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]);
|
||||
}
|
||||
}
|
11
modules/angular2/src/core/compiler/template_loader.js
vendored
Normal file
11
modules/angular2/src/core/compiler/template_loader.js
vendored
Normal 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;
|
||||
}
|
||||
}
|
589
modules/angular2/src/core/compiler/view.js
vendored
Normal file
589
modules/angular2/src/core/compiler/view.js
vendored
Normal 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;
|
||||
}
|
||||
}
|
151
modules/angular2/src/core/compiler/viewport.js
vendored
Normal file
151
modules/angular2/src/core/compiler/viewport.js
vendored
Normal 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]);
|
||||
}
|
||||
}
|
||||
}
|
13
modules/angular2/src/core/dom/element.js
vendored
Normal file
13
modules/angular2/src/core/dom/element.js
vendored
Normal 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));
|
||||
}
|
||||
}
|
35
modules/angular2/src/core/life_cycle/life_cycle.js
vendored
Normal file
35
modules/angular2/src/core/life_cycle/life_cycle.js
vendored
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
105
modules/angular2/src/core/zone/vm_turn_zone.dart
Normal file
105
modules/angular2/src/core/zone/vm_turn_zone.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
89
modules/angular2/src/core/zone/vm_turn_zone.es6
Normal file
89
modules/angular2/src/core/zone/vm_turn_zone.es6
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user