refactor(render): use render layer fully

Introduces angular2/src/core/compiler/ViewFactory which
extracts ProtoView.instantiate and replaces ViewPool.

Note: This is a work in progress commit to unblock other commits.
There will be follow ups to add unit tests, remove TODOs, …
This commit is contained in:
Tobias Bosch
2015-04-07 20:54:20 -07:00
parent de581ea8b3
commit 50098767fc
60 changed files with 1206 additions and 3341 deletions

View File

@ -9,12 +9,12 @@ import {ExceptionHandler} from './exception_handler';
import {TemplateLoader} from 'angular2/src/render/dom/compiler/template_loader';
import {TemplateResolver} from './compiler/template_resolver';
import {DirectiveMetadataReader} from './compiler/directive_metadata_reader';
import {DirectiveBinding} from './compiler/element_injector';
import {List, ListWrapper} from 'angular2/src/facade/collection';
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
import {VmTurnZone} from 'angular2/src/core/zone/vm_turn_zone';
import {LifeCycle} from 'angular2/src/core/life_cycle/life_cycle';
import {ShadowDomStrategy, NativeShadowDomStrategy, EmulatedUnscopedShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
import {ShadowDomStrategy} from 'angular2/src/render/dom/shadow_dom/shadow_dom_strategy';
import {EmulatedUnscopedShadowDomStrategy} from 'angular2/src/render/dom/shadow_dom/emulated_unscoped_shadow_dom_strategy';
import {XHR} from 'angular2/src/services/xhr';
import {XHRImpl} from 'angular2/src/services/xhr_impl';
import {EventManager, DomEventsPlugin} from 'angular2/src/render/dom/events/event_manager';
@ -27,6 +27,13 @@ import {StyleInliner} from 'angular2/src/render/dom/shadow_dom/style_inliner';
import {Component} from 'angular2/src/core/annotations/annotations';
import {PrivateComponentLoader} from 'angular2/src/core/compiler/private_component_loader';
import {TestabilityRegistry, Testability} from 'angular2/src/core/testability/testability';
import {ViewFactory, VIEW_POOL_CAPACITY} from 'angular2/src/core/compiler/view_factory';
import {ProtoViewFactory} from 'angular2/src/core/compiler/proto_view_factory';
import {Renderer} from 'angular2/src/render/api';
import {DirectDomRenderer} from 'angular2/src/render/dom/direct_dom_renderer';
import * as rc from 'angular2/src/render/dom/compiler/compiler';
import * as rvf from 'angular2/src/render/dom/view/view_factory';
import {
appViewToken,
appChangeDetectorToken,
@ -60,7 +67,7 @@ function _injectorBindings(appComponentType): List<Binding> {
return element;
}, [appComponentAnnotatedTypeToken, appDocumentToken]),
bind(appViewToken).toAsyncFactory((changeDetection, compiler, injector, appElement,
appComponentAnnotatedType, strategy, eventManager, testability, registry) => {
appComponentAnnotatedType, testability, registry, viewFactory) => {
// We need to do this here to ensure that we create Testability and
// it's ready on the window for users.
@ -73,18 +80,18 @@ function _injectorBindings(appComponentType): List<Binding> {
}
return compiler.compileRoot(
appElement,
DirectiveBinding.createFromType(appComponentAnnotatedType.type, appComponentAnnotatedType.annotation)
appComponentAnnotatedType.type
).then(
(appProtoView) => {
// 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, eventManager);
view.hydrate(injector, null, null, new Object(), null);
var view = viewFactory.getView(appProtoView);
view.hydrate(injector, null, new Object(), null);
return view;
});
}, [ChangeDetection, Compiler, Injector, appElementToken, appComponentAnnotatedTypeToken,
ShadowDomStrategy, EventManager, Testability, TestabilityRegistry]),
Testability, TestabilityRegistry, ViewFactory]),
bind(appChangeDetectorToken).toFactory((rootView) => rootView.changeDetector,
[appViewToken]),
@ -98,6 +105,23 @@ function _injectorBindings(appComponentType): List<Binding> {
bind(ShadowDomStrategy).toFactory(
(styleUrlResolver, doc) => new EmulatedUnscopedShadowDomStrategy(styleUrlResolver, doc.head),
[StyleUrlResolver, appDocumentToken]),
bind(Renderer).toClass(DirectDomRenderer),
bind(rc.Compiler).toClass(rc.DefaultCompiler),
// TODO(tbosch): We need an explicit factory here, as
// we are getting errors in dart2js with mirrors...
bind(rvf.ViewFactory).toFactory(
(capacity, eventManager, shadowDomStrategy) => new rvf.ViewFactory(capacity, eventManager, shadowDomStrategy),
[rvf.VIEW_POOL_CAPACITY, EventManager, ShadowDomStrategy]
),
bind(rvf.VIEW_POOL_CAPACITY).toValue(100000),
ProtoViewFactory,
// TODO(tbosch): We need an explicit factory here, as
// we are getting errors in dart2js with mirrors...
bind(ViewFactory).toFactory(
(capacity) => new ViewFactory(capacity),
[VIEW_POOL_CAPACITY]
),
bind(VIEW_POOL_CAPACITY).toValue(100000),
Compiler,
CompilerCache,
TemplateResolver,

View File

@ -3,24 +3,16 @@ import {Type, isBlank, isPresent, BaseException, normalizeBlank, stringify} from
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
import {List, ListWrapper, Map, MapWrapper} from 'angular2/src/facade/collection';
import {ChangeDetection, Parser} from 'angular2/change_detection';
import {DirectiveMetadataReader} from './directive_metadata_reader';
import {Component, Viewport, DynamicComponent, Decorator} from '../annotations/annotations';
import {ProtoView} from './view';
import {DirectiveBinding} from './element_injector';
import {TemplateResolver} from './template_resolver';
import {Template} from '../annotations/template';
import {ShadowDomStrategy} from './shadow_dom_strategy';
import {ComponentUrlMapper} from './component_url_mapper';
import {ProtoViewFactory} from './proto_view_factory';
import {UrlResolver} from 'angular2/src/services/url_resolver';
import {TemplateLoader} from 'angular2/src/render/dom/compiler/template_loader';
import {DefaultStepFactory} from 'angular2/src/render/dom/compiler/compile_step_factory';
import {DirectDomRenderer} from 'angular2/src/render/dom/direct_dom_renderer';
import * as rc from 'angular2/src/render/dom/compiler/compiler';
import * as renderApi from 'angular2/src/render/api';
/**
@ -49,9 +41,7 @@ export class CompilerCache {
}
// TODO(tbosch): rename this class to Compiler
// and remove the current Compiler when core uses the render views.
export class NewCompiler {
export class Compiler {
_reader: DirectiveMetadataReader;
_compilerCache:CompilerCache;
_compiling:Map<Type, Promise>;
@ -80,19 +70,19 @@ export class NewCompiler {
this._protoViewFactory = protoViewFactory;
}
_bindDirective(directive) {
var meta = this._reader.read(directive);
_bindDirective(directiveTypeOrBinding) {
if (directiveTypeOrBinding instanceof DirectiveBinding) {
return directiveTypeOrBinding;
}
var meta = this._reader.read(directiveTypeOrBinding);
return DirectiveBinding.createFromType(meta.type, meta.annotation);
}
// Create a rootView as if the compiler encountered <rootcmp></rootcmp>.
// Used for bootstrapping.
compileRoot(elementOrSelector, componentBinding:DirectiveBinding):Promise<ProtoView> {
compileRoot(elementOrSelector, componentTypeOrBinding:any):Promise<ProtoView> {
return this._renderer.createRootProtoView(elementOrSelector, 'root').then( (rootRenderPv) => {
return this._compileNestedProtoViews(null, rootRenderPv, [componentBinding], true)
}).then( (rootProtoView) => {
rootProtoView.instantiateInPlace = true;
return rootProtoView;
return this._compileNestedProtoViews(null, rootRenderPv, [this._bindDirective(componentTypeOrBinding)], true);
});
}
@ -266,40 +256,3 @@ export class NewCompiler {
}
}
// TODO(tbosch): delete this class once we use the render views
/**
* The compiler loads and translates the html templates of components into
* nested ProtoViews. To decompose its functionality it uses
* the render compiler.
*
* @publicModule angular2/template
*/
@Injectable()
export class Compiler extends NewCompiler {
constructor(changeDetection:ChangeDetection,
templateLoader:TemplateLoader,
reader: DirectiveMetadataReader,
parser:Parser,
cache:CompilerCache,
shadowDomStrategy: ShadowDomStrategy,
templateResolver: TemplateResolver,
componentUrlMapper: ComponentUrlMapper,
urlResolver: UrlResolver) {
super(
reader,
cache,
templateResolver,
componentUrlMapper,
urlResolver,
new DirectDomRenderer(
new rc.Compiler(
new DefaultStepFactory(parser, shadowDomStrategy.render),
templateLoader
),
null, shadowDomStrategy.render
),
new ProtoViewFactory(changeDetection, shadowDomStrategy)
);
}
}

View File

@ -8,11 +8,8 @@ export class ElementBinder {
protoElementInjector:eiModule.ProtoElementInjector;
componentDirective:DirectiveBinding;
viewportDirective:DirectiveBinding;
textNodeIndices:List<int>;
hasElementPropertyBindings:boolean;
nestedProtoView: viewModule.ProtoView;
events:StringMap;
contentTagSelector:string;
parent:ElementBinder;
index:int;
distanceToParent:int;
@ -32,13 +29,7 @@ export class ElementBinder {
this.distanceToParent = distanceToParent;
// 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;
// updated later in the compilation pipeline
this.contentTagSelector = null;
}
}

View File

@ -6,11 +6,10 @@ import {Parent, Ancestor} from 'angular2/src/core/annotations/visibility';
import {EventEmitter, PropertySetter, Attribute} from 'angular2/src/core/annotations/di';
import * as viewModule from 'angular2/src/core/compiler/view';
import {ViewContainer} from 'angular2/src/core/compiler/view_container';
import {NgElement} from 'angular2/src/core/dom/element';
import {NgElement} from 'angular2/src/core/compiler/ng_element';
import {Directive, onChange, onDestroy, onAllChangesDone} from 'angular2/src/core/annotations/annotations';
import {BindingPropagationConfig} from 'angular2/change_detection';
import * as pclModule from 'angular2/src/core/compiler/private_component_location';
import {setterFactory} from 'angular2/src/render/dom/view/property_setter_factory';
var _MAX_DIRECTIVE_CONSTRUCTION_COUNTER = 10;
@ -268,8 +267,8 @@ export class ProtoElementInjector {
}
}
instantiate(parent:ElementInjector, host:ElementInjector):ElementInjector {
return new ElementInjector(this, parent, host);
instantiate(parent:ElementInjector):ElementInjector {
return new ElementInjector(this, parent);
}
directParent(): ProtoElementInjector {
@ -339,21 +338,12 @@ export class ElementInjector extends TreeNode {
_privateComponent;
_privateComponentBinding:DirectiveBinding;
constructor(proto:ProtoElementInjector, parent:ElementInjector, host:ElementInjector) {
constructor(proto:ProtoElementInjector, parent:ElementInjector) {
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._host = null;
this._preBuiltObjects = null;
this._lightDomAppInjector = null;
this._shadowDomAppInjector = null;
@ -371,6 +361,7 @@ export class ElementInjector extends TreeNode {
}
clearDirectives() {
this._host = null;
this._preBuiltObjects = null;
this._lightDomAppInjector = null;
this._shadowDomAppInjector = null;
@ -406,7 +397,8 @@ export class ElementInjector extends TreeNode {
this._constructionCounter = 0;
}
instantiateDirectives(lightDomAppInjector:Injector, shadowDomAppInjector:Injector, preBuiltObjects:PreBuiltObjects) {
instantiateDirectives(lightDomAppInjector:Injector, host:ElementInjector, shadowDomAppInjector:Injector, preBuiltObjects:PreBuiltObjects) {
this._host = host;
this._checkShadowDomAppInjector(shadowDomAppInjector);
this._preBuiltObjects = preBuiltObjects;
@ -456,10 +448,6 @@ export class ElementInjector extends TreeNode {
return pb !== _undefined && isPresent(pb);
}
forElement(el):boolean {
return this._preBuiltObjects.element.domElement === el;
}
/** Gets the NgElement associated with this ElementInjector */
getNgElement() {
return this._preBuiltObjects.element;
@ -538,6 +526,10 @@ export class ElementInjector extends TreeNode {
return obj;
}
getBoundElementIndex() {
return this._proto.index;
}
_getByDependency(dep:DirectiveDependency, requestor:Key) {
if (isPresent(dep.eventEmitterName)) return this._buildEventEmitter(dep);
if (isPresent(dep.propSetterName)) return this._buildPropSetter(dep);
@ -553,10 +545,12 @@ export class ElementInjector extends TreeNode {
}
_buildPropSetter(dep) {
var ngElement = this._getPreBuiltObjectByKeyId(StaticKeys.instance().ngElementId);
var domElement = ngElement.domElement;
var setter = setterFactory(dep.propSetterName);
return function(v) { setter(domElement, v) };
var view = this._getPreBuiltObjectByKeyId(StaticKeys.instance().viewId);
var renderer = view.proto.renderer;
var index = this._proto.index;
return function(v) {
renderer.setElementProperty(view.render, index, dep.propSetterName, v);
};
}
_buildAttribute(dep): string {
@ -582,7 +576,6 @@ export class ElementInjector extends TreeNode {
*/
_getByKey(key:Key, depth:number, optional:boolean, requestor:Key) {
var ei = this;
if (! this._shouldIncludeSelf(depth)) {
depth -= ei._proto.distanceToParent;
ei = ei._parent;
@ -631,7 +624,7 @@ export class ElementInjector extends TreeNode {
if (keyId === staticKeys.bindingPropagationConfigId) return this._preBuiltObjects.bindingPropagationConfig;
if (keyId === staticKeys.privateComponentLocationId) {
return new pclModule.PrivateComponentLocation(this, this._preBuiltObjects.element, this._preBuiltObjects.view);
return new pclModule.PrivateComponentLocation(this, this._preBuiltObjects.view);
}
//TODO add other objects as needed

View File

@ -0,0 +1,34 @@
import {DOM} from 'angular2/src/dom/dom_adapter';
import {normalizeBlank} from 'angular2/src/facade/lang';
import * as viewModule from '../compiler/view';
import {DirectDomViewRef} from 'angular2/src/render/dom/direct_dom_renderer';
/**
* Allows direct access to the underlying DOM element.
*
* Attention: NgElement will be replaced by a different concept
* for accessing an element in a way that is compatible with the render layer.
*
* @publicModule angular2/angular2
*/
export class NgElement {
_view:viewModule.View;
_boundElementIndex:number;
constructor(view, boundElementIndex) {
this._view = view;
this._boundElementIndex = boundElementIndex;
}
// TODO(tbosch): Here we expose the real DOM element.
// We need a more general way to read/write to the DOM element
// via a proper abstraction in the render layer
get domElement() {
var domViewRef:DirectDomViewRef = this._view.render;
return domViewRef.delegate.boundElements[this._boundElementIndex];
}
getAttribute(name:string) {
return normalizeBlank(DOM.getAttribute(this.domElement, name));
}
}

View File

@ -1,7 +1,6 @@
import {Compiler} from './compiler';
import {ShadowDomStrategy} from './shadow_dom_strategy';
import {ViewFactory} from './view_factory';
import {Injectable} from 'angular2/di';
import {EventManager} from 'angular2/src/render/dom/events/event_manager';
import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader';
import {Component} from 'angular2/src/core/annotations/annotations';
import {PrivateComponentLocation} from './private_component_location';
@ -11,17 +10,14 @@ import {Type, stringify, BaseException} from 'angular2/src/facade/lang';
@Injectable()
export class PrivateComponentLoader {
compiler:Compiler;
shadowDomStrategy:ShadowDomStrategy;
eventManager:EventManager;
directiveMetadataReader:DirectiveMetadataReader;
viewFactory:ViewFactory;
constructor(compiler:Compiler, shadowDomStrategy:ShadowDomStrategy,
eventManager:EventManager, directiveMetadataReader:DirectiveMetadataReader) {
constructor(compiler:Compiler, directiveMetadataReader:DirectiveMetadataReader, viewFactory:ViewFactory) {
this.compiler = compiler;
this.shadowDomStrategy = shadowDomStrategy;
this.eventManager = eventManager;
this.directiveMetadataReader = directiveMetadataReader;
this.viewFactory = viewFactory;
}
load(type:Type, location:PrivateComponentLocation) {
@ -33,10 +29,10 @@ export class PrivateComponentLoader {
return this.compiler.compile(type).then((componentProtoView) => {
location.createComponent(
this.viewFactory,
type, annotation,
componentProtoView,
this.eventManager,
this.shadowDomStrategy);
componentProtoView
);
});
}
}

View File

@ -1,33 +1,28 @@
import {Directive} from 'angular2/src/core/annotations/annotations'
import {NgElement} from 'angular2/src/core/dom/element';
import * as viewModule from './view';
import * as eiModule from './element_injector';
import {ShadowDomStrategy} from './shadow_dom_strategy';
import {EventManager} from 'angular2/src/render/dom/events/event_manager';
import {ListWrapper} from 'angular2/src/facade/collection';
import {Type} from 'angular2/src/facade/lang';
import * as vfModule from './view_factory';
export class PrivateComponentLocation {
_elementInjector:eiModule.ElementInjector;
_elt:NgElement;
_view:viewModule.View;
constructor(elementInjector:eiModule.ElementInjector, elt:NgElement, view:viewModule.View){
constructor(elementInjector:eiModule.ElementInjector, view:viewModule.View){
this._elementInjector = elementInjector;
this._elt = elt;
this._view = view;
}
createComponent(type:Type, annotation:Directive, componentProtoView:viewModule.ProtoView,
eventManager:EventManager, shadowDomStrategy:ShadowDomStrategy) {
createComponent(viewFactory: vfModule.ViewFactory, type:Type, annotation:Directive, componentProtoView:viewModule.ProtoView) {
var context = this._elementInjector.createPrivateComponent(type, annotation);
var view = componentProtoView.instantiate(this._elementInjector, eventManager);
view.hydrate(this._elementInjector.getShadowDomAppInjector(), this._elementInjector, null, context, null);
shadowDomStrategy.attachTemplate(this._elt.domElement, view);
var view = viewFactory.getView(componentProtoView);
view.hydrate(this._elementInjector.getShadowDomAppInjector(), this._elementInjector, context, null);
this._view.proto.renderer.setDynamicComponentView(
this._view.render, this._elementInjector.getBoundElementIndex(), view.render
);
ListWrapper.push(this._view.componentChildViews, view);
this._view.changeDetector.addChild(view.changeDetector);
}

View File

@ -1,23 +1,23 @@
import {Injectable} from 'angular2/di';
import {List, ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
import {isPresent, isBlank} from 'angular2/src/facade/lang';
import {reflector} from 'angular2/src/reflection/reflection';
import {ChangeDetection} from 'angular2/change_detection';
import {ShadowDomStrategy} from './shadow_dom_strategy';
import {Component, Viewport, DynamicComponent} from '../annotations/annotations';
import * as renderApi from 'angular2/src/render/api';
import {DirectDomProtoViewRef} from 'angular2/src/render/dom/direct_dom_renderer';
import {ProtoView} from './view';
import {ProtoElementInjector, DirectiveBinding} from './element_injector';
@Injectable()
export class ProtoViewFactory {
_changeDetection:ChangeDetection;
_shadowDomStrategy:ShadowDomStrategy;
_renderer:renderApi.Renderer;
constructor(changeDetection, shadowDomStrategy) {
constructor(changeDetection:ChangeDetection, renderer:renderApi.Renderer) {
this._changeDetection = changeDetection;
this._shadowDomStrategy = shadowDomStrategy;
this._renderer = renderer;
}
createProtoView(componentBinding:DirectiveBinding, renderProtoView: renderApi.ProtoView, directives:List<DirectiveBinding>):ProtoView {
@ -30,13 +30,10 @@ export class ProtoViewFactory {
'dummy', componentAnnotation.changeDetection
);
}
var domProtoView = this._getDomProtoView(renderProtoView.render);
var protoView = new ProtoView(renderProtoView.render, domProtoView.element, protoChangeDetector,
this._shadowDomStrategy, null);
var protoView = new ProtoView(this._renderer, renderProtoView.render, protoChangeDetector);
for (var i=0; i<renderProtoView.elementBinders.length; i++) {
var renderElementBinder = renderProtoView.elementBinders[i];
var domElementBinder = domProtoView.elementBinders[i];
var sortedDirectives = new SortedDirectives(renderElementBinder.directives, directives);
var parentPeiWithDistance = this._findParentProtoElementInjectorWithDistance(
i, protoView.elementBinders, renderProtoView.elementBinders
@ -46,7 +43,7 @@ export class ProtoViewFactory {
sortedDirectives, renderElementBinder
);
this._createElementBinder(
protoView, renderElementBinder, domElementBinder, protoElementInjector, sortedDirectives
protoView, renderElementBinder, protoElementInjector, sortedDirectives
);
this._createDirectiveBinders(protoView, sortedDirectives);
}
@ -56,11 +53,6 @@ export class ProtoViewFactory {
return protoView;
}
// This method is needed to make DartAnalyzer happy
_getDomProtoView(protoViewRef: DirectDomProtoViewRef) {
return protoViewRef.delegate;
}
_findParentProtoElementInjectorWithDistance(binderIndex, elementBinders, renderElementBinders) {
var distance = 0;
do {
@ -105,7 +97,7 @@ export class ProtoViewFactory {
return protoElementInjector;
}
_createElementBinder(protoView, renderElementBinder, domElementBinder, protoElementInjector, sortedDirectives) {
_createElementBinder(protoView, renderElementBinder, protoElementInjector, sortedDirectives) {
var parent = null;
if (renderElementBinder.parentIndex !== -1) {
parent = protoView.elementBinders[renderElementBinder.parentIndex];
@ -117,14 +109,13 @@ export class ProtoViewFactory {
sortedDirectives.componentDirective,
sortedDirectives.viewportDirective
);
elBinder.contentTagSelector = domElementBinder.contentTagSelector;
// text nodes
for (var i=0; i<renderElementBinder.textBindings.length; i++) {
protoView.bindTextNode(domElementBinder.textNodeIndices[i], renderElementBinder.textBindings[i].ast);
protoView.bindTextNode(renderElementBinder.textBindings[i].ast);
}
// element properties
MapWrapper.forEach(renderElementBinder.propertyBindings, (astWithSource, propertyName) => {
protoView.bindElementProperty(astWithSource.ast, propertyName, MapWrapper.get(domElementBinder.propertySetters, propertyName));
protoView.bindElementProperty(astWithSource.ast, propertyName);
});
// events
MapWrapper.forEach(renderElementBinder.eventBindings, (astWithSource, eventName) => {

View File

@ -1,88 +0,0 @@
import * as ldModule from './light_dom';
import {Inject, Injectable} from 'angular2/di';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {isPresent} from 'angular2/src/facade/lang';
import {List, ListWrapper} from 'angular2/src/facade/collection';
class ContentStrategy {
nodes:List;
insert(nodes:List){}
}
/**
* 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.
*/
@Injectable()
class RenderedContent extends ContentStrategy {
beginScript;
endScript;
constructor(contentEl) {
super();
this.beginScript = contentEl;
this.endScript = DOM.nextSibling(this.beginScript);
this.nodes = [];
}
// Inserts the nodes in between the start and end scripts.
// Previous content is removed.
insert(nodes:List) {
this.nodes = nodes;
DOM.insertAllBefore(this.endScript, nodes);
this._removeNodesUntil(ListWrapper.isEmpty(nodes) ? this.endScript : nodes[0]);
}
_removeNodesUntil(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:ldModule.LightDom;
constructor(destinationLightDom:ldModule.LightDom) {
super();
this.destinationLightDom = destinationLightDom;
this.nodes = [];
}
insert(nodes:List) {
this.nodes = nodes;
this.destinationLightDom.redistribute();
}
}
export class Content {
select:string;
_strategy:ContentStrategy;
contentStartElement;
constructor(destinationLightDom:ldModule.LightDom, contentStartEl, selector:string) {
this.select = selector;
this.contentStartElement = contentStartEl;
this._strategy = isPresent(destinationLightDom) ?
new IntermediateContent(destinationLightDom) :
new RenderedContent(contentStartEl);
}
nodes():List {
return this._strategy.nodes;
}
insert(nodes:List) {
this._strategy.insert(nodes);
}
}

View File

@ -1,140 +0,0 @@
import {DOM} from 'angular2/src/dom/dom_adapter';
import {List, ListWrapper} from 'angular2/src/facade/collection';
import {isBlank, isPresent} from 'angular2/src/facade/lang';
import * as viewModule from '../view';
import {Content} from './content_tag';
export class DestinationLightDom {}
class _Root {
node;
viewContainer;
content;
constructor(node, viewContainer, content) {
this.node = node;
this.viewContainer = viewContainer;
this.content = content;
}
}
// TODO: LightDom should implement DestinationLightDom
// once interfaces are supported
export class LightDom {
// The light DOM of the element is enclosed inside the lightDomView
lightDomView:viewModule.View;
// The shadow DOM
shadowDomView:viewModule.View;
// The nodes of the light DOM
nodes:List;
roots:List<_Root>;
constructor(lightDomView:viewModule.View, shadowDomView:viewModule.View, 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: viewModule.View, acc:List<Content>):List<Content> {
var contentTags = view.contentTags;
var vcs = view.viewContainers;
for (var i=0; i<vcs.length; i++) {
var vc = vcs[i];
var contentTag = contentTags[i];
if (isPresent(contentTag)) {
ListWrapper.push(acc, contentTag);
}
if (isPresent(vc)) {
ListWrapper.forEach(vc.contentTagContainers(), (view) => {
this._collectAllContentTags(view, acc);
});
}
}
return acc;
}
// Collects the nodes of the light DOM by merging:
// - nodes from enclosed ViewContainers,
// - 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];
if (isPresent(root.viewContainer)) {
res = ListWrapper.concat(res, root.viewContainer.nodes());
} else if (isPresent(root.content)) {
res = ListWrapper.concat(res, root.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 viewContainers = this.lightDomView.viewContainers;
var contentTags = this.lightDomView.contentTags;
this.roots = ListWrapper.map(this.nodes, (n) => {
var foundVc = null;
var foundContentTag = null;
for (var i=0; i<viewContainers.length; i++) {
var vc = viewContainers[i];
var contentTag = contentTags[i];
if (isPresent(vc) && vc.templateElement === n) {
foundVc = vc;
}
if (isPresent(contentTag) && contentTag.contentStartElement === n) {
foundContentTag = contentTag;
}
}
return new _Root(n, foundVc, foundContentTag);
});
return this.roots;
}
}
// Projects the light DOM into the shadow DOM
function redistributeNodes(contents:List<Content>, nodes:List) {
for (var i = 0; i < contents.length; ++i) {
var content = contents[i];
var select = content.select;
var matchSelector = (n) => DOM.elementMatches(n, select);
// Empty selector is identical to <content/>
if (select.length === 0) {
content.insert(nodes);
ListWrapper.clear(nodes);
} else {
var matchingNodes = ListWrapper.filter(nodes, matchSelector);
content.insert(matchingNodes);
ListWrapper.removeAll(nodes, matchingNodes);
}
}
}

View File

@ -1,114 +0,0 @@
import {Injectable} from 'angular2/di';
import {stringify} from 'angular2/src/facade/lang';
import {DOM} from 'angular2/src/dom/dom_adapter';
import * as viewModule from './view';
import {LightDom} from './shadow_dom_emulation/light_dom';
import {StyleInliner} from 'angular2/src/render/dom/shadow_dom/style_inliner';
import {StyleUrlResolver} from 'angular2/src/render/dom/shadow_dom/style_url_resolver';
// temporal import while we migrated the views over
import * as sds from 'angular2/src/render/dom/shadow_dom/shadow_dom_strategy';
import * as nsds from 'angular2/src/render/dom/shadow_dom/native_shadow_dom_strategy';
import * as eusds from 'angular2/src/render/dom/shadow_dom/emulated_unscoped_shadow_dom_strategy';
import * as essds from 'angular2/src/render/dom/shadow_dom/emulated_scoped_shadow_dom_strategy';
/**
* @publicModule angular2/template
*/
export class ShadowDomStrategy {
render: sds.ShadowDomStrategy;
attachTemplate(el, view:viewModule.View) {}
constructLightDom(lightDomView:viewModule.View, shadowDomView:viewModule.View, el): LightDom { return null; }
shimAppElement(componentType, insertionElement) {
this.render.processElement(null, stringify(componentType), insertionElement);
}
}
/**
* This strategy emulates the Shadow DOM for the templates, styles **excluded**:
* - components templates are added as children of their component element,
* - styles are moved from the templates to the styleHost (i.e. the document head).
*
* Notes:
* - styles are **not** scoped to their component and will apply to the whole document,
* - you can **not** use shadow DOM specific selectors in the styles
*
* @publicModule angular2/template
*/
@Injectable()
export class EmulatedUnscopedShadowDomStrategy extends ShadowDomStrategy {
constructor(styleUrlResolver: StyleUrlResolver, styleHost) {
super();
this.render = new eusds.EmulatedUnscopedShadowDomStrategy(styleUrlResolver, styleHost);
}
attachTemplate(el, view:viewModule.View) {
DOM.clearNodes(el);
_moveViewNodesIntoParent(el, view);
}
constructLightDom(lightDomView:viewModule.View, shadowDomView:viewModule.View, el): LightDom {
return new LightDom(lightDomView, shadowDomView, el);
}
}
/**
* This strategy emulates the Shadow DOM for the templates, styles **included**:
* - components templates are added as children of their component element,
* - both the template and the styles are modified so that styles are scoped to the component
* they belong to,
* - styles are moved from the templates to the styleHost (i.e. the document head).
*
* Notes:
* - styles are scoped to their component and will apply only to it,
* - a common subset of shadow DOM selectors are supported,
* - see `ShadowCss` for more information and limitations.
*
* @publicModule angular2/template
*/
@Injectable()
export class EmulatedScopedShadowDomStrategy extends ShadowDomStrategy {
constructor(styleInliner: StyleInliner, styleUrlResolver: StyleUrlResolver, styleHost) {
super();
this.render = new essds.EmulatedScopedShadowDomStrategy(styleInliner, styleUrlResolver, styleHost);
}
attachTemplate(el, view:viewModule.View) {
DOM.clearNodes(el);
_moveViewNodesIntoParent(el, view);
}
constructLightDom(lightDomView:viewModule.View, shadowDomView:viewModule.View, el): LightDom {
return new LightDom(lightDomView, shadowDomView, el);
}
}
/**
* This strategies uses the native Shadow DOM support.
*
* The templates for the component are inserted in a Shadow Root created on the component element.
* Hence they are strictly isolated.
*/
@Injectable()
export class NativeShadowDomStrategy extends ShadowDomStrategy {
constructor(styleUrlResolver: StyleUrlResolver) {
super();
this.render = new nsds.NativeShadowDomStrategy(styleUrlResolver);
}
attachTemplate(el, view:viewModule.View){
_moveViewNodesIntoParent(DOM.createShadowRoot(el), view);
}
}
function _moveViewNodesIntoParent(parent, view) {
for (var i = 0; i < view.nodes.length; ++i) {
DOM.appendChild(parent, view.nodes[i]);
}
}

View File

@ -1,5 +1,3 @@
import {DOM} from 'angular2/src/dom/dom_adapter';
import {Promise} from 'angular2/src/facade/async';
import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src/facade/collection';
import {AST, Locals, ChangeDispatcher, ProtoChangeDetector, ChangeDetector,
ChangeRecord, BindingRecord, BindingPropagationConfig, uninitialized} from 'angular2/change_detection';
@ -9,76 +7,51 @@ import {ElementBinder} from './element_binder';
import {SetterFn} from 'angular2/src/reflection/types';
import {IMPLEMENTS, int, isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
import {Injector} from 'angular2/di';
import {NgElement} from 'angular2/src/core/dom/element';
import {ViewContainer} from './view_container';
import {LightDom} from './shadow_dom_emulation/light_dom';
import {Content} from './shadow_dom_emulation/content_tag';
import {ShadowDomStrategy} from './shadow_dom_strategy';
import {ViewPool} from './view_pool';
import {EventManager} from 'angular2/src/render/dom/events/event_manager';
import * as renderApi from 'angular2/src/render/api';
const NG_BINDING_CLASS = 'ng-binding';
const NG_BINDING_CLASS_SELECTOR = '.ng-binding';
// TODO(rado): make this configurable/smarter.
var VIEW_POOL_CAPACITY = 10000;
var VIEW_POOL_PREFILL = 0;
/**
* Const of making objects: http://jsperf.com/instantiate-size-of-object
*
* @publicModule angular2/template
*/
@IMPLEMENTS(ChangeDispatcher)
// TODO(tbosch): this is not supported in dart2js (no '.' is allowed)
// @IMPLEMENTS(renderApi.EventDispatcher)
export class View {
render:renderApi.ViewRef;
/// This list matches the _nodes list. It is sparse, since only Elements have ElementInjector
rootElementInjectors:List<ElementInjector>;
elementInjectors:List<ElementInjector>;
bindElements:List;
textNodes:List;
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;
componentChildViews: List<View>;
viewContainers: List<ViewContainer>;
contentTags: List<Content>;
preBuiltObjects: List<PreBuiltObjects>;
lightDoms: List<LightDom>;
proto: ProtoView;
context: any;
locals:Locals;
constructor(proto:ProtoView, nodes:List, protoLocals:Map) {
constructor(proto:ProtoView, protoLocals:Map) {
this.render = null;
this.proto = proto;
this.nodes = nodes;
this.changeDetector = null;
this.elementInjectors = null;
this.rootElementInjectors = null;
this.textNodes = null;
this.bindElements = null;
this.componentChildViews = null;
this.viewContainers = null;
this.contentTags = null;
this.preBuiltObjects = null;
this.lightDoms = null;
this.context = null;
this.locals = new Locals(null, MapWrapper.clone(protoLocals)); //TODO optimize this
}
init(changeDetector:ChangeDetector, elementInjectors:List, rootElementInjectors:List, textNodes: List, bindElements:List,
viewContainers:List, contentTags:List, preBuiltObjects:List, componentChildViews:List, lightDoms:List<LightDom>) {
init(changeDetector:ChangeDetector, elementInjectors:List, rootElementInjectors:List,
viewContainers:List, preBuiltObjects:List, componentChildViews:List) {
this.changeDetector = changeDetector;
this.elementInjectors = elementInjectors;
this.rootElementInjectors = rootElementInjectors;
this.textNodes = textNodes;
this.bindElements = bindElements;
this.viewContainers = viewContainers;
this.contentTags = contentTags;
this.preBuiltObjects = preBuiltObjects;
this.componentChildViews = componentChildViews;
this.lightDoms = lightDoms;
}
setLocal(contextName: string, value) {
@ -124,16 +97,33 @@ export class View {
* A call to hydrate/dehydrate does not attach/detach the view from the view
* tree.
*/
hydrate(appInjector: Injector, hostElementInjector: ElementInjector, hostLightDom: LightDom,
hydrate(appInjector: Injector, hostElementInjector: ElementInjector,
context: Object, locals:Locals) {
var renderComponentViewRefs = this.proto.renderer.createView(this.proto.render);
this.internalHydrateRecurse(renderComponentViewRefs, 0, appInjector, hostElementInjector, context, locals);
}
dehydrate() {
var render = this.render;
this.internalDehydrateRecurse();
this.proto.renderer.destroyView(render);
}
internalHydrateRecurse(
renderComponentViewRefs:List<renderApi.ViewRef>,
renderComponentIndex:number,
appInjector: Injector, hostElementInjector: ElementInjector,
context: Object, locals:Locals):number {
if (this.hydrated()) throw new BaseException('The view is already hydrated.');
this.render = renderComponentViewRefs[renderComponentIndex++];
this._hydrateContext(context, locals);
// viewContainers
for (var i = 0; i < this.viewContainers.length; i++) {
var vc = this.viewContainers[i];
if (isPresent(vc)) {
vc.hydrate(appInjector, hostElementInjector, hostLightDom);
vc.internalHydrateRecurse(new renderApi.ViewContainerRef(this.render, i), appInjector, hostElementInjector);
}
}
@ -158,7 +148,7 @@ export class View {
// elementInjectors
var elementInjector = this.elementInjectors[i];
if (isPresent(elementInjector)) {
elementInjector.instantiateDirectives(appInjector, shadowDomAppInjector, this.preBuiltObjects[i]);
elementInjector.instantiateDirectives(appInjector, hostElementInjector, shadowDomAppInjector, this.preBuiltObjects[i]);
// The exporting of $implicit is a special case. Since multiple elements will all export
// the different values as $implicit, directly assign $implicit bindings to the variable
@ -167,30 +157,32 @@ export class View {
if (elementInjector.isExportingComponent()) {
this.locals.set(exportImplicitName, elementInjector.getComponent());
} else if (elementInjector.isExportingElement()) {
this.locals.set(exportImplicitName, elementInjector.getNgElement().domElement);
this.locals.set(exportImplicitName, elementInjector.getNgElement());
}
}
if (isPresent(binders[i].nestedProtoView) && isPresent(componentDirective)) {
this.componentChildViews[componentChildViewIndex++].hydrate(shadowDomAppInjector,
elementInjector, this.lightDoms[i], elementInjector.getComponent(), null);
}
}
for (var i = 0; i < this.lightDoms.length; ++i) {
var lightDom = this.lightDoms[i];
if (isPresent(lightDom)) {
lightDom.redistribute();
renderComponentIndex = this.componentChildViews[componentChildViewIndex].internalHydrateRecurse(
renderComponentViewRefs,
renderComponentIndex,
shadowDomAppInjector,
elementInjector,
elementInjector.getComponent(),
null
);
componentChildViewIndex++;
}
}
this.proto.renderer.setEventDispatcher(this.render, this);
return renderComponentIndex;
}
dehydrate() {
internalDehydrateRecurse() {
// Note: preserve the opposite order of the hydration process.
// componentChildViews
for (var i = 0; i < this.componentChildViews.length; i++) {
this.componentChildViews[i].dehydrate();
this.componentChildViews[i].internalDehydrateRecurse();
}
// elementInjectors
@ -205,11 +197,13 @@ export class View {
for (var i = 0; i < this.viewContainers.length; i++) {
var vc = this.viewContainers[i];
if (isPresent(vc)) {
vc.dehydrate();
vc.internalDehydrateRecurse();
}
}
}
this.render = null;
this._dehydrateContext();
}
@ -223,11 +217,9 @@ export class View {
* @param {int} binderIndex
*/
triggerEventHandlers(eventName: string, eventObj, binderIndex: int) {
var handlers = this.proto.eventHandlers[binderIndex];
if (isBlank(handlers)) return;
var handler = StringMapWrapper.get(handlers, eventName);
if (isBlank(handler)) return;
handler(eventObj, this);
var locals = MapWrapper.create();
MapWrapper.set(locals, '$event', eventObj);
this.dispatchEvent(binderIndex, eventName, locals);
}
onAllChangesDone(directiveMemento:DirectiveMemento) {
@ -245,15 +237,39 @@ export class View {
if (memento instanceof DirectiveBindingMemento) {
var directiveMemento:DirectiveBindingMemento = memento;
directiveMemento.invoke(currentValue, this.elementInjectors);
} else if (memento instanceof ElementBindingMemento) {
var elementMemento:ElementBindingMemento = memento;
elementMemento.invoke(currentValue, this.bindElements);
this.proto.renderer.setElementProperty(
this.render, elementMemento.elementIndex, elementMemento.propertyName, currentValue
);
} else {
// we know it refers to _textNodes.
var textNodeIndex:number = memento;
DOM.setText(this.textNodes[textNodeIndex], currentValue);
this.proto.renderer.setText(this.render, textNodeIndex, currentValue);
}
}
// implementation of EventDispatcher#dispatchEvent
dispatchEvent(
elementIndex:number, eventName:string, locals:Map<string, any>
):void {
// 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 (this.hydrated()) {
var elBinder = this.proto.elementBinders[elementIndex];
if (isBlank(elBinder.events)) return;
var eventMap = elBinder.events[eventName];
if (isBlank(eventName)) return;
MapWrapper.forEach(eventMap, (expr, directiveIndex) => {
var context;
if (directiveIndex === -1) {
context = this.context;
} else {
context = this.elementInjectors[elementIndex].getDirectiveAtIndex(directiveIndex);
}
expr.eval(context, new Locals(this.locals, locals));
});
}
}
}
@ -263,21 +279,11 @@ export class View {
* @publicModule angular2/template
*/
export class ProtoView {
element;
elementBinders:List<ElementBinder>;
protoChangeDetector:ProtoChangeDetector;
variableBindings: Map;
protoLocals:Map;
textNodesWithBindingCount:int;
elementsWithBindingCount:int;
instantiateInPlace:boolean;
rootBindingOffset:int;
isTemplateElement:boolean;
shadowDomStrategy: ShadowDomStrategy;
_viewPool: ViewPool;
stylePromises: List<Promise>;
// List<Map<eventName, handler>>, indexed by binder index
eventHandlers:List;
bindingRecords:List;
parentProtoView:ProtoView;
_variableBindings:List;
@ -285,58 +291,36 @@ export class ProtoView {
_directiveMementosMap:Map;
_directiveMementos:List;
render:renderApi.ProtoViewRef;
renderer:renderApi.Renderer;
constructor(
renderer:renderApi.Renderer,
render:renderApi.ProtoViewRef,
template,
protoChangeDetector:ProtoChangeDetector,
shadowDomStrategy:ShadowDomStrategy, parentProtoView:ProtoView = null) {
protoChangeDetector:ProtoChangeDetector) {
this.renderer = renderer;
this.render = render;
this.element = template;
this.elementBinders = [];
this.variableBindings = MapWrapper.create();
this.protoLocals = MapWrapper.create();
this.protoChangeDetector = protoChangeDetector;
this.parentProtoView = parentProtoView;
this.parentProtoView = null;
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 = DOM.isTemplateElement(this.element);
this.shadowDomStrategy = shadowDomStrategy;
this._viewPool = new ViewPool(VIEW_POOL_CAPACITY);
this.stylePromises = [];
this.eventHandlers = [];
this.bindingRecords = [];
this._directiveMementosMap = MapWrapper.create();
this._variableBindings = null;
this._directiveMementos = null;
}
// TODO(rado): hostElementInjector should be moved to hydrate phase.
instantiate(hostElementInjector: ElementInjector, eventManager: EventManager):View {
if (this._viewPool.length() == 0) this._preFillPool(hostElementInjector, eventManager);
var view = this._viewPool.pop();
return isPresent(view) ? view : this._instantiate(hostElementInjector, eventManager);
}
_preFillPool(hostElementInjector: ElementInjector, eventManager: EventManager) {
for (var i = 0; i < VIEW_POOL_PREFILL; i++) {
this._viewPool.push(this._instantiate(hostElementInjector, eventManager));
}
}
//TODO: Tobias or Victor. Moving it into the constructor.
// this work should be done the constructor of ProtoView once we separate
// ProtoView and ProtoViewBuilder
_getVariableBindings() {
getVariableBindings() {
if (isPresent(this._variableBindings)) {
return this._variableBindings;
}
this._variableBindings = isPresent(this.parentProtoView) ?
ListWrapper.clone(this.parentProtoView._getVariableBindings()) : [];
ListWrapper.clone(this.parentProtoView.getVariableBindings()) : [];
MapWrapper.forEach(this.protoLocals, (v, local) => {
ListWrapper.push(this._variableBindings, local);
@ -348,7 +332,7 @@ export class ProtoView {
//TODO: Tobias or Victor. Moving it into the constructor.
// this work should be done the constructor of ProtoView once we separate
// ProtoView and ProtoViewBuilder
_getDirectiveMementos() {
getDirectiveMementos() {
if (isPresent(this._directiveMementos)) {
return this._directiveMementos;
}
@ -367,185 +351,6 @@ export class ProtoView {
return this._directiveMementos;
}
_instantiate(hostElementInjector: ElementInjector, eventManager: EventManager): View {
var rootElementClone = this.instantiateInPlace ? this.element : DOM.importIntoDoc(this.element);
var elementsWithBindingsDynamic;
if (this.isTemplateElement) {
elementsWithBindingsDynamic = DOM.querySelectorAll(DOM.content(rootElementClone), NG_BINDING_CLASS_SELECTOR);
} else {
elementsWithBindingsDynamic= DOM.getElementsByClassName(rootElementClone, NG_BINDING_CLASS);
}
var elementsWithBindings = ListWrapper.createFixedSize(elementsWithBindingsDynamic.length);
for (var binderIdx = 0; binderIdx < elementsWithBindingsDynamic.length; ++binderIdx) {
elementsWithBindings[binderIdx] = elementsWithBindingsDynamic[binderIdx];
}
var viewNodes;
if (this.isTemplateElement) {
var childNode = DOM.firstChild(DOM.content(rootElementClone));
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.protoLocals);
var changeDetector = this.protoChangeDetector.instantiate(view, this.bindingRecords,
this._getVariableBindings(), this._getDirectiveMementos());
var binders = this.elementBinders;
var elementInjectors = ListWrapper.createFixedSize(binders.length);
var eventHandlers = ListWrapper.createFixedSize(binders.length);
var rootElementInjectors = [];
var textNodes = [];
var elementsWithPropertyBindings = [];
var preBuiltObjects = ListWrapper.createFixedSize(binders.length);
var viewContainers = ListWrapper.createFixedSize(binders.length);
var contentTags = ListWrapper.createFixedSize(binders.length);
var componentChildViews = [];
var lightDoms = ListWrapper.createFixedSize(binders.length);
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
var binder = binders[binderIdx];
var element;
if (binderIdx === 0 && this.rootBindingOffset === 1) {
element = rootElementClone;
} else {
element = elementsWithBindings[binderIdx - 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);
} else {
elementInjector = protoElementInjector.instantiate(null, hostElementInjector);
ListWrapper.push(rootElementInjectors, elementInjector);
}
}
elementInjectors[binderIdx] = 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.nestedProtoView) && isPresent(binder.componentDirective)) {
var strategy = this.shadowDomStrategy;
var childView = binder.nestedProtoView.instantiate(elementInjector, eventManager);
changeDetector.addChild(childView.changeDetector);
lightDom = strategy.constructLightDom(view, childView, element);
strategy.attachTemplate(element, childView);
bindingPropagationConfig = new BindingPropagationConfig(childView.changeDetector);
ListWrapper.push(componentChildViews, childView);
}
lightDoms[binderIdx] = lightDom;
var destLightDom = null;
if (isPresent(binder.parent) && binder.distanceToParent === 1) {
destLightDom = lightDoms[binder.parent.index];
}
// viewContainers
var viewContainer = null;
if (isPresent(binder.viewportDirective)) {
viewContainer = new ViewContainer(view, element, binder.nestedProtoView, elementInjector,
eventManager, destLightDom);
}
viewContainers[binderIdx] = viewContainer;
// contentTags
var contentTag = null;
if (isPresent(binder.contentTagSelector)) {
contentTag = new Content(destLightDom, element, binder.contentTagSelector);
}
contentTags[binderIdx] = contentTag;
// preBuiltObjects
if (isPresent(elementInjector)) {
preBuiltObjects[binderIdx] = new PreBuiltObjects(view, new NgElement(element), viewContainer,
bindingPropagationConfig);
}
// events
if (isPresent(binder.events)) {
eventHandlers[binderIdx] = StringMapWrapper.create();
StringMapWrapper.forEach(binder.events, (eventMap, eventName) => {
var handler = ProtoView.buildEventHandler(eventMap, binderIdx);
StringMapWrapper.set(eventHandlers[binderIdx], eventName, handler);
if (isBlank(elementInjector) || !elementInjector.hasEventEmitter(eventName)) {
eventManager.addEventListener(element, eventName,
(event) => { handler(event, view); });
}
});
}
}
this.eventHandlers = eventHandlers;
view.init(changeDetector, elementInjectors, rootElementInjectors, textNodes, elementsWithPropertyBindings,
viewContainers, contentTags, preBuiltObjects, componentChildViews, lightDoms);
return view;
}
returnToPool(view: View) {
this._viewPool.push(view);
}
/**
* Creates an event handler.
*
* @param {Map} eventMap Map directiveIndexes to expressions
* @param {int} injectorIdx
* @returns {Function}
*/
static buildEventHandler(eventMap: Map, injectorIdx: int) {
var locals = MapWrapper.create();
return (event, view) => {
// 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);
MapWrapper.forEach(eventMap, (expr, directiveIndex) => {
var context;
if (directiveIndex === -1) {
context = view.context;
} else {
context = view.elementInjectors[injectorIdx].getDirectiveAtIndex(directiveIndex);
}
expr.eval(context, new Locals(view.locals, locals));
});
}
}
}
bindVariable(contextName:string, templateName:string) {
MapWrapper.set(this.variableBindings, contextName, templateName);
MapWrapper.set(this.protoLocals, templateName, null);
@ -562,12 +367,7 @@ export class ProtoView {
/**
* 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);
bindTextNode(expression:AST) {
var memento = this.textNodesWithBindingCount++;
ListWrapper.push(this.bindingRecords, new BindingRecord(expression, memento, null));
}
@ -575,13 +375,8 @@ export class ProtoView {
/**
* 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 ElementBindingMemento(this.elementsWithBindingCount-1, setter);
bindElementProperty(expression:AST, setterName:string) {
var memento = new ElementBindingMemento(this.elementBinders.length-1, setterName);
ListWrapper.push(this.bindingRecords, new BindingRecord(expression, memento, null));
}
@ -651,16 +446,12 @@ export class ProtoView {
/**
*/
export class ElementBindingMemento {
_elementIndex:int;
_setter:SetterFn;
constructor(elementIndex:int, setter:SetterFn) {
this._elementIndex = elementIndex;
this._setter = setter;
}
elementIndex:int;
propertyName:string;
invoke(currentValue:any, bindElements:List) {
var element = bindElements[this._elementIndex];
this._setter(element, currentValue);
constructor(elementIndex:int, propertyName:string) {
this.elementIndex = elementIndex;
this.propertyName = propertyName;
}
}

View File

@ -1,59 +1,64 @@
import * as viewModule from './view';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {ListWrapper, MapWrapper, List} from 'angular2/src/facade/collection';
import {BaseException} from 'angular2/src/facade/lang';
import {Injector} from 'angular2/di';
import * as eiModule from 'angular2/src/core/compiler/element_injector';
import {isPresent, isBlank} from 'angular2/src/facade/lang';
import {EventManager} from 'angular2/src/render/dom/events/event_manager';
import {LightDom} from './shadow_dom_emulation/light_dom';
import * as renderApi from 'angular2/src/render/api';
import * as viewModule from './view';
import * as vfModule from './view_factory';
/**
* @publicModule angular2/template
*/
export class ViewContainer {
render:renderApi.ViewContainerRef;
viewFactory: vfModule.ViewFactory;
parentView: viewModule.View;
templateElement;
defaultProtoView: viewModule.ProtoView;
_views: List<viewModule.View>;
_lightDom: LightDom;
_eventManager: EventManager;
elementInjector: eiModule.ElementInjector;
appInjector: Injector;
hostElementInjector: eiModule.ElementInjector;
hostLightDom: LightDom;
constructor(parentView: viewModule.View,
templateElement,
constructor(viewFactory:vfModule.ViewFactory,
parentView: viewModule.View,
defaultProtoView: viewModule.ProtoView,
elementInjector: eiModule.ElementInjector,
eventManager: EventManager,
lightDom = null) {
elementInjector: eiModule.ElementInjector) {
this.viewFactory = viewFactory;
this.render = 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;
this.hostLightDom = null;
this._eventManager = eventManager;
}
hydrate(appInjector: Injector, hostElementInjector: eiModule.ElementInjector, hostLightDom: LightDom) {
internalHydrateRecurse(render:renderApi.ViewContainerRef, appInjector: Injector, hostElementInjector: eiModule.ElementInjector) {
this.render = render;
this.appInjector = appInjector;
this.hostElementInjector = hostElementInjector;
this.hostLightDom = hostLightDom;
}
dehydrate() {
internalDehydrateRecurse() {
this.appInjector = null;
this.hostElementInjector = null;
this.hostLightDom = null;
this.clear();
this.render = null;
// Note: We don't call clear here,
// as we don't want to change the render side
// (i.e. don't deattach views on the render side),
// as the render side does its own recursion.
for (var i = this._views.length - 1; i >= 0; i--) {
var view = this._views[i];
view.changeDetector.remove();
this._unlinkElementInjectors(view);
view.internalDehydrateRecurse();
this.viewFactory.returnView(view);
}
this._views = [];
}
clear() {
@ -70,11 +75,6 @@ export class ViewContainer {
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);
}
@ -84,28 +84,27 @@ export class ViewContainer {
create(atIndex=-1): viewModule.View {
if (!this.hydrated()) throw new BaseException(
'Cannot create views on a dehydrated ViewContainer');
// TODO(rado): replace with viewFactory.
var newView = this.defaultProtoView.instantiate(this.hostElementInjector, this._eventManager);
var newView = this.viewFactory.getView(this.defaultProtoView);
// insertion must come before hydration so that element injector trees are attached.
this.insert(newView, atIndex);
newView.hydrate(this.appInjector, this.hostElementInjector, this.hostLightDom,
this._insertWithoutRender(newView, atIndex);
// hydration must come before changing the render side,
// as it acquires the render views.
newView.hydrate(this.appInjector, this.hostElementInjector,
this.parentView.context, this.parentView.locals);
this.defaultProtoView.renderer.insertViewIntoContainer(this.render, newView.render, atIndex);
// new content tags might have appeared, we need to redistrubute.
if (isPresent(this.hostLightDom)) {
this.hostLightDom.redistribute();
}
return newView;
}
insert(view, atIndex=-1): viewModule.View {
this._insertWithoutRender(view, atIndex);
this.defaultProtoView.renderer.insertViewIntoContainer(this.render, view.render, atIndex);
return view;
}
_insertWithoutRender(view, atIndex=-1): viewModule.View {
if (atIndex == -1) atIndex = this._views.length;
ListWrapper.insert(this._views, atIndex, view);
if (isBlank(this._lightDom)) {
ViewContainer.moveViewNodesAfterSibling(this._siblingToInsertAfter(atIndex), view);
} else {
this._lightDom.redistribute();
}
this.parentView.changeDetector.addChild(view.changeDetector);
this._linkElementInjectors(view);
@ -116,8 +115,7 @@ export class ViewContainer {
if (atIndex == -1) atIndex = this._views.length - 1;
var view = this.detach(atIndex);
view.dehydrate();
// TODO(rado): this needs to be delayed until after any pending animations.
this.defaultProtoView.returnToPool(view);
this.viewFactory.returnView(view);
// view is intentionally not returned to the client.
}
@ -129,32 +127,12 @@ export class ViewContainer {
if (atIndex == -1) atIndex = this._views.length - 1;
var detachedView = this.get(atIndex);
ListWrapper.removeAt(this._views, atIndex);
if (isBlank(this._lightDom)) {
ViewContainer.removeViewNodes(detachedView);
} else {
this._lightDom.redistribute();
}
// content tags might have disappeared we need to do redistribution.
if (isPresent(this.hostLightDom)) {
this.hostLightDom.redistribute();
}
this.defaultProtoView.renderer.detachViewFromContainer(this.render, atIndex);
detachedView.changeDetector.remove();
this._unlinkElementInjectors(detachedView);
return detachedView;
}
contentTagContainers() {
return this._views;
}
nodes():List {
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;
@ -166,19 +144,4 @@ export class ViewContainer {
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 removeViewNodes(view) {
var len = view.nodes.length;
if (len == 0) return;
var parent = view.nodes[0].parentNode;
for (var i = len - 1; i >= 0; --i) {
DOM.removeChild(parent, view.nodes[i]);
}
}
}

View File

@ -0,0 +1,109 @@
import {Injectable, Inject, OpaqueToken} from 'angular2/di';
import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src/facade/collection';
import * as eli from './element_injector';
import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
import {NgElement} from 'angular2/src/core/compiler/ng_element';
import * as vcModule from './view_container';
import * as viewModule from './view';
import {BindingPropagationConfig} from 'angular2/change_detection';
// TODO(tbosch): Make this an OpaqueToken as soon as our transpiler supports this!
export const VIEW_POOL_CAPACITY = 'ViewFactory.viewPoolCapacity';
@Injectable()
export class ViewFactory {
_poolCapacity:number;
_pooledViews:List<viewModule.View>;
constructor(@Inject(VIEW_POOL_CAPACITY) capacity) {
this._poolCapacity = capacity;
this._pooledViews = ListWrapper.create();
}
getView(protoView:viewModule.ProtoView):viewModule.View {
// TODO(tbosch): benchmark this scanning of views and maybe
// replace it with a fancy LRU Map/List combination...
var view;
for (var i=this._pooledViews.length-1; i>=0; i--) {
var pooledView = this._pooledViews[i];
if (pooledView.proto === protoView) {
view = ListWrapper.removeAt(this._pooledViews, i);
}
}
if (isBlank(view)) {
view = this._createView(protoView);
}
return view;
}
returnView(view:viewModule.View) {
if (view.hydrated()) {
throw new BaseException('Only dehydrated Views can be put back into the pool!');
}
ListWrapper.push(this._pooledViews, view);
while (this._pooledViews.length > this._poolCapacity) {
ListWrapper.removeAt(this._pooledViews, 0);
}
}
_createView(protoView:viewModule.ProtoView): viewModule.View {
var view = new viewModule.View(protoView, protoView.protoLocals);
var changeDetector = protoView.protoChangeDetector.instantiate(view, protoView.bindingRecords,
protoView.getVariableBindings(), protoView.getDirectiveMementos());
var binders = protoView.elementBinders;
var elementInjectors = ListWrapper.createFixedSize(binders.length);
var rootElementInjectors = [];
var preBuiltObjects = ListWrapper.createFixedSize(binders.length);
var viewContainers = ListWrapper.createFixedSize(binders.length);
var componentChildViews = [];
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
var binder = binders[binderIdx];
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);
} else {
elementInjector = protoElementInjector.instantiate(null);
ListWrapper.push(rootElementInjectors, elementInjector);
}
}
elementInjectors[binderIdx] = elementInjector;
// componentChildViews
var bindingPropagationConfig = null;
if (isPresent(binder.nestedProtoView) && isPresent(binder.componentDirective)) {
var childView = this._createView(binder.nestedProtoView);
changeDetector.addChild(childView.changeDetector);
bindingPropagationConfig = new BindingPropagationConfig(childView.changeDetector);
ListWrapper.push(componentChildViews, childView);
}
// viewContainers
var viewContainer = null;
if (isPresent(binder.viewportDirective)) {
viewContainer = new vcModule.ViewContainer(this, view, binder.nestedProtoView, elementInjector);
}
viewContainers[binderIdx] = viewContainer;
// preBuiltObjects
if (isPresent(elementInjector)) {
preBuiltObjects[binderIdx] = new eli.PreBuiltObjects(view, new NgElement(view, binderIdx), viewContainer,
bindingPropagationConfig);
}
}
view.init(changeDetector, elementInjectors, rootElementInjectors,
viewContainers, preBuiltObjects, componentChildViews);
return view;
}
}

View File

@ -1,26 +0,0 @@
import {ListWrapper, MapWrapper, StringMapWrapper, List} from 'angular2/src/facade/collection';
import * as viewModule from './view';
export class ViewPool {
_views: List<viewModule.View>;
_capacity: number;
constructor(capacity: number) {
this._views = [];
this._capacity = capacity;
}
pop(): viewModule.View {
return ListWrapper.isEmpty(this._views) ? null : ListWrapper.removeLast(this._views);
}
push(view: viewModule.View) {
if (this._views.length < this._capacity) {
ListWrapper.push(this._views, view);
}
}
length() {
return this._views.length;
}
}

View File

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

View File

@ -1,7 +1,7 @@
import {Decorator} from 'angular2/src/core/annotations/annotations';
import {isPresent} from 'angular2/src/facade/lang';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {NgElement} from 'angular2/src/core/dom/element';
import {NgElement} from 'angular2/src/core/compiler/ng_element';
@Decorator({
selector: '[class]',

View File

@ -1,6 +1,5 @@
import {Decorator, Viewport} from 'angular2/src/core/annotations/annotations';
import {ViewContainer} from 'angular2/src/core/compiler/view_container';
import {NgElement} from 'angular2/src/core/dom/element';
import {isPresent, isBlank, normalizeBlank} from 'angular2/src/facade/lang';
import {ListWrapper, List, MapWrapper, Map} from 'angular2/src/facade/collection';
import {Parent} from 'angular2/src/core/annotations/visibility';
@ -156,7 +155,7 @@ export class SwitchWhen {
_switch: Switch;
_viewContainer: ViewContainer;
constructor(el: NgElement, viewContainer: ViewContainer, @Parent() sswitch: Switch) {
constructor(viewContainer: ViewContainer, @Parent() sswitch: Switch) {
// `_whenDefault` is used as a marker for a not yet initialized value
this._value = _whenDefault;
this._switch = sswitch;

View File

@ -203,7 +203,7 @@ export class Renderer {
* Sets the dispatcher for all events that have been defined in the template or in directives
* in the given view.
*/
setEventDispatcher(viewRef:ViewRef, dispatcher:EventDispatcher):void {}
setEventDispatcher(viewRef:ViewRef, dispatcher:any/*EventDispatcher*/):void {}
/**
* To be called at the end of the VmTurn so the API can buffer calls
@ -218,10 +218,10 @@ export class Renderer {
export class EventDispatcher {
/**
* Called when an event was triggered for a on-* attribute on an element.
* @param {List<any>} locals Locals to be used to evaluate the
* @param {Map<string, any>} locals Locals to be used to evaluate the
* event expressions
*/
dispatchEvent(
elementIndex:number, eventName:string, locals:List<any>
elementIndex:number, eventName:string, locals:Map<string, any>
):void {}
}

View File

@ -1,10 +1,14 @@
import {Injectable} from 'angular2/di';
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
import {BaseException} from 'angular2/src/facade/lang';
import {Template, ProtoView} from '../../api';
import {CompilePipeline} from './compile_pipeline';
import {TemplateLoader} from 'angular2/src/render/dom/compiler/template_loader';
import {CompileStepFactory} from './compile_step_factory';
import {CompileStepFactory, DefaultStepFactory} from './compile_step_factory';
import {Parser} from 'angular2/change_detection';
import {ShadowDomStrategy} from '../shadow_dom/shadow_dom_strategy';
/**
* The compiler loads and translates the html templates of components into
@ -43,4 +47,11 @@ export class Compiler {
return PromiseWrapper.resolve(protoView);
}
}
}
@Injectable()
export class DefaultCompiler extends Compiler {
constructor(parser:Parser, shadowDomStrategy:ShadowDomStrategy, templateLoader: TemplateLoader) {
super(new DefaultStepFactory(parser, shadowDomStrategy), templateLoader);
}
}

View File

@ -1,3 +1,4 @@
import {Injectable} from 'angular2/di';
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
import {List, ListWrapper} from 'angular2/src/facade/collection';
import {isBlank, isPresent} from 'angular2/src/facade/lang';
@ -14,7 +15,7 @@ function _resolveViewContainer(vc:api.ViewContainerRef) {
return _resolveView(vc.view).viewContainers[vc.elementIndex];
}
function _resolveView(viewRef:_DirectDomViewRef) {
function _resolveView(viewRef:DirectDomViewRef) {
return isPresent(viewRef) ? viewRef.delegate : null;
}
@ -23,7 +24,7 @@ function _resolveProtoView(protoViewRef:DirectDomProtoViewRef) {
}
function _wrapView(view:View) {
return new _DirectDomViewRef(view);
return new DirectDomViewRef(view);
}
function _collectComponentChildViewRefs(view, target = null) {
@ -51,7 +52,7 @@ export class DirectDomProtoViewRef extends api.ProtoViewRef {
}
}
class _DirectDomViewRef extends api.ViewRef {
export class DirectDomViewRef extends api.ViewRef {
delegate:View;
constructor(delegate:View) {
@ -60,6 +61,7 @@ class _DirectDomViewRef extends api.ViewRef {
}
}
@Injectable()
export class DirectDomRenderer extends api.Renderer {
_compiler: Compiler;
_viewFactory: ViewFactory;
@ -131,7 +133,7 @@ export class DirectDomRenderer extends api.Renderer {
_resolveView(viewRef).setText(textNodeIndex, text);
}
setEventDispatcher(viewRef:api.ViewRef, dispatcher:api.EventDispatcher) {
setEventDispatcher(viewRef:api.ViewRef, dispatcher:any/*api.EventDispatcher*/):void {
_resolveView(viewRef).setEventDispatcher(dispatcher);
}
}

View File

@ -242,19 +242,19 @@ export class EventLocalsAstSplitter extends AstTransformer {
}
splitEventAstIntoLocals(eventBindings:Map<string, ASTWithSource>):Map<string, ASTWithSource> {
// TODO(tbosch): reenable this when we are using
// the render views
return eventBindings;
// if (isPresent(eventBindings)) {
// var result = MapWrapper.create();
// MapWrapper.forEach(eventBindings, (astWithSource, eventName) => {
// var adjustedAst = astWithSource.ast.visit(this);
// MapWrapper.set(result, eventName, new ASTWithSource(adjustedAst, astWithSource.source, ''));
// ListWrapper.push(this.eventNames, eventName);
// });
// return result;
// }
// return null;
if (isPresent(eventBindings)) {
var result = MapWrapper.create();
MapWrapper.forEach(eventBindings, (astWithSource, eventName) => {
// TODO(tbosch): reenable this when we are parsing element properties
// out of action expressions
// var adjustedAst = astWithSource.ast.visit(this);
var adjustedAst = astWithSource.ast;
MapWrapper.set(result, eventName, new ASTWithSource(adjustedAst, astWithSource.source, ''));
ListWrapper.push(this.eventNames, eventName);
});
return result;
}
return null;
}
visitAccessMember(ast:AccessMember) {

View File

@ -1,7 +1,6 @@
import {DOM} from 'angular2/src/dom/dom_adapter';
import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src/facade/collection';
import {int, isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
import {Locals} from 'angular2/change_detection';
import {ViewContainer} from './view_container';
import {ProtoView} from './proto_view';
@ -10,7 +9,7 @@ import {Content} from '../shadow_dom/content_tag';
import {ShadowDomStrategy} from '../shadow_dom/shadow_dom_strategy';
import {EventDispatcher} from '../../api';
// import {EventDispatcher} from '../../api';
const NG_BINDING_CLASS = 'ng-binding';
@ -31,7 +30,7 @@ export class View {
lightDoms: List<LightDom>;
proto: ProtoView;
_hydrated: boolean;
_eventDispatcher: EventDispatcher;
_eventDispatcher: any/*EventDispatcher*/;
constructor(
proto:ProtoView, rootNodes:List,
@ -43,6 +42,7 @@ export class View {
this.viewContainers = viewContainers;
this.contentTags = contentTags;
this.lightDoms = ListWrapper.createFixedSize(boundElements.length);
ListWrapper.fill(this.lightDoms, null);
this.componentChildViews = ListWrapper.createFixedSize(boundElements.length);
this._hydrated = false;
}
@ -134,7 +134,10 @@ export class View {
// componentChildViews
for (var i = 0; i < this.componentChildViews.length; i++) {
this.componentChildViews[i].dehydrate();
var cv = this.componentChildViews[i];
if (isPresent(cv)) {
cv.dehydrate();
}
}
// viewContainers and content tags
@ -150,10 +153,11 @@ export class View {
}
}
}
this._eventDispatcher = null;
this._hydrated = false;
}
setEventDispatcher(dispatcher:EventDispatcher) {
setEventDispatcher(dispatcher:any/*EventDispatcher*/) {
this._eventDispatcher = dispatcher;
}
@ -161,8 +165,11 @@ export class View {
if (isPresent(this._eventDispatcher)) {
var evalLocals = MapWrapper.create();
MapWrapper.set(evalLocals, '$event', event);
var localValues = this.proto.elementBinders[elementIndex].eventLocals.eval(null, new Locals(null, evalLocals));
this._eventDispatcher.dispatchEvent(elementIndex, eventName, localValues);
// TODO(tbosch): reenable this when we are parsing element properties
// out of action expressions
// var localValues = this.proto.elementBinders[elementIndex].eventLocals.eval(null, new Locals(null, evalLocals));
// this._eventDispatcher.dispatchEvent(elementIndex, eventName, localValues);
this._eventDispatcher.dispatchEvent(elementIndex, eventName, evalLocals);
}
}
}

View File

@ -35,7 +35,7 @@ export class ViewContainer {
if (isBlank(this._lightDom)) {
for (var i = this._views.length - 1; i >= 0; i--) {
var view = this._views[i];
ViewContainer.removeViewNodesFromParent(this.templateElement.parentNode, view);
ViewContainer.removeViewNodes(view);
this._viewFactory.returnView(view);
}
this._views = [];
@ -72,12 +72,11 @@ export class ViewContainer {
}
insert(view, atIndex=-1): viewModule.View {
this._checkHydrated();
if (atIndex == -1) atIndex = this._views.length;
ListWrapper.insert(this._views, atIndex, view);
if (!view.hydrated()) {
view.hydrate(this._hostLightDom);
}
if (atIndex == -1) atIndex = this._views.length;
ListWrapper.insert(this._views, atIndex, view);
if (isBlank(this._lightDom)) {
ViewContainer.moveViewNodesAfterSibling(this._siblingToInsertAfter(atIndex), view);
@ -100,7 +99,7 @@ export class ViewContainer {
var detachedView = this.get(atIndex);
ListWrapper.removeAt(this._views, atIndex);
if (isBlank(this._lightDom)) {
ViewContainer.removeViewNodesFromParent(this.templateElement.parentNode, detachedView);
ViewContainer.removeViewNodes(detachedView);
} else {
this._lightDom.redistribute();
}
@ -129,8 +128,11 @@ export class ViewContainer {
}
}
static removeViewNodesFromParent(parent, view) {
for (var i = view.rootNodes.length - 1; i >= 0; --i) {
static removeViewNodes(view) {
var len = view.rootNodes.length;
if (len == 0) return;
var parent = view.rootNodes[0].parentNode;
for (var i = len - 1; i >= 0; --i) {
DOM.removeChild(parent, view.rootNodes[i]);
}
}

View File

@ -1,4 +1,4 @@
import {OpaqueToken} from 'angular2/di';
import {OpaqueToken, Inject, Injectable} from 'angular2/di';
import {int, isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src/facade/collection';
@ -8,32 +8,33 @@ import {Content} from '../shadow_dom/content_tag';
import {ShadowDomStrategy} from '../shadow_dom/shadow_dom_strategy';
import {EventManager} from 'angular2/src/render/dom/events/event_manager';
import {ViewContainer} from './view_container';
import {ProtoView} from './proto_view';
import {View} from './view';
import * as vcModule from './view_container';
import * as pvModule from './proto_view';
import * as viewModule from './view';
import {NG_BINDING_CLASS_SELECTOR, NG_BINDING_CLASS} from '../util';
export var VIEW_POOL_CAPACITY = new OpaqueToken('ViewFactory.viewPoolCapacity');
// TODO(tbosch): Make this an OpaqueToken as soon as our transpiler supports this!
export const VIEW_POOL_CAPACITY = 'render.ViewFactory.viewPoolCapacity';
@Injectable()
export class ViewFactory {
_poolCapacity:number;
_pooledViews:List<View>;
_pooledViews:List<viewModule.View>;
_eventManager:EventManager;
_shadowDomStrategy:ShadowDomStrategy;
constructor(capacity, eventManager, shadowDomStrategy) {
constructor(@Inject(VIEW_POOL_CAPACITY) capacity, eventManager:EventManager, shadowDomStrategy:ShadowDomStrategy) {
this._poolCapacity = capacity;
this._pooledViews = ListWrapper.create();
this._eventManager = eventManager;
this._shadowDomStrategy = shadowDomStrategy;
}
getView(protoView:ProtoView):View {
getView(protoView:pvModule.ProtoView):viewModule.View {
// TODO(tbosch): benchmark this scanning of views and maybe
// replace it with a fancy LRU Map/List combination...
var view;
for (var i=0; i<this._pooledViews.length; i++) {
for (var i=this._pooledViews.length-1; i>=0; i--) {
var pooledView = this._pooledViews[i];
if (pooledView.proto === protoView) {
view = ListWrapper.removeAt(this._pooledViews, i);
@ -45,7 +46,7 @@ export class ViewFactory {
return view;
}
returnView(view:View) {
returnView(view:viewModule.View) {
if (view.hydrated()) {
view.dehydrate();
}
@ -55,7 +56,7 @@ export class ViewFactory {
}
}
_createView(protoView:ProtoView): View {
_createView(protoView:pvModule.ProtoView): viewModule.View {
var rootElementClone = protoView.isRootView ? protoView.element : DOM.importIntoDoc(protoView.element);
var elementsWithBindingsDynamic;
if (protoView.isTemplateElement) {
@ -72,7 +73,7 @@ export class ViewFactory {
var viewRootNodes;
if (protoView.isTemplateElement) {
var childNode = DOM.firstChild(DOM.content(rootElementClone));
viewRootNodes = []; // TODO(perf): Should be fixed size, since we could pre-compute in in ProtoView
viewRootNodes = []; // TODO(perf): Should be fixed size, since we could pre-compute in in pvModule.ProtoView
// Note: An explicit loop is the fastest way to convert a DOM array into a JS array!
while(childNode != null) {
ListWrapper.push(viewRootNodes, childNode);
@ -108,7 +109,7 @@ export class ViewFactory {
// viewContainers
var viewContainer = null;
if (isBlank(binder.componentId) && isPresent(binder.nestedProtoView)) {
viewContainer = new ViewContainer(this, element);
viewContainer = new vcModule.ViewContainer(this, element);
}
viewContainers[binderIdx] = viewContainer;
@ -120,7 +121,7 @@ export class ViewFactory {
contentTags[binderIdx] = contentTag;
}
var view = new View(
var view = new viewModule.View(
protoView, viewRootNodes,
boundTextNodes, boundElements, viewContainers, contentTags
);

View File

@ -1,6 +1,6 @@
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
import {DomAdapter} from 'angular2/src/dom/dom_adapter';
import {NgElement} from 'angular2/src/core/dom/element';
import {NgElement} from 'angular2/src/core/compiler/ng_element';
export class Rectangle {
left;

View File

@ -1,4 +1,4 @@
import {Injector} from 'angular2/di';
import {Injector, bind} from 'angular2/di';
import {Type, isPresent, BaseException} from 'angular2/src/facade/lang';
import {Promise} from 'angular2/src/facade/async';
@ -10,12 +10,15 @@ import {Template} from 'angular2/src/core/annotations/template';
import {TemplateResolver} from 'angular2/src/core/compiler/template_resolver';
import {Compiler} from 'angular2/src/core/compiler/compiler';
import {View} from 'angular2/src/core/compiler/view';
import {ViewFactory} from 'angular2/src/core/compiler/view_factory';
import {EventManager} from 'angular2/src/render/dom/events/event_manager';
import {DirectiveBinding} from 'angular2/src/core/compiler/element_injector';
import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader';
import {queryView} from './utils';
import {queryView, viewRootNodes, el} from './utils';
import {instantiateType, getTypeOf} from './lang_utils';
export class TestBed {
_injector: Injector;
@ -85,11 +88,17 @@ export class TestBed {
this.setInlineTemplate(component, html);
}
return this._injector.get(Compiler).compile(component).then((pv) => {
var eventManager = this._injector.get(EventManager);
var view = pv.instantiate(null, eventManager);
view.hydrate(this._injector, null, null, context, null);
return new ViewProxy(view);
var rootEl = el('<div></div>');
var metadataReader = this._injector.get(DirectiveMetadataReader);
var componentBinding = DirectiveBinding.createFromBinding(
bind(component).toValue(context),
metadataReader.read(component).annotation
);
return this._injector.get(Compiler).compileRoot(rootEl, componentBinding).then((pv) => {
var viewFactory = this._injector.get(ViewFactory);
var view = viewFactory.getView(pv);
view.hydrate(this._injector, null, context, null);
return new ViewProxy(view.componentChildViews[0]);
});
}
}
@ -108,8 +117,8 @@ export class ViewProxy {
return this._view.context;
}
get nodes(): List {
return this._view.nodes;
get rootNodes(): List {
return viewRootNodes(this._view);
}
detectChanges(): void {

View File

@ -7,13 +7,15 @@ import {ExceptionHandler} from 'angular2/src/core/exception_handler';
import {TemplateLoader} from 'angular2/src/render/dom/compiler/template_loader';
import {TemplateResolver} from 'angular2/src/core/compiler/template_resolver';
import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader';
import {ShadowDomStrategy, EmulatedUnscopedShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
import {ShadowDomStrategy} from 'angular2/src/render/dom/shadow_dom/shadow_dom_strategy';
import {EmulatedUnscopedShadowDomStrategy} from 'angular2/src/render/dom/shadow_dom/emulated_unscoped_shadow_dom_strategy';
import {XHR} from 'angular2/src/services/xhr';
import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mapper';
import {UrlResolver} from 'angular2/src/services/url_resolver';
import {StyleUrlResolver} from 'angular2/src/render/dom/shadow_dom/style_url_resolver';
import {StyleInliner} from 'angular2/src/render/dom/shadow_dom/style_inliner';
import {VmTurnZone} from 'angular2/src/core/zone/vm_turn_zone';
import {PrivateComponentLoader} from 'angular2/src/core/compiler/private_component_loader';
import {DOM} from 'angular2/src/dom/dom_adapter';
@ -32,6 +34,13 @@ import {Injector} from 'angular2/di';
import {List, ListWrapper} from 'angular2/src/facade/collection';
import {FunctionWrapper} from 'angular2/src/facade/lang';
import {ViewFactory, VIEW_POOL_CAPACITY} from 'angular2/src/core/compiler/view_factory';
import {ProtoViewFactory} from 'angular2/src/core/compiler/proto_view_factory';
import {Renderer} from 'angular2/src/render/api';
import {DirectDomRenderer} from 'angular2/src/render/dom/direct_dom_renderer';
import * as rc from 'angular2/src/render/dom/compiler/compiler';
import * as rvf from 'angular2/src/render/dom/view/view_factory';
/**
* Returns the root injector bindings.
*
@ -67,11 +76,19 @@ function _getAppBindings() {
bind(ShadowDomStrategy).toFactory(
(styleUrlResolver, doc) => new EmulatedUnscopedShadowDomStrategy(styleUrlResolver, doc.head),
[StyleUrlResolver, appDocumentToken]),
bind(Renderer).toClass(DirectDomRenderer),
bind(rc.Compiler).toClass(rc.DefaultCompiler),
rvf.ViewFactory,
bind(rvf.VIEW_POOL_CAPACITY).toValue(500),
ProtoViewFactory,
ViewFactory,
bind(VIEW_POOL_CAPACITY).toValue(500),
Compiler,
CompilerCache,
bind(TemplateResolver).toClass(MockTemplateResolver),
bind(ChangeDetection).toValue(dynamicChangeDetection),
TemplateLoader,
PrivateComponentLoader,
DirectiveMetadataReader,
Parser,
Lexer,

View File

@ -24,9 +24,14 @@ export class Log {
}
}
export function viewRootNodes(view) {
return view.render.delegate.rootNodes;
}
export function queryView(view, selector) {
for (var i = 0; i < view.nodes.length; ++i) {
var res = DOM.querySelector(view.nodes[i], selector);
var rootNodes = viewRootNodes(view);
for (var i = 0; i < rootNodes.length; ++i) {
var res = DOM.querySelector(rootNodes[i], selector);
if (isPresent(res)) {
return res;
}