refactor(shadow dom): do not use injectors nor directives

This prepares us for the app/render split in the compiler.
This commit is contained in:
Yegor Jbanov
2015-03-16 11:31:58 -07:00
parent 115ac5f290
commit 70c875ee14
31 changed files with 367 additions and 343 deletions

View File

@ -67,7 +67,7 @@ function _injectorBindings(appComponentType): List<Binding> {
// the angular application. Thus the context and lightDomInjector are
// empty.
var view = appProtoView.instantiate(null, eventManager);
view.hydrate(injector, null, new Object());
view.hydrate(injector, null, null, new Object());
return view;
});
}, [ChangeDetection, Compiler, Injector, appElementToken, appComponentAnnotatedTypeToken,

View File

@ -11,7 +11,6 @@ import {CompileElement} from './pipeline/compile_element';
import {createDefaultSteps} from './pipeline/default_steps';
import {TemplateLoader} from './template_loader';
import {TemplateResolver} from './template_resolver';
import {DirectiveMetadata} from './directive_metadata';
import {Template} from '../annotations/template';
import {ShadowDomStrategy} from './shadow_dom_strategy';
import {CompileStep} from './pipeline/compile_step';
@ -56,7 +55,6 @@ export class Compiler {
_templateLoader:TemplateLoader;
_compiling:Map<Type, Promise>;
_shadowDomStrategy: ShadowDomStrategy;
_shadowDomDirectives: List<DirectiveMetadata>;
_templateResolver: TemplateResolver;
_componentUrlMapper: ComponentUrlMapper;
_urlResolver: UrlResolver;
@ -80,11 +78,6 @@ export class Compiler {
this._templateLoader = templateLoader;
this._compiling = MapWrapper.create();
this._shadowDomStrategy = shadowDomStrategy;
this._shadowDomDirectives = [];
var types = shadowDomStrategy.polyfillDirectives();
for (var i = 0; i < types.length; i++) {
ListWrapper.push(this._shadowDomDirectives, reader.read(types[i]));
}
this._templateResolver = templateResolver;
this._componentUrlMapper = componentUrlMapper;
this._urlResolver = urlResolver;
@ -93,12 +86,8 @@ export class Compiler {
}
createSteps(component:Type, template: Template):List<CompileStep> {
// Merge directive metadata (from the template and from the shadow dom strategy)
var dirMetadata = [];
var tplMetadata = ListWrapper.map(this._flattenDirectives(template),
var dirMetadata = ListWrapper.map(this._flattenDirectives(template),
(d) => this._reader.read(d));
dirMetadata = ListWrapper.concat(dirMetadata, tplMetadata);
dirMetadata = ListWrapper.concat(dirMetadata, this._shadowDomDirectives);
var cmpMetadata = this._reader.read(component);

View File

@ -1,3 +1,4 @@
import {int, isBlank, BaseException} from 'angular2/src/facade/lang';
import {ProtoElementInjector} from './element_injector';
import {DirectiveMetadata} from './directive_metadata';
import {List, StringMap} from 'angular2/src/facade/collection';
@ -11,12 +12,24 @@ export class ElementBinder {
hasElementPropertyBindings:boolean;
nestedProtoView: ProtoView;
events:StringMap;
contentTagSelector:string;
parent:ElementBinder;
index:int;
distanceToParent:int;
constructor(
index:int, parent:ElementBinder, distanceToParent: int,
protoElementInjector: ProtoElementInjector, componentDirective:DirectiveMetadata,
viewportDirective:DirectiveMetadata) {
if (isBlank(index)) {
throw new BaseException('null index not allowed.');
}
this.protoElementInjector = protoElementInjector;
this.componentDirective = componentDirective;
this.viewportDirective = viewportDirective;
this.parent = parent;
this.index = index;
this.distanceToParent = distanceToParent;
// updated later when events are bound
this.events = null;
// updated later when text nodes are bound
@ -25,5 +38,7 @@ export class ElementBinder {
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

@ -5,7 +5,6 @@ import {Injector, Key, Dependency, bind, Binding, NoProviderError, ProviderError
import {Parent, Ancestor} from 'angular2/src/core/annotations/visibility';
import {EventEmitter, PropertySetter} from 'angular2/src/core/annotations/di';
import * as viewModule from 'angular2/src/core/compiler/view';
import {LightDom, DestinationLightDom} from 'angular2/src/core/compiler/shadow_dom_emulation/light_dom';
import {ViewContainer} from 'angular2/src/core/compiler/view_container';
import {NgElement} from 'angular2/src/core/dom/element';
import {Directive, onChange, onDestroy} from 'angular2/src/core/annotations/annotations'
@ -24,8 +23,6 @@ class StaticKeys {
viewId:number;
ngElementId:number;
viewContainerId:number;
destinationLightDomId:number;
lightDomId:number;
bindingPropagationConfigId:number;
constructor() {
@ -33,8 +30,6 @@ class StaticKeys {
this.viewId = Key.get(viewModule.View).id;
this.ngElementId = Key.get(NgElement).id;
this.viewContainerId = Key.get(ViewContainer).id;
this.destinationLightDomId = Key.get(DestinationLightDom).id;
this.lightDomId = Key.get(LightDom).id;
this.bindingPropagationConfigId = Key.get(BindingPropagationConfig).id;
}
@ -166,14 +161,12 @@ export class PreBuiltObjects {
view:viewModule.View;
element:NgElement;
viewContainer:ViewContainer;
lightDom:LightDom;
bindingPropagationConfig:BindingPropagationConfig;
constructor(view, element:NgElement, viewContainer:ViewContainer, lightDom:LightDom,
constructor(view, element:NgElement, viewContainer:ViewContainer,
bindingPropagationConfig:BindingPropagationConfig) {
this.view = view;
this.element = element;
this.viewContainer = viewContainer;
this.lightDom = lightDom;
this.bindingPropagationConfig = bindingPropagationConfig;
}
}
@ -577,13 +570,6 @@ export class ElementInjector extends TreeNode {
if (keyId === staticKeys.ngElementId) return this._preBuiltObjects.element;
if (keyId === staticKeys.viewContainerId) return this._preBuiltObjects.viewContainer;
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.lightDomId) {
return this._preBuiltObjects.lightDom;
}
//TODO add other objects as needed
return _undefined;

View File

@ -36,10 +36,12 @@ export class CompileElement {
inheritedProtoView:ProtoView;
inheritedProtoElementInjector:ProtoElementInjector;
inheritedElementBinder:ElementBinder;
distanceToParentInjector:number;
distanceToParentInjector:int;
distanceToParentBinder:int;
compileChildren: boolean;
ignoreBindings: boolean;
elementDescription: string; // e.g. '<div [class]="foo">' : used to provide context in case of error
contentTagSelector: string;
constructor(element, compilationUnit = '') {
this.element = element;
@ -65,9 +67,11 @@ export class CompileElement {
// an own elementBinder
this.inheritedElementBinder = null;
this.distanceToParentInjector = 0;
this.distanceToParentBinder = 0;
this.compileChildren = true;
// set to true to ignore all the bindings on the element
this.ignoreBindings = false;
this.contentTagSelector = null;
// description is calculated here as compilation steps may change the element
var tplDesc = assertionsEnabled()? getElementDescription(element) : null;
if (compilationUnit !== '') {

View File

@ -1,6 +1,5 @@
import {ChangeDetection, Parser} from 'angular2/change_detection';
import {List, ListWrapper} from 'angular2/src/facade/collection';
import {isPresent} from 'angular2/src/facade/lang';
import {PropertyBindingParser} from './property_binding_parser';
import {TextInterpolationParser} from './text_interpolation_parser';
@ -32,6 +31,7 @@ export function createDefaultSteps(
var steps = [
new ViewSplitter(parser),
cssProcessor.getCompileStep(compiledComponent, shadowDomStrategy, templateUrl),
shadowDomStrategy.getTemplateCompileStep(compiledComponent),
new PropertyBindingParser(parser),
new DirectiveParser(directives),
new TextInterpolationParser(parser),
@ -41,10 +41,5 @@ export function createDefaultSteps(
new ElementBinderBuilder(parser),
];
var shadowDomStep = shadowDomStrategy.getTemplateCompileStep(compiledComponent);
if (isPresent(shadowDomStep)) {
ListWrapper.push(steps, shadowDomStep);
}
return steps;
}

View File

@ -49,7 +49,8 @@ export class DirectiveParser extends CompileStep {
var classList = current.classList();
var cssSelector = new CssSelector();
cssSelector.setElement(DOM.nodeName(current.element));
var nodeName = DOM.nodeName(current.element);
cssSelector.setElement(nodeName);
for (var i=0; i < classList.length; i++) {
cssSelector.addClassName(classList[i]);
}

View File

@ -135,6 +135,11 @@ export class ElementBinderBuilder extends CompileStep {
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
var elementBinder = null;
var parentElementBinder = null;
var distanceToParentBinder = this._getDistanceToParentBinder(parent, current);
if (isPresent(parent)) {
parentElementBinder = parent.inheritedElementBinder;
}
if (current.hasBindings) {
var protoView = current.inheritedProtoView;
var protoInjectorWasBuilt = isBlank(parent) ? true :
@ -143,8 +148,9 @@ export class ElementBinderBuilder extends CompileStep {
var currentProtoElementInjector = protoInjectorWasBuilt ?
current.inheritedProtoElementInjector : null;
elementBinder = protoView.bindElement(currentProtoElementInjector,
current.componentDirective, current.viewportDirective);
elementBinder = protoView.bindElement(parentElementBinder, distanceToParentBinder,
currentProtoElementInjector, current.componentDirective, current.viewportDirective);
current.distanceToParentBinder = 0;
if (isPresent(current.textNodeBindings)) {
this._bindTextNodes(protoView, current);
@ -155,15 +161,23 @@ export class ElementBinderBuilder extends CompileStep {
if (isPresent(current.eventBindings)) {
this._bindEvents(protoView, current);
}
if (isPresent(current.contentTagSelector)) {
elementBinder.contentTagSelector = current.contentTagSelector;
}
var directives = current.getAllDirectives();
this._bindDirectiveProperties(directives, current);
this._bindDirectiveEvents(directives, current);
} else if (isPresent(parent)) {
elementBinder = parent.inheritedElementBinder;
elementBinder = parentElementBinder;
current.distanceToParentBinder = distanceToParentBinder;
}
current.inheritedElementBinder = elementBinder;
}
_getDistanceToParentBinder(parent, current) {
return isPresent(parent) ? parent.distanceToParentBinder + 1 : 0;
}
_bindTextNodes(protoView, compileElement) {
MapWrapper.forEach(compileElement.textNodeBindings, (expression, indexInParent) => {
protoView.bindTextNode(indexInParent, expression);

View File

@ -37,7 +37,8 @@ export class ElementBindingMarker extends CompileStep {
(isPresent(current.eventBindings) && MapWrapper.size(current.eventBindings)>0) ||
(isPresent(current.decoratorDirectives) && current.decoratorDirectives.length > 0) ||
isPresent(current.viewportDirective) ||
isPresent(current.componentDirective);
isPresent(current.componentDirective) ||
isPresent(current.contentTagSelector);
if (hasBindings) {
var element = current.element;

View File

@ -1,10 +1,7 @@
import {Decorator} from '../../annotations/annotations';
import * as ldModule from './light_dom';
import {Inject} 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';
import {NgElement} from 'angular2/src/core/dom/element';
class ContentStrategy {
nodes:List;
@ -17,23 +14,16 @@ class ContentStrategy {
* and thus does not affect redistribution.
*/
class RenderedContent extends ContentStrategy {
static _lazyScriptTemplate;
beginScript;
endScript;
constructor(contentEl) {
super();
this._replaceContentElementWithScriptTags(contentEl);
this.beginScript = contentEl;
this.endScript = DOM.nextSibling(this.beginScript);
this.nodes = [];
}
_scriptTemplate() {
if (!isPresent(RenderedContent._lazyScriptTemplate)) {
RenderedContent._lazyScriptTemplate = DOM.createScriptTag('type', 'ng/content');
}
return RenderedContent._lazyScriptTemplate;
}
// Inserts the nodes in between the start and end scripts.
// Previous content is removed.
insert(nodes:List) {
@ -42,16 +32,6 @@ class RenderedContent extends ContentStrategy {
this._removeNodesUntil(ListWrapper.isEmpty(nodes) ? this.endScript : nodes[0]);
}
// Replaces the content tag with a pair of script tags
_replaceContentElementWithScriptTags(contentEl) {
this.beginScript = DOM.clone(this._scriptTemplate());
this.endScript = DOM.clone(this._scriptTemplate());
DOM.insertBefore(contentEl, this.beginScript);
DOM.insertBefore(contentEl, this.endScript);
DOM.removeChild(DOM.parentElement(contentEl), contentEl);
}
_removeNodesUntil(node) {
var p = DOM.parentElement(this.beginScript);
for (var next = DOM.nextSibling(this.beginScript);
@ -83,18 +63,17 @@ class IntermediateContent extends ContentStrategy {
}
@Decorator({
selector: 'content'
})
export class Content {
select:string;
_strategy:ContentStrategy;
contentStartElement;
constructor(@Inject(ldModule.DestinationLightDom) destinationLightDom, contentEl:NgElement) {
this.select = contentEl.getAttribute('select');
constructor(destinationLightDom:ldModule.LightDom, contentStartEl, selector:string) {
this.select = selector;
this.contentStartElement = contentStartEl;
this._strategy = isPresent(destinationLightDom) ?
new IntermediateContent(destinationLightDom) :
new RenderedContent(contentEl.domElement);
new RenderedContent(contentStartEl);
}
nodes():List {

View File

@ -3,8 +3,6 @@ import {List, ListWrapper} from 'angular2/src/facade/collection';
import {isBlank, isPresent} from 'angular2/src/facade/lang';
import * as viewModule from '../view';
import {ElementInjector} from '../element_injector';
import {ViewContainer} from '../view_container';
import {Content} from './content_tag';
export class DestinationLightDom {}
@ -12,11 +10,13 @@ export class DestinationLightDom {}
class _Root {
node;
injector:ElementInjector;
viewContainer;
content;
constructor(node, injector) {
constructor(node, viewContainer, content) {
this.node = node;
this.injector = injector;
this.viewContainer = viewContainer;
this.content = content;
}
}
@ -52,19 +52,18 @@ export class LightDom {
// Collects the Content directives from the view and all its child views
_collectAllContentTags(view: viewModule.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(ViewContainer)) {
var vc = ei.get(ViewContainer);
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;
@ -76,21 +75,16 @@ export class LightDom {
// - 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(ViewContainer)) {
var vc = root.injector.get(ViewContainer);
res = ListWrapper.concat(res, vc.nodes());
} else if (isPresent(ei) && ei.hasDirective(Content)) {
var content = root.injector.get(Content);
res = ListWrapper.concat(res, content.nodes());
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);
}
@ -103,10 +97,24 @@ export class LightDom {
_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) => isPresent(inj) ? inj.forElement(n) : false)));
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;
}
@ -119,10 +127,10 @@ function redistributeNodes(contents:List<Content>, nodes:List) {
var select = content.select;
var matchSelector = (n) => DOM.elementMatches(n, select);
if (isBlank(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);

View File

@ -1,4 +1,4 @@
import {Type, isBlank, isPresent, int} from 'angular2/src/facade/lang';
import {Type, isBlank, isPresent, int, StringWrapper, assertionsEnabled} from 'angular2/src/facade/lang';
import {List, ListWrapper, MapWrapper, Map} from 'angular2/src/facade/collection';
import {PromiseWrapper} from 'angular2/src/facade/async';
@ -6,7 +6,6 @@ import {DOM} from 'angular2/src/dom/dom_adapter';
import * as viewModule from './view';
import {Content} from './shadow_dom_emulation/content_tag';
import {LightDom} from './shadow_dom_emulation/light_dom';
import {ShadowCss} from './shadow_dom_emulation/shadow_css';
@ -19,20 +18,30 @@ import * as NS from './pipeline/compile_step';
import {CompileElement} from './pipeline/compile_element';
import {CompileControl} from './pipeline/compile_control';
var _EMPTY_STEP;
// Note: fill _EMPTY_STEP to prevent
// problems from cyclic dependencies
function _emptyStep() {
if (isBlank(_EMPTY_STEP)) {
_EMPTY_STEP = new _EmptyCompileStep();
}
return _EMPTY_STEP;
}
export class ShadowDomStrategy {
attachTemplate(el, view:viewModule.View) {}
constructLightDom(lightDomView:viewModule.View, shadowDomView:viewModule.View, el): LightDom { return null; }
polyfillDirectives():List<Type> { return []; }
/**
* An optional step that can modify the template style elements.
*
* @param {DirectiveMetadata} cmpMetadata
* @param {string} templateUrl the template base URL
* @returns {CompileStep} a compile step to append to the compiler pipeline, null if not required.
* @returns {CompileStep} a compile step to append to the compiler pipeline
*/
getStyleCompileStep(cmpMetadata: DirectiveMetadata, templateUrl: string): NS.CompileStep {
return null;
return _emptyStep();
}
/**
@ -41,9 +50,9 @@ export class ShadowDomStrategy {
* This step could be used to modify the template in order to scope the styles.
*
* @param {DirectiveMetadata} cmpMetadata
* @returns {CompileStep} a compile step to append to the compiler pipeline, null if not required.
* @returns {CompileStep} a compile step to append to the compiler pipeline
*/
getTemplateCompileStep(cmpMetadata: DirectiveMetadata): NS.CompileStep { return null; }
getTemplateCompileStep(cmpMetadata: DirectiveMetadata): NS.CompileStep { return _emptyStep(); }
/**
* The application element does not go through the compiler pipeline.
@ -87,14 +96,14 @@ export class EmulatedUnscopedShadowDomStrategy extends ShadowDomStrategy {
return new LightDom(lightDomView, shadowDomView, el);
}
polyfillDirectives():List<Type> {
return [Content];
}
getStyleCompileStep(cmpMetadata: DirectiveMetadata, templateUrl: string): NS.CompileStep {
return new _EmulatedUnscopedCssStep(cmpMetadata, templateUrl, this._styleUrlResolver,
this._styleHost);
}
getTemplateCompileStep(cmpMetadata: DirectiveMetadata): NS.CompileStep {
return new _BaseEmulatedShadowDomStep();
}
}
/**
@ -156,7 +165,39 @@ export class NativeShadowDomStrategy extends ShadowDomStrategy {
}
}
class _ShimShadowDomStep extends NS.CompileStep {
class _BaseEmulatedShadowDomStep extends NS.CompileStep {
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
if (current.ignoreBindings) {
return;
}
var nodeName = DOM.nodeName(current.element);
if (StringWrapper.equals(nodeName.toUpperCase(), 'CONTENT')) {
var attrs = current.attrs();
var selector = MapWrapper.get(attrs, 'select');
current.contentTagSelector = isPresent(selector) ? selector : '';
var contentStart = DOM.createScriptTag('type', 'ng/contentStart');
if (assertionsEnabled()) {
DOM.setAttribute(contentStart, 'select', current.contentTagSelector);
}
var contentEnd = DOM.createScriptTag('type', 'ng/contentEnd');
DOM.insertBefore(current.element, contentStart);
DOM.insertBefore(current.element, contentEnd);
DOM.remove(current.element);
current.element = contentStart;
}
}
}
class _EmptyCompileStep extends NS.CompileStep {
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
}
}
class _ShimShadowDomStep extends _BaseEmulatedShadowDomStep {
_contentAttribute: string;
constructor(cmpMetadata: DirectiveMetadata) {
@ -167,6 +208,7 @@ class _ShimShadowDomStep extends NS.CompileStep {
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
super.process(parent, current, control);
if (current.ignoreBindings) {
return;
}

View File

@ -13,7 +13,8 @@ import {IMPLEMENTS, int, isPresent, isBlank, BaseException} from 'angular2/src/f
import {Injector} from 'angular2/di';
import {NgElement} from 'angular2/src/core/dom/element';
import {ViewContainer} from './view_container';
import {LightDom, DestinationLightDom} from './shadow_dom_emulation/light_dom';
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/core/events/event_manager';
@ -41,7 +42,9 @@ export class View {
nodes:List;
componentChildViews: List<View>;
viewContainers: List<ViewContainer>;
contentTags: List<Content>;
preBuiltObjects: List<PreBuiltObjects>;
lightDoms: List<LightDom>;
proto: ProtoView;
context: any;
contextWithLocals:ContextWithVariableBindings;
@ -56,7 +59,9 @@ export class View {
this.bindElements = null;
this.componentChildViews = null;
this.viewContainers = null;
this.contentTags = null;
this.preBuiltObjects = null;
this.lightDoms = null;
this.context = null;
this.contextWithLocals = (MapWrapper.size(protoContextLocals) > 0)
? new ContextWithVariableBindings(null, MapWrapper.clone(protoContextLocals))
@ -64,15 +69,17 @@ export class View {
}
init(changeDetector:ChangeDetector, elementInjectors:List, rootElementInjectors:List, textNodes: List, bindElements:List,
viewContainers:List, preBuiltObjects:List, componentChildViews:List) {
viewContainers:List, contentTags:List, preBuiltObjects:List, componentChildViews:List, lightDoms:List<LightDom>) {
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) {
@ -125,14 +132,17 @@ export class View {
* A call to hydrate/dehydrate does not attach/detach the view from the view
* tree.
*/
hydrate(appInjector: Injector, hostElementInjector: ElementInjector,
hydrate(appInjector: Injector, hostElementInjector: ElementInjector, hostLightDom: LightDom,
context: Object) {
if (this.hydrated()) throw new BaseException('The view is already hydrated.');
this._hydrateContext(context);
// viewContainers
for (var i = 0; i < this.viewContainers.length; i++) {
this.viewContainers[i].hydrate(appInjector, hostElementInjector);
var vc = this.viewContainers[i];
if (isPresent(vc)) {
vc.hydrate(appInjector, hostElementInjector, hostLightDom);
}
}
var binders = this.proto.elementBinders;
@ -171,18 +181,14 @@ export class View {
if (isPresent(componentDirective)) {
this.componentChildViews[componentChildViewIndex++].hydrate(shadowDomAppInjector,
elementInjector, elementInjector.getComponent());
elementInjector, this.lightDoms[i], 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();
}
for (var i = 0; i < this.lightDoms.length; ++i) {
var lightDom = this.lightDoms[i];
if (isPresent(lightDom)) {
lightDom.redistribute();
}
}
}
@ -205,7 +211,10 @@ export class View {
// viewContainers
if (isPresent(this.viewContainers)) {
for (var i = 0; i < this.viewContainers.length; i++) {
this.viewContainers[i].dehydrate();
var vc = this.viewContainers[i];
if (isPresent(vc)) {
vc.dehydrate();
}
}
}
@ -370,8 +379,10 @@ export class ProtoView {
var textNodes = [];
var elementsWithPropertyBindings = [];
var preBuiltObjects = ListWrapper.createFixedSize(binders.length);
var viewContainers = [];
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];
@ -427,20 +438,32 @@ export class ProtoView {
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)) {
var destLightDom = this._directParentElementLightDom(protoElementInjector, preBuiltObjects);
viewContainer = new ViewContainer(view, element, binder.nestedProtoView, elementInjector,
eventManager, destLightDom);
ListWrapper.push(viewContainers, viewContainer);
}
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,
lightDom, bindingPropagationConfig);
bindingPropagationConfig);
}
// events
@ -460,7 +483,7 @@ export class ProtoView {
this.eventHandlers = eventHandlers;
view.init(changeDetector, elementInjectors, rootElementInjectors, textNodes, elementsWithPropertyBindings,
viewContainers, preBuiltObjects, componentChildViews);
viewContainers, contentTags, preBuiltObjects, componentChildViews, lightDoms);
return view;
}
@ -497,19 +520,15 @@ export class ProtoView {
}
}
_directParentElementLightDom(protoElementInjector:ProtoElementInjector, preBuiltObjects:List):LightDom {
var p = protoElementInjector.directParent();
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,
bindElement(parent:ElementBinder, distanceToParent:int, protoElementInjector:ProtoElementInjector,
componentDirective:DirectiveMetadata = null, viewportDirective:DirectiveMetadata = null):ElementBinder {
var elBinder = new ElementBinder(protoElementInjector, componentDirective, viewportDirective);
var elBinder = new ElementBinder(this.elementBinders.length, parent, distanceToParent,
protoElementInjector, componentDirective, viewportDirective);
ListWrapper.push(this.elementBinders, elBinder);
return elBinder;
}
@ -601,7 +620,7 @@ export class ProtoView {
var cmpType = rootComponentAnnotatedType.type;
var rootProtoView = new ProtoView(insertionElement, protoChangeDetector, shadowDomStrategy);
rootProtoView.instantiateInPlace = true;
var binder = rootProtoView.bindElement(
var binder = rootProtoView.bindElement(null, 0,
new ProtoElementInjector(null, 0, [cmpType], true));
binder.componentDirective = rootComponentAnnotatedType;
binder.nestedProtoView = protoView;

View File

@ -40,10 +40,10 @@ export class ViewContainer {
this._eventManager = eventManager;
}
hydrate(appInjector: Injector, hostElementInjector: eiModule.ElementInjector) {
hydrate(appInjector: Injector, hostElementInjector: eiModule.ElementInjector, hostLightDom: ldModule.LightDom) {
this.appInjector = appInjector;
this.hostElementInjector = hostElementInjector;
this.hostLightDom = isPresent(hostElementInjector) ? hostElementInjector.get(ldModule.LightDom) : null;
this.hostLightDom = hostLightDom;
}
dehydrate() {
@ -85,7 +85,7 @@ export class ViewContainer {
var newView = this.defaultProtoView.instantiate(this.hostElementInjector, this._eventManager);
// insertion must come before hydration so that element injector trees are attached.
this.insert(newView, atIndex);
newView.hydrate(this.appInjector, this.hostElementInjector, this.parentView.context);
newView.hydrate(this.appInjector, this.hostElementInjector, this.hostLightDom, this.parentView.context);
// new content tags might have appeared, we need to redistrubute.
if (isPresent(this.hostLightDom)) {