refactor(render): create and store render ProtoViewRef in every app ProtoView

Needed to change Renderer.mergeChildComponentProtoViews to not create
new ProtoViews to be able to deal with cyclic references.

This commit is part of using the new render layer in Angular.
This commit is contained in:
Tobias Bosch 2015-04-07 17:24:09 -07:00
parent d6003ee0ab
commit ca958464c4
16 changed files with 509 additions and 436 deletions

View File

@ -3,7 +3,6 @@ import {Type, isBlank, isPresent, BaseException, assertionsEnabled, print, strin
import {BrowserDomAdapter} from 'angular2/src/dom/browser_adapter'; import {BrowserDomAdapter} from 'angular2/src/dom/browser_adapter';
import {DOM} from 'angular2/src/dom/dom_adapter'; import {DOM} from 'angular2/src/dom/dom_adapter';
import {Compiler, CompilerCache} from './compiler/compiler'; import {Compiler, CompilerCache} from './compiler/compiler';
import {ProtoView} from './compiler/view';
import {Reflector, reflector} from 'angular2/src/reflection/reflection'; import {Reflector, reflector} from 'angular2/src/reflection/reflection';
import {Parser, Lexer, ChangeDetection, dynamicChangeDetection, jitChangeDetection} from 'angular2/change_detection'; import {Parser, Lexer, ChangeDetection, dynamicChangeDetection, jitChangeDetection} from 'angular2/change_detection';
import {ExceptionHandler} from './exception_handler'; import {ExceptionHandler} from './exception_handler';
@ -72,12 +71,11 @@ function _injectorBindings(appComponentType): List<Binding> {
throw new BaseException(`Only Components can be bootstrapped; ` + throw new BaseException(`Only Components can be bootstrapped; ` +
`Directive of ${stringify(type)} is not a Component`); `Directive of ${stringify(type)} is not a Component`);
} }
return compiler.compile(appComponentAnnotatedType.type).then( return compiler.compileRoot(
(protoView) => { appElement,
var appProtoView = ProtoView.createRootProtoView(protoView, appElement, DirectiveBinding.createFromType(appComponentAnnotatedType.type, appComponentAnnotatedType.annotation)
DirectiveBinding.createFromType(appComponentAnnotatedType.type, appComponentAnnotatedType.annotation), ).then(
changeDetection.createProtoChangeDetector('root'), (appProtoView) => {
strategy);
// The light Dom of the app element is not considered part of // The light Dom of the app element is not considered part of
// the angular application. Thus the context and lightDomInjector are // the angular application. Thus the context and lightDomInjector are
// empty. // empty.

View File

@ -85,6 +85,17 @@ export class NewCompiler {
return DirectiveBinding.createFromType(meta.type, meta.annotation); 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> {
return this._renderer.createRootProtoView(elementOrSelector, 'root').then( (rootRenderPv) => {
return this._compileNestedProtoViews(null, rootRenderPv, [componentBinding], true)
}).then( (rootProtoView) => {
rootProtoView.instantiateInPlace = true;
return rootProtoView;
});
}
compile(component: Type):Promise<ProtoView> { compile(component: Type):Promise<ProtoView> {
var protoView = this._compile(this._bindDirective(component)); var protoView = this._compile(this._bindDirective(component));
return PromiseWrapper.isPromise(protoView) ? protoView : PromiseWrapper.resolve(protoView); return PromiseWrapper.isPromise(protoView) ? protoView : PromiseWrapper.resolve(protoView);
@ -96,7 +107,8 @@ export class NewCompiler {
var protoView = this._compilerCache.get(component); var protoView = this._compilerCache.get(component);
if (isPresent(protoView)) { if (isPresent(protoView)) {
// The component has already been compiled into a ProtoView, // The component has already been compiled into a ProtoView,
// returns a resolved Promise. // returns a plain ProtoView, not wrapped inside of a Promise.
// Needed for recursive components.
return protoView; return protoView;
} }
@ -113,31 +125,72 @@ export class NewCompiler {
this._flattenDirectives(template), this._flattenDirectives(template),
(directive) => this._bindDirective(directive) (directive) => this._bindDirective(directive)
); );
var renderTemplate = this._buildRenderTemplate(component, template, directives);
pvPromise = this._compileNoRecurse(componentBinding, template, directives).then( (protoView) => { pvPromise = this._renderer.compile(renderTemplate).then( (renderPv) => {
// Populate the cache before compiling the nested components, return this._compileNestedProtoViews(componentBinding, renderPv, directives, true);
// so that components can reference themselves in their template.
this._compilerCache.set(component, protoView);
MapWrapper.delete(this._compiling, component);
// Compile all the components from the template
var nestedPVPromises = this._compileNestedComponents(protoView);
if (nestedPVPromises.length > 0) {
// Returns ProtoView Promise when there are any asynchronous nested ProtoViews.
// The promise will resolved after nested ProtoViews are compiled.
return PromiseWrapper.then(PromiseWrapper.all(nestedPVPromises),
(_) => protoView,
(e) => { throw new BaseException(`${e} -> Failed to compile ${stringify(component)}`); }
);
}
return protoView;
}); });
MapWrapper.set(this._compiling, component, pvPromise); MapWrapper.set(this._compiling, component, pvPromise);
return pvPromise; return pvPromise;
} }
_compileNoRecurse(componentBinding, template, directives):Promise<ProtoView> { // TODO(tbosch): union type return ProtoView or Promise<ProtoView>
var component = componentBinding.key.token; _compileNestedProtoViews(componentBinding, renderPv, directives, isComponentRootView) {
var nestedPVPromises = [];
var protoView = this._protoViewFactory.createProtoView(componentBinding, renderPv, directives);
if (isComponentRootView && isPresent(componentBinding)) {
// Populate the cache before compiling the nested components,
// so that components can reference themselves in their template.
var component = componentBinding.key.token;
this._compilerCache.set(component, protoView);
MapWrapper.delete(this._compiling, component);
}
var binderIndex = 0;
ListWrapper.forEach(protoView.elementBinders, (elementBinder) => {
var nestedComponent = elementBinder.componentDirective;
var nestedRenderProtoView = renderPv.elementBinders[binderIndex].nestedProtoView;
var elementBinderDone = (nestedPv) => {
elementBinder.nestedProtoView = nestedPv;
// Can't set the parentProtoView for components,
// as their ProtoView might be used in multiple other components.
nestedPv.parentProtoView = isPresent(nestedComponent) ? null : protoView;
};
var nestedCall = null;
if (isPresent(nestedComponent)) {
if (!(nestedComponent.annotation instanceof DynamicComponent)) {
nestedCall = this._compile(nestedComponent);
}
} else if (isPresent(nestedRenderProtoView)) {
nestedCall = this._compileNestedProtoViews(componentBinding, nestedRenderProtoView, directives, false);
}
if (PromiseWrapper.isPromise(nestedCall)) {
ListWrapper.push(nestedPVPromises, nestedCall.then(elementBinderDone));
} else if (isPresent(nestedCall)) {
elementBinderDone(nestedCall);
}
binderIndex++;
});
var protoViewDone = (_) => {
var childComponentRenderPvRefs = [];
ListWrapper.forEach(protoView.elementBinders, (eb) => {
if (isPresent(eb.componentDirective)) {
var componentPv = eb.nestedProtoView;
ListWrapper.push(childComponentRenderPvRefs, isPresent(componentPv) ? componentPv.render : null);
}
});
this._renderer.mergeChildComponentProtoViews(protoView.render, childComponentRenderPvRefs);
return protoView;
};
if (nestedPVPromises.length > 0) {
return PromiseWrapper.all(nestedPVPromises).then(protoViewDone);
} else {
return protoViewDone(null);
}
}
_buildRenderTemplate(component, template, directives) {
var componentUrl = this._urlResolver.resolve( var componentUrl = this._urlResolver.resolve(
this._appUrl, this._componentUrlMapper.getUrl(component) this._appUrl, this._componentUrlMapper.getUrl(component)
); );
@ -150,37 +203,12 @@ export class NewCompiler {
// is able to resolve urls in stylesheets. // is able to resolve urls in stylesheets.
templateAbsUrl = componentUrl; templateAbsUrl = componentUrl;
} }
var renderTemplate = new renderApi.Template({ return new renderApi.Template({
componentId: stringify(component), componentId: stringify(component),
absUrl: templateAbsUrl, absUrl: templateAbsUrl,
inline: template.inline, inline: template.inline,
directives: ListWrapper.map(directives, this._buildRenderDirective) directives: ListWrapper.map(directives, this._buildRenderDirective)
}); });
return this._renderer.compile(renderTemplate).then( (renderPv) => {
return this._protoViewFactory.createProtoView(componentBinding.annotation, renderPv, directives);
});
}
_compileNestedComponents(protoView, nestedPVPromises = null):List<Promise> {
if (isBlank(nestedPVPromises)) {
nestedPVPromises = [];
}
ListWrapper.map(protoView.elementBinders, (elementBinder) => {
var nestedComponent = elementBinder.componentDirective;
if (isPresent(nestedComponent) && !(nestedComponent.annotation instanceof DynamicComponent)) {
var nestedCall = this._compile(nestedComponent);
if (PromiseWrapper.isPromise(nestedCall)) {
ListWrapper.push(nestedPVPromises, nestedCall.then( (nestedPv) => {
elementBinder.nestedProtoView = nestedPv;
}));
} else {
elementBinder.nestedProtoView = nestedCall;
}
} else if (isPresent(elementBinder.nestedProtoView)) {
this._compileNestedComponents(elementBinder.nestedProtoView, nestedPVPromises);
}
});
return nestedPVPromises;
} }
_buildRenderDirective(directiveBinding) { _buildRenderDirective(directiveBinding) {
@ -269,7 +297,7 @@ export class Compiler extends NewCompiler {
new DefaultStepFactory(parser, shadowDomStrategy.render), new DefaultStepFactory(parser, shadowDomStrategy.render),
templateLoader templateLoader
), ),
null, null null, shadowDomStrategy.render
), ),
new ProtoViewFactory(changeDetection, shadowDomStrategy) new ProtoViewFactory(changeDetection, shadowDomStrategy)
); );

View File

@ -20,16 +20,19 @@ export class ProtoViewFactory {
this._shadowDomStrategy = shadowDomStrategy; this._shadowDomStrategy = shadowDomStrategy;
} }
createProtoView(componentAnnotation:Component, renderProtoView: renderApi.ProtoView, directives:List<DirectiveBinding>):ProtoView { createProtoView(componentBinding:DirectiveBinding, renderProtoView: renderApi.ProtoView, directives:List<DirectiveBinding>):ProtoView {
return this._createProtoView(null, componentAnnotation, renderProtoView, directives); var protoChangeDetector;
} if (isBlank(componentBinding)) {
protoChangeDetector = this._changeDetection.createProtoChangeDetector('root', null);
_createProtoView(parent:ProtoView, componentAnnotation:Component, renderProtoView: renderApi.ProtoView, directives:List<DirectiveBinding>):ProtoView { } else {
var protoChangeDetector = this._changeDetection.createProtoChangeDetector('dummy', var componentAnnotation:Component = componentBinding.annotation;
componentAnnotation.changeDetection); protoChangeDetector = this._changeDetection.createProtoChangeDetector(
'dummy', componentAnnotation.changeDetection
);
}
var domProtoView = this._getDomProtoView(renderProtoView.render); var domProtoView = this._getDomProtoView(renderProtoView.render);
var protoView = new ProtoView(domProtoView.element, protoChangeDetector, var protoView = new ProtoView(renderProtoView.render, domProtoView.element, protoChangeDetector,
this._shadowDomStrategy, parent); this._shadowDomStrategy, null);
for (var i=0; i<renderProtoView.elementBinders.length; i++) { for (var i=0; i<renderProtoView.elementBinders.length; i++) {
var renderElementBinder = renderProtoView.elementBinders[i]; var renderElementBinder = renderProtoView.elementBinders[i];
@ -42,13 +45,10 @@ export class ProtoViewFactory {
i, parentPeiWithDistance, i, parentPeiWithDistance,
sortedDirectives, renderElementBinder sortedDirectives, renderElementBinder
); );
var elementBinder = this._createElementBinder( this._createElementBinder(
protoView, renderElementBinder, domElementBinder, protoElementInjector, sortedDirectives protoView, renderElementBinder, domElementBinder, protoElementInjector, sortedDirectives
); );
this._createDirectiveBinders(protoView, sortedDirectives); this._createDirectiveBinders(protoView, sortedDirectives);
if (isPresent(renderElementBinder.nestedProtoView)) {
elementBinder.nestedProtoView = this._createProtoView(protoView, componentAnnotation, renderElementBinder.nestedProtoView, directives);
}
} }
MapWrapper.forEach(renderProtoView.variableBindings, (mappedName, varName) => { MapWrapper.forEach(renderProtoView.variableBindings, (mappedName, varName) => {
protoView.bindVariable(varName, mappedName); protoView.bindVariable(varName, mappedName);

View File

@ -16,6 +16,7 @@ import {Content} from './shadow_dom_emulation/content_tag';
import {ShadowDomStrategy} from './shadow_dom_strategy'; import {ShadowDomStrategy} from './shadow_dom_strategy';
import {ViewPool} from './view_pool'; import {ViewPool} from './view_pool';
import {EventManager} from 'angular2/src/render/dom/events/event_manager'; 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 = 'ng-binding';
const NG_BINDING_CLASS_SELECTOR = '.ng-binding'; const NG_BINDING_CLASS_SELECTOR = '.ng-binding';
@ -283,11 +284,14 @@ export class ProtoView {
_directiveMementosMap:Map; _directiveMementosMap:Map;
_directiveMementos:List; _directiveMementos:List;
render:renderApi.ProtoViewRef;
constructor( constructor(
render:renderApi.ProtoViewRef,
template, template,
protoChangeDetector:ProtoChangeDetector, protoChangeDetector:ProtoChangeDetector,
shadowDomStrategy:ShadowDomStrategy, parentProtoView:ProtoView = null) { shadowDomStrategy:ShadowDomStrategy, parentProtoView:ProtoView = null) {
this.render = render;
this.element = template; this.element = template;
this.elementBinders = []; this.elementBinders = [];
this.variableBindings = MapWrapper.create(); this.variableBindings = MapWrapper.create();
@ -642,28 +646,6 @@ export class ProtoView {
return MapWrapper.get(this._directiveMementosMap, id); return MapWrapper.get(this._directiveMementosMap, id);
} }
// Create a rootView as if the compiler encountered <rootcmp></rootcmp>,
// and the component template is already compiled into protoView.
// Used for bootstrapping.
static createRootProtoView(protoView: ProtoView,
insertionElement,
rootComponentBinding: DirectiveBinding,
protoChangeDetector:ProtoChangeDetector,
shadowDomStrategy: ShadowDomStrategy
): ProtoView {
DOM.addClass(insertionElement, NG_BINDING_CLASS);
var cmpType = rootComponentBinding.key.token;
var rootProtoView = new ProtoView(insertionElement, protoChangeDetector, shadowDomStrategy);
rootProtoView.instantiateInPlace = true;
var binder = rootProtoView.bindElement(null, 0,
new ProtoElementInjector(null, 0, [cmpType], true));
binder.componentDirective = rootComponentBinding;
binder.nestedProtoView = protoView;
shadowDomStrategy.shimAppElement(cmpType, insertionElement);
return rootProtoView;
}
} }
/** /**

View File

@ -141,20 +141,20 @@ export class Renderer {
compile(template:Template):Promise<ProtoView> { return null; } compile(template:Template):Promise<ProtoView> { return null; }
/** /**
* Creates a new ProtoView with preset nested components, * Sets the preset nested components,
* which will be instantiated when this protoView is instantiated. * which will be instantiated when this protoView is instantiated.
* Note: We can't create new ProtoViewRefs here as we need to support cycles / recursive components.
* @param {List<ProtoViewRef>} protoViewRefs * @param {List<ProtoViewRef>} protoViewRefs
* ProtoView for every element with a component in this protoView or in a view container's protoView * ProtoView for every element with a component in this protoView or in a view container's protoView
* @return {List<ProtoViewRef>}
* new ProtoViewRef for the given protoView and all of its view container's protoViews
*/ */
mergeChildComponentProtoViews(protoViewRef:ProtoViewRef, protoViewRefs:List<ProtoViewRef>):List<ProtoViewRef> { return null; } mergeChildComponentProtoViews(protoViewRef:ProtoViewRef, componentProtoViewRefs:List<ProtoViewRef>) { return null; }
/** /**
* Creats a ProtoView that will create a root view for the given element, * Creats a ProtoView that will create a root view for the given element,
* i.e. it will not clone the element but only attach other proto views to it. * i.e. it will not clone the element but only attach other proto views to it.
* Contains a single nested component with the given componentId.
*/ */
createRootProtoView(selectorOrElement):ProtoViewRef { return null; } createRootProtoView(selectorOrElement, componentId):Promise<ProtoView> { return null; }
/** /**
* Creates a view and all of its nested child components. * Creates a view and all of its nested child components.

View File

@ -1,4 +1,4 @@
import {Promise} from 'angular2/src/facade/async'; import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
import {List, ListWrapper} from 'angular2/src/facade/collection'; import {List, ListWrapper} from 'angular2/src/facade/collection';
import {isBlank, isPresent} from 'angular2/src/facade/lang'; import {isBlank, isPresent} from 'angular2/src/facade/lang';
@ -26,10 +26,6 @@ function _wrapView(view:View) {
return new _DirectDomViewRef(view); return new _DirectDomViewRef(view);
} }
function _wrapProtoView(protoView:ProtoView) {
return new DirectDomProtoViewRef(protoView);
}
function _collectComponentChildViewRefs(view, target = null) { function _collectComponentChildViewRefs(view, target = null) {
if (isBlank(target)) { if (isBlank(target)) {
target = []; target = [];
@ -83,22 +79,22 @@ export class DirectDomRenderer extends api.Renderer {
return this._compiler.compile(template); return this._compiler.compile(template);
} }
mergeChildComponentProtoViews(protoViewRef:api.ProtoViewRef, protoViewRefs:List<api.ProtoViewRef>):List<api.ProtoViewRef> { mergeChildComponentProtoViews(protoViewRef:api.ProtoViewRef, protoViewRefs:List<api.ProtoViewRef>) {
var protoViews = [];
_resolveProtoView(protoViewRef).mergeChildComponentProtoViews( _resolveProtoView(protoViewRef).mergeChildComponentProtoViews(
ListWrapper.map(protoViewRefs, _resolveProtoView), ListWrapper.map(protoViewRefs, _resolveProtoView)
protoViews
); );
return ListWrapper.map(protoViews, _wrapProtoView);
} }
createRootProtoView(selectorOrElement):api.ProtoViewRef { createRootProtoView(selectorOrElement, componentId):Promise<api.ProtoView> {
var element = selectorOrElement; // TODO: select the element if it is not a real element... var element = selectorOrElement; // TODO: select the element if it is not a real element...
var rootProtoViewBuilder = new ProtoViewBuilder(element); var rootProtoViewBuilder = new ProtoViewBuilder(element);
rootProtoViewBuilder.setIsRootView(true); rootProtoViewBuilder.setIsRootView(true);
rootProtoViewBuilder.bindElement(element, 'root element').setComponentId('root'); var elBinder = rootProtoViewBuilder.bindElement(element, 'root element');
this._shadowDomStrategy.processElement(null, 'root', element); elBinder.setComponentId(componentId);
return rootProtoViewBuilder.build().render; elBinder.bindDirective(0);
this._shadowDomStrategy.processElement(null, componentId, element);
return PromiseWrapper.resolve(rootProtoViewBuilder.build());
} }
createView(protoViewRef:api.ProtoViewRef):List<api.ViewRef> { createView(protoViewRef:api.ProtoViewRef):List<api.ViewRef> {

View File

@ -1,12 +1,8 @@
import {AST} from 'angular2/change_detection'; import {AST} from 'angular2/change_detection';
import {SetterFn} from 'angular2/src/reflection/types'; import {SetterFn} from 'angular2/src/reflection/types';
import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
import {List, ListWrapper} from 'angular2/src/facade/collection'; import {List, ListWrapper} from 'angular2/src/facade/collection';
import * as protoViewModule from './proto_view'; import * as protoViewModule from './proto_view';
/**
* Note: Code that uses this class assumes that is immutable!
*/
export class ElementBinder { export class ElementBinder {
contentTagSelector: string; contentTagSelector: string;
textNodeIndices: List<number>; textNodeIndices: List<number>;
@ -39,24 +35,4 @@ export class ElementBinder {
this.distanceToParent = distanceToParent; this.distanceToParent = distanceToParent;
this.propertySetters = propertySetters; this.propertySetters = propertySetters;
} }
mergeChildComponentProtoViews(protoViews:List<protoViewModule.ProtoView>, target:List<protoViewModule.ProtoView>):ElementBinder {
var nestedProtoView;
if (isPresent(this.componentId)) {
nestedProtoView = ListWrapper.removeAt(protoViews, 0);
} else if (isPresent(this.nestedProtoView)) {
nestedProtoView = this.nestedProtoView.mergeChildComponentProtoViews(protoViews, target);
}
return new ElementBinder({
parentIndex: this.parentIndex,
textNodeIndices: this.textNodeIndices,
contentTagSelector: this.contentTagSelector,
nestedProtoView: nestedProtoView,
componentId: this.componentId,
eventLocals: this.eventLocals,
eventNames: this.eventNames,
distanceToParent: this.distanceToParent,
propertySetters: this.propertySetters
});
}
} }

View File

@ -6,9 +6,6 @@ import {List, Map, ListWrapper, MapWrapper} from 'angular2/src/facade/collection
import {ElementBinder} from './element_binder'; import {ElementBinder} from './element_binder';
import {NG_BINDING_CLASS} from '../util'; import {NG_BINDING_CLASS} from '../util';
/**
* Note: Code that uses this class assumes that is immutable!
*/
export class ProtoView { export class ProtoView {
element; element;
elementBinders:List<ElementBinder>; elementBinders:List<ElementBinder>;
@ -28,22 +25,14 @@ export class ProtoView {
this.rootBindingOffset = (isPresent(this.element) && DOM.hasClass(this.element, NG_BINDING_CLASS)) ? 1 : 0; this.rootBindingOffset = (isPresent(this.element) && DOM.hasClass(this.element, NG_BINDING_CLASS)) ? 1 : 0;
} }
mergeChildComponentProtoViews(protoViews:List<ProtoView>, target:List<ProtoView>):ProtoView { mergeChildComponentProtoViews(componentProtoViews:List<ProtoView>) {
var elementBinders = ListWrapper.createFixedSize(this.elementBinders.length); var componentProtoViewIndex = 0;
for (var i=0; i<this.elementBinders.length; i++) { for (var i=0; i<this.elementBinders.length; i++) {
var eb = this.elementBinders[i]; var eb = this.elementBinders[i];
if (isPresent(eb.componentId) || isPresent(eb.nestedProtoView)) { if (isPresent(eb.componentId)) {
elementBinders[i] = eb.mergeChildComponentProtoViews(protoViews, target); eb.nestedProtoView = componentProtoViews[componentProtoViewIndex];
} else { componentProtoViewIndex++;
elementBinders[i] = eb;
} }
} }
var result = new ProtoView({
elementBinders: elementBinders,
element: this.element,
isRootView: this.isRootView
});
ListWrapper.insert(target, 0, result);
return result
} }
} }

View File

@ -214,14 +214,12 @@ export function main() {
}); });
})); }));
it('should pass the component annotation', inject([AsyncTestCompleter], (async) => { it('should pass the component binding', inject([AsyncTestCompleter], (async) => {
tplResolver.setTemplate(MainComponent, new Template({inline: '<div></div>'})); tplResolver.setTemplate(MainComponent, new Template({inline: '<div></div>'}));
var compiler = createCompiler([createRenderProtoView()], [createProtoView()]); var compiler = createCompiler([createRenderProtoView()], [createProtoView()]);
compiler.compile(MainComponent).then( (protoView) => { compiler.compile(MainComponent).then( (protoView) => {
var request = protoViewFactory.requests[0]; var request = protoViewFactory.requests[0];
expect(request[0]).toEqual(new Component({ expect(request[0].key.token).toBe(MainComponent);
selector: 'main-comp'
}));
async.done(); async.done();
}); });
})); }));
@ -255,7 +253,7 @@ export function main() {
}); });
it('should load nested components in root ProtoView', inject([AsyncTestCompleter], (async) => { it('should load nested components', inject([AsyncTestCompleter], (async) => {
tplResolver.setTemplate(MainComponent, new Template({inline: '<div></div>'})); tplResolver.setTemplate(MainComponent, new Template({inline: '<div></div>'}));
tplResolver.setTemplate(NestedComponent, new Template({inline: '<div></div>'})); tplResolver.setTemplate(NestedComponent, new Template({inline: '<div></div>'}));
var mainProtoView = createProtoView([ var mainProtoView = createProtoView([
@ -263,34 +261,54 @@ export function main() {
]); ]);
var nestedProtoView = createProtoView(); var nestedProtoView = createProtoView();
var compiler = createCompiler( var compiler = createCompiler(
[createRenderProtoView(), createRenderProtoView()], [
createRenderProtoView([createRenderComponentElementBinder(0)]),
createRenderProtoView()
],
[mainProtoView, nestedProtoView] [mainProtoView, nestedProtoView]
); );
compiler.compile(MainComponent).then( (protoView) => { compiler.compile(MainComponent).then( (protoView) => {
expect(protoView).toBe(mainProtoView); expect(protoView).toBe(mainProtoView);
expect(mainProtoView.elementBinders[0].nestedProtoView).toBe(nestedProtoView); expect(mainProtoView.elementBinders[0].nestedProtoView).toBe(nestedProtoView);
// parentProtoView of nested components has to be null as components can
// be used by multiple other components.
expect(nestedProtoView.parentProtoView).toBe(null);
async.done(); async.done();
}); });
})); }));
it('should load nested components in viewport ProtoView', inject([AsyncTestCompleter], (async) => { it('should load nested components in viewport', inject([AsyncTestCompleter], (async) => {
tplResolver.setTemplate(MainComponent, new Template({inline: '<div></div>'})); tplResolver.setTemplate(MainComponent, new Template({inline: '<div></div>'}));
tplResolver.setTemplate(NestedComponent, new Template({inline: '<div></div>'})); tplResolver.setTemplate(NestedComponent, new Template({inline: '<div></div>'}));
var mainProtoView = createProtoView([ var mainProtoView = createProtoView([
createViewportElementBinder(createProtoView([ createViewportElementBinder(null)
createComponentElementBinder(reader, NestedComponent) ]);
])) var viewportProtoView = createProtoView([
createComponentElementBinder(reader, NestedComponent)
]); ]);
var nestedProtoView = createProtoView(); var nestedProtoView = createProtoView();
var compiler = createCompiler( var compiler = createCompiler(
[createRenderProtoView(), createRenderProtoView()], [
[mainProtoView, nestedProtoView] createRenderProtoView([
createRenderViewportElementBinder(
createRenderProtoView([
createRenderComponentElementBinder(0)
])
)
]),
createRenderProtoView()
],
[mainProtoView, viewportProtoView, nestedProtoView]
); );
compiler.compile(MainComponent).then( (protoView) => { compiler.compile(MainComponent).then( (protoView) => {
expect(protoView).toBe(mainProtoView); expect(protoView).toBe(mainProtoView);
expect( expect(mainProtoView.elementBinders[0].nestedProtoView).toBe(viewportProtoView);
mainProtoView.elementBinders[0].nestedProtoView.elementBinders[0].nestedProtoView expect(viewportProtoView.parentProtoView).toBe(mainProtoView);
).toBe(nestedProtoView); expect(viewportProtoView.elementBinders[0].nestedProtoView).toBe(nestedProtoView);
// parentProtoView of nested components has to be null as components can
// be used by multiple other components.
expect(nestedProtoView.parentProtoView).toBe(null);
async.done(); async.done();
}); });
})); }));
@ -331,7 +349,9 @@ export function main() {
createComponentElementBinder(reader, MainComponent) createComponentElementBinder(reader, MainComponent)
]); ]);
var compiler = createCompiler( var compiler = createCompiler(
[createRenderProtoView()], [createRenderProtoView([
createRenderComponentElementBinder(0)
])],
[mainProtoView] [mainProtoView]
); );
compiler.compile(MainComponent).then( (protoView) => { compiler.compile(MainComponent).then( (protoView) => {
@ -340,20 +360,44 @@ export function main() {
async.done(); async.done();
}); });
})); }));
it('should create root proto views', inject([AsyncTestCompleter], (async) => {
tplResolver.setTemplate(MainComponent, new Template({inline: '<div></div>'}));
var rootProtoView = createProtoView([
createComponentElementBinder(reader, MainComponent)
]);
var mainProtoView = createProtoView();
var compiler = createCompiler(
[
createRenderProtoView()
],
[rootProtoView, mainProtoView]
);
compiler.compileRoot(null, createDirectiveBinding(reader, MainComponent)).then( (protoView) => {
expect(protoView).toBe(rootProtoView);
expect(rootProtoView.elementBinders[0].nestedProtoView).toBe(mainProtoView);
async.done();
});
}));
}); });
} }
function createDirectiveBinding(reader, type) {
var meta = reader.read(type);
return DirectiveBinding.createFromType(meta.type, meta.annotation);
}
function createProtoView(elementBinders = null) { function createProtoView(elementBinders = null) {
var pv = new ProtoView(null, null, null, null); var pv = new ProtoView(null, null, null, null, null);
if (isPresent(elementBinders)) { if (isBlank(elementBinders)) {
pv.elementBinders = elementBinders; elementBinders = [];
} }
pv.elementBinders = elementBinders;
return pv; return pv;
} }
function createComponentElementBinder(reader, type) { function createComponentElementBinder(reader, type) {
var meta = reader.read(type); var binding = createDirectiveBinding(reader, type);
var binding = DirectiveBinding.createFromType(meta.type, meta.annotation);
return new ElementBinder( return new ElementBinder(
0, null, 0, 0, null, 0,
null, binding, null, binding,
@ -371,8 +415,27 @@ function createViewportElementBinder(nestedProtoView) {
return elBinder; return elBinder;
} }
function createRenderProtoView() { function createRenderProtoView(elementBinders = null) {
return new renderApi.ProtoView(); if (isBlank(elementBinders)) {
elementBinders = [];
}
return new renderApi.ProtoView({
elementBinders: elementBinders
});
}
function createRenderComponentElementBinder(directiveIndex) {
return new renderApi.ElementBinder({
directives: [new renderApi.DirectiveBinder({
directiveIndex: directiveIndex
})]
});
}
function createRenderViewportElementBinder(nestedProtoView) {
return new renderApi.ElementBinder({
nestedProtoView: nestedProtoView
});
} }
@Component({ @Component({
@ -433,6 +496,12 @@ class FakeRenderer extends renderApi.Renderer {
ListWrapper.push(this.requests, template); ListWrapper.push(this.requests, template);
return PromiseWrapper.resolve(ListWrapper.removeAt(this._results, 0)); return PromiseWrapper.resolve(ListWrapper.removeAt(this._results, 0));
} }
createRootProtoView(elementOrSelector, componentId):Promise<renderApi.ProtoView> {
return PromiseWrapper.resolve(
createRenderProtoView([createRenderComponentElementBinder(0)])
);
}
} }
class FakeUrlResolver extends UrlResolver { class FakeUrlResolver extends UrlResolver {
@ -481,8 +550,8 @@ class FakeProtoViewFactory extends ProtoViewFactory {
this._results = results; this._results = results;
} }
createProtoView(componentAnnotation:Component, renderProtoView: renderApi.ProtoView, directives:List<DirectiveBinding>):ProtoView { createProtoView(componentBinding:DirectiveBinding, renderProtoView: renderApi.ProtoView, directives:List<DirectiveBinding>):ProtoView {
ListWrapper.push(this.requests, [componentAnnotation, renderProtoView, directives]); ListWrapper.push(this.requests, [componentBinding, renderProtoView, directives]);
return ListWrapper.removeAt(this._results, 0); return ListWrapper.removeAt(this._results, 0);
} }
} }

View File

@ -577,7 +577,7 @@ export function main() {
function createpreBuildObject(eventName, eventHandler) { function createpreBuildObject(eventName, eventHandler) {
var handlers = StringMapWrapper.create(); var handlers = StringMapWrapper.create();
StringMapWrapper.set(handlers, eventName, eventHandler); StringMapWrapper.set(handlers, eventName, eventHandler);
var pv = new ProtoView(null, null, null); var pv = new ProtoView(null, null, null, null);
pv.eventHandlers = [handlers]; pv.eventHandlers = [handlers];
var view = new View(pv, null, MapWrapper.create()); var view = new View(pv, null, MapWrapper.create());
return new PreBuiltObjects(view, null, null, null); return new PreBuiltObjects(view, null, null, null);

View File

@ -43,7 +43,7 @@ export function main() {
it('should attach the view nodes to the shadow root', () => { it('should attach the view nodes to the shadow root', () => {
var host = el('<div></div>'); var host = el('<div></div>');
var nodes = el('<div>view</div>'); var nodes = el('<div>view</div>');
var pv = new ProtoView(nodes, new DynamicProtoChangeDetector(null, null), null); var pv = new ProtoView(null, nodes, new DynamicProtoChangeDetector(null, null), null);
var view = pv.instantiate(null, null); var view = pv.instantiate(null, null);
strategy.attachTemplate(host, view); strategy.attachTemplate(host, view);
@ -68,7 +68,7 @@ export function main() {
it('should attach the view nodes as child of the host element', () => { it('should attach the view nodes as child of the host element', () => {
var host = el('<div><span>original content</span></div>'); var host = el('<div><span>original content</span></div>');
var nodes = el('<div>view</div>'); var nodes = el('<div>view</div>');
var pv = new ProtoView(nodes, new DynamicProtoChangeDetector(null, null), null); var pv = new ProtoView(null, nodes, new DynamicProtoChangeDetector(null, null), null);
var view = pv.instantiate(null, null); var view = pv.instantiate(null, null);
strategy.attachTemplate(host, view); strategy.attachTemplate(host, view);
@ -92,7 +92,7 @@ export function main() {
it('should attach the view nodes as child of the host element', () => { it('should attach the view nodes as child of the host element', () => {
var host = el('<div><span>original content</span></div>'); var host = el('<div><span>original content</span></div>');
var nodes = el('<div>view</div>'); var nodes = el('<div>view</div>');
var pv = new ProtoView(nodes, new DynamicProtoChangeDetector(null, null), null); var pv = new ProtoView(null, nodes, new DynamicProtoChangeDetector(null, null), null);
var view = pv.instantiate(null, null); var view = pv.instantiate(null, null);
strategy.attachTemplate(host, view); strategy.attachTemplate(host, view);

View File

@ -71,7 +71,7 @@ export function main() {
dom = el(`<div><stuff></stuff><div insert-after-me></div><stuff></stuff></div>`); dom = el(`<div><stuff></stuff><div insert-after-me></div><stuff></stuff></div>`);
var insertionElement = dom.childNodes[1]; var insertionElement = dom.childNodes[1];
parentView = createView([dom.childNodes[0]]); parentView = createView([dom.childNodes[0]]);
protoView = new ProtoView(el('<div>hi</div>'), new DynamicProtoChangeDetector(null, null), protoView = new ProtoView(null, el('<div>hi</div>'), new DynamicProtoChangeDetector(null, null),
new NativeShadowDomStrategy(null)); new NativeShadowDomStrategy(null));
elementInjector = new ElementInjector(null, null, null); elementInjector = new ElementInjector(null, null, null);
viewContainer = new ViewContainer(parentView, insertionElement, protoView, elementInjector, viewContainer = new ViewContainer(parentView, insertionElement, protoView, elementInjector,
@ -216,7 +216,7 @@ export function main() {
var parser = new Parser(new Lexer()); var parser = new Parser(new Lexer());
viewContainer.hydrate(new Injector([]), null, null); viewContainer.hydrate(new Injector([]), null, null);
var pv = new ProtoView(el('<div class="ng-binding">{{}}</div>'), var pv = new ProtoView(null, el('<div class="ng-binding">{{}}</div>'),
new DynamicProtoChangeDetector(null, null), new NativeShadowDomStrategy(null)); new DynamicProtoChangeDetector(null, null), new NativeShadowDomStrategy(null));
pv.bindElement(null, 0, new ProtoElementInjector(null, 1, [SomeDirective])); pv.bindElement(null, 0, new ProtoElementInjector(null, 1, [SomeDirective]));
pv.bindTextNode(0, parser.parseBinding('foo', null)); pv.bindTextNode(0, parser.parseBinding('foo', null));

View File

@ -63,7 +63,7 @@ export function main() {
describe('instantiated from protoView', () => { describe('instantiated from protoView', () => {
var view; var view;
beforeEach(() => { beforeEach(() => {
var pv = new ProtoView(el('<div id="1"></div>'), new DynamicProtoChangeDetector(null, null), null); var pv = new ProtoView(null, el('<div id="1"></div>'), new DynamicProtoChangeDetector(null, null), null);
view = pv.instantiate(null, null); view = pv.instantiate(null, null);
}); });
@ -90,7 +90,7 @@ export function main() {
}); });
it('should use the view pool to reuse views', () => { it('should use the view pool to reuse views', () => {
var pv = new ProtoView(el('<div id="1"></div>'), new DynamicProtoChangeDetector(null, null), null); var pv = new ProtoView(null, el('<div id="1"></div>'), new DynamicProtoChangeDetector(null, null), null);
var fakeView = new FakeView(); var fakeView = new FakeView();
pv.returnToPool(fakeView); pv.returnToPool(fakeView);
@ -101,7 +101,7 @@ export function main() {
describe('with locals', function() { describe('with locals', function() {
var view; var view;
beforeEach(() => { beforeEach(() => {
var pv = new ProtoView(el('<div id="1"></div>'), new DynamicProtoChangeDetector(null, null), null); var pv = new ProtoView(null, el('<div id="1"></div>'), new DynamicProtoChangeDetector(null, null), null);
pv.bindVariable('context-foo', 'template-foo'); pv.bindVariable('context-foo', 'template-foo');
view = createView(pv); view = createView(pv);
}); });
@ -137,7 +137,7 @@ export function main() {
} }
it('should collect the root node in the ProtoView element', () => { it('should collect the root node in the ProtoView element', () => {
var pv = new ProtoView(templateAwareCreateElement('<div id="1"></div>'), var pv = new ProtoView(null, templateAwareCreateElement('<div id="1"></div>'),
new DynamicProtoChangeDetector(null, null), null); new DynamicProtoChangeDetector(null, null), null);
var view = pv.instantiate(null, null); var view = pv.instantiate(null, null);
view.hydrate(null, null, null, null, null); view.hydrate(null, null, null, null, null);
@ -148,7 +148,7 @@ export function main() {
describe('collect elements with property bindings', () => { describe('collect elements with property bindings', () => {
it('should collect property bindings on the root element if it has the ng-binding class', () => { it('should collect property bindings on the root element if it has the ng-binding class', () => {
var pv = new ProtoView(templateAwareCreateElement('<div [prop]="a" class="ng-binding"></div>'), var pv = new ProtoView(null, templateAwareCreateElement('<div [prop]="a" class="ng-binding"></div>'),
new DynamicProtoChangeDetector(null, null), null); new DynamicProtoChangeDetector(null, null), null);
pv.bindElement(null, 0, null); pv.bindElement(null, 0, null);
pv.bindElementProperty(parser.parseBinding('a', null), 'prop', reflector.setter('prop')); pv.bindElementProperty(parser.parseBinding('a', null), 'prop', reflector.setter('prop'));
@ -160,7 +160,7 @@ export function main() {
}); });
it('should collect property bindings on child elements with ng-binding class', () => { it('should collect property bindings on child elements with ng-binding class', () => {
var pv = new ProtoView(templateAwareCreateElement('<div><span></span><span class="ng-binding"></span></div>'), var pv = new ProtoView(null, templateAwareCreateElement('<div><span></span><span class="ng-binding"></span></div>'),
new DynamicProtoChangeDetector(null, null), null); new DynamicProtoChangeDetector(null, null), null);
pv.bindElement(null, 0, null); pv.bindElement(null, 0, null);
pv.bindElementProperty(parser.parseBinding('b', null), 'a', reflector.setter('a')); pv.bindElementProperty(parser.parseBinding('b', null), 'a', reflector.setter('a'));
@ -176,7 +176,7 @@ export function main() {
describe('collect text nodes with bindings', () => { describe('collect text nodes with bindings', () => {
it('should collect text nodes under the root element', () => { it('should collect text nodes under the root element', () => {
var pv = new ProtoView(templateAwareCreateElement('<div class="ng-binding">{{}}<span></span>{{}}</div>'), var pv = new ProtoView(null, templateAwareCreateElement('<div class="ng-binding">{{}}<span></span>{{}}</div>'),
new DynamicProtoChangeDetector(null, null), null); new DynamicProtoChangeDetector(null, null), null);
pv.bindElement(null, 0, null); pv.bindElement(null, 0, null);
pv.bindTextNode(0, parser.parseBinding('a', null)); pv.bindTextNode(0, parser.parseBinding('a', null));
@ -190,7 +190,7 @@ export function main() {
}); });
it('should collect text nodes with bindings on child elements with ng-binding class', () => { it('should collect text nodes with bindings on child elements with ng-binding class', () => {
var pv = new ProtoView(templateAwareCreateElement('<div><span> </span><span class="ng-binding">{{}}</span></div>'), var pv = new ProtoView(null, templateAwareCreateElement('<div><span> </span><span class="ng-binding">{{}}</span></div>'),
new DynamicProtoChangeDetector(null, null), null); new DynamicProtoChangeDetector(null, null), null);
pv.bindElement(null, 0, null); pv.bindElement(null, 0, null);
pv.bindTextNode(0, parser.parseBinding('b', null)); pv.bindTextNode(0, parser.parseBinding('b', null));
@ -207,7 +207,7 @@ export function main() {
describe('inplace instantiation', () => { describe('inplace instantiation', () => {
it('should be supported.', () => { it('should be supported.', () => {
var template = el('<div></div>'); var template = el('<div></div>');
var pv = new ProtoView(template, new DynamicProtoChangeDetector(null, null), var pv = new ProtoView(null, template, new DynamicProtoChangeDetector(null, null),
new NativeShadowDomStrategy(null)); new NativeShadowDomStrategy(null));
pv.instantiateInPlace = true; pv.instantiateInPlace = true;
var view = pv.instantiate(null, null); var view = pv.instantiate(null, null);
@ -217,7 +217,7 @@ export function main() {
it('should be off by default.', () => { it('should be off by default.', () => {
var template = el('<div></div>') var template = el('<div></div>')
var pv = new ProtoView(template, new DynamicProtoChangeDetector(null, null), var pv = new ProtoView(null, template, new DynamicProtoChangeDetector(null, null),
new NativeShadowDomStrategy(null)) new NativeShadowDomStrategy(null))
var view = pv.instantiate(null, null); var view = pv.instantiate(null, null);
view.hydrate(null, null, null, null, null); view.hydrate(null, null, null, null, null);
@ -235,7 +235,7 @@ export function main() {
describe('create ElementInjectors', () => { describe('create ElementInjectors', () => {
it('should use the directives of the ProtoElementInjector', () => { it('should use the directives of the ProtoElementInjector', () => {
var pv = new ProtoView(el('<div class="ng-binding"></div>'), var pv = new ProtoView(null, el('<div class="ng-binding"></div>'),
new DynamicProtoChangeDetector(null, null), null); new DynamicProtoChangeDetector(null, null), null);
pv.bindElement(null, 0, new ProtoElementInjector(null, 1, [SomeDirective])); pv.bindElement(null, 0, new ProtoElementInjector(null, 1, [SomeDirective]));
@ -246,7 +246,7 @@ export function main() {
}); });
it('should use the correct parent', () => { it('should use the correct parent', () => {
var pv = new ProtoView(el('<div class="ng-binding"><span class="ng-binding"></span></div>'), var pv = new ProtoView(null, el('<div class="ng-binding"><span class="ng-binding"></span></div>'),
new DynamicProtoChangeDetector(null, null), null); new DynamicProtoChangeDetector(null, null), null);
var protoParent = new ProtoElementInjector(null, 0, [SomeDirective]); var protoParent = new ProtoElementInjector(null, 0, [SomeDirective]);
pv.bindElement(null, 0, protoParent); pv.bindElement(null, 0, protoParent);
@ -260,7 +260,7 @@ export function main() {
}); });
it('should not pass the host injector when a parent injector exists', () => { it('should not pass the host injector when a parent injector exists', () => {
var pv = new ProtoView(el('<div class="ng-binding"><span class="ng-binding"></span></div>'), var pv = new ProtoView(null, el('<div class="ng-binding"><span class="ng-binding"></span></div>'),
new DynamicProtoChangeDetector(null, null), null); new DynamicProtoChangeDetector(null, null), null);
var protoParent = new ProtoElementInjector(null, 0, [SomeDirective]); var protoParent = new ProtoElementInjector(null, 0, [SomeDirective]);
pv.bindElement(null, 0, protoParent); pv.bindElement(null, 0, protoParent);
@ -276,7 +276,7 @@ export function main() {
}); });
it('should pass the host injector when there is no parent injector', () => { it('should pass the host injector when there is no parent injector', () => {
var pv = new ProtoView(el('<div class="ng-binding"><span class="ng-binding"></span></div>'), var pv = new ProtoView(null, el('<div class="ng-binding"><span class="ng-binding"></span></div>'),
new DynamicProtoChangeDetector(null, null), null); new DynamicProtoChangeDetector(null, null), null);
pv.bindElement(null, 0, new ProtoElementInjector(null, 0, [SomeDirective])); pv.bindElement(null, 0, new ProtoElementInjector(null, 0, [SomeDirective]));
var testProtoElementInjector = new TestProtoElementInjector(null, 1, [AnotherDirective]); var testProtoElementInjector = new TestProtoElementInjector(null, 1, [AnotherDirective]);
@ -293,7 +293,7 @@ export function main() {
describe('collect root element injectors', () => { describe('collect root element injectors', () => {
it('should collect a single root element injector', () => { it('should collect a single root element injector', () => {
var pv = new ProtoView(el('<div class="ng-binding"><span class="ng-binding"></span></div>'), var pv = new ProtoView(null, el('<div class="ng-binding"><span class="ng-binding"></span></div>'),
new DynamicProtoChangeDetector(null, null), null); new DynamicProtoChangeDetector(null, null), null);
var protoParent = new ProtoElementInjector(null, 0, [SomeDirective]); var protoParent = new ProtoElementInjector(null, 0, [SomeDirective]);
pv.bindElement(null, 0, protoParent); pv.bindElement(null, 0, protoParent);
@ -306,7 +306,7 @@ export function main() {
}); });
it('should collect multiple root element injectors', () => { it('should collect multiple root element injectors', () => {
var pv = new ProtoView(el('<div><span class="ng-binding"></span><span class="ng-binding"></span></div>'), var pv = new ProtoView(null, el('<div><span class="ng-binding"></span><span class="ng-binding"></span></div>'),
new DynamicProtoChangeDetector(null, null), null); new DynamicProtoChangeDetector(null, null), null);
pv.bindElement(null, 0, new ProtoElementInjector(null, 1, [SomeDirective])); pv.bindElement(null, 0, new ProtoElementInjector(null, 1, [SomeDirective]));
pv.bindElement(null, 0, new ProtoElementInjector(null, 2, [AnotherDirective])); pv.bindElement(null, 0, new ProtoElementInjector(null, 2, [AnotherDirective]));
@ -324,7 +324,7 @@ export function main() {
var ctx; var ctx;
function createComponentWithSubPV(subProtoView) { function createComponentWithSubPV(subProtoView) {
var pv = new ProtoView(el('<cmp class="ng-binding"></cmp>'), var pv = new ProtoView(null, el('<cmp class="ng-binding"></cmp>'),
new DynamicProtoChangeDetector(null, null), new NativeShadowDomStrategy(null)); new DynamicProtoChangeDetector(null, null), new NativeShadowDomStrategy(null));
var binder = pv.bindElement(null, 0, new ProtoElementInjector(null, 0, [SomeComponent], true)); var binder = pv.bindElement(null, 0, new ProtoElementInjector(null, 0, [SomeComponent], true));
binder.componentDirective = someComponentDirective; binder.componentDirective = someComponentDirective;
@ -340,7 +340,7 @@ export function main() {
} }
it('should expose component services to the component', () => { it('should expose component services to the component', () => {
var subpv = new ProtoView(el('<span></span>'), new DynamicProtoChangeDetector(null, null), null); var subpv = new ProtoView(null, el('<span></span>'), new DynamicProtoChangeDetector(null, null), null);
var pv = createComponentWithSubPV(subpv); var pv = createComponentWithSubPV(subpv);
var view = createNestedView(pv); var view = createNestedView(pv);
@ -351,7 +351,7 @@ export function main() {
it('should expose component services and component instance to directives in the shadow Dom', it('should expose component services and component instance to directives in the shadow Dom',
() => { () => {
var subpv = new ProtoView( var subpv = new ProtoView(null,
el('<div dec class="ng-binding">hello shadow dom</div>'), el('<div dec class="ng-binding">hello shadow dom</div>'),
new DynamicProtoChangeDetector(null, null), new DynamicProtoChangeDetector(null, null),
null); null);
@ -376,7 +376,7 @@ export function main() {
} }
it('dehydration should dehydrate child component views too', () => { it('dehydration should dehydrate child component views too', () => {
var subpv = new ProtoView( var subpv = new ProtoView(null,
el('<div dec class="ng-binding">hello shadow dom</div>'), el('<div dec class="ng-binding">hello shadow dom</div>'),
new DynamicProtoChangeDetector(null, null), new DynamicProtoChangeDetector(null, null),
null); null);
@ -394,7 +394,7 @@ export function main() {
}); });
it('should create shadow dom (Native Strategy)', () => { it('should create shadow dom (Native Strategy)', () => {
var subpv = new ProtoView(el('<span>hello shadow dom</span>'), var subpv = new ProtoView(null, el('<span>hello shadow dom</span>'),
new DynamicProtoChangeDetector(null, null), new DynamicProtoChangeDetector(null, null),
null); null);
var pv = createComponentWithSubPV(subpv); var pv = createComponentWithSubPV(subpv);
@ -405,10 +405,10 @@ export function main() {
}); });
it('should emulate shadow dom (Emulated Strategy)', () => { it('should emulate shadow dom (Emulated Strategy)', () => {
var subpv = new ProtoView(el('<span>hello shadow dom</span>'), var subpv = new ProtoView(null, el('<span>hello shadow dom</span>'),
new DynamicProtoChangeDetector(null, null), null); new DynamicProtoChangeDetector(null, null), null);
var pv = new ProtoView(el('<cmp class="ng-binding"></cmp>'), var pv = new ProtoView(null, el('<cmp class="ng-binding"></cmp>'),
new DynamicProtoChangeDetector(null, null), new EmulatedScopedShadowDomStrategy(null, null, null)); new DynamicProtoChangeDetector(null, null), new EmulatedScopedShadowDomStrategy(null, null, null));
var binder = pv.bindElement(null, 0, new ProtoElementInjector(null, 0, [SomeComponent], true)); var binder = pv.bindElement(null, 0, new ProtoElementInjector(null, 0, [SomeComponent], true));
binder.componentDirective = readDirectiveBinding(SomeComponent); binder.componentDirective = readDirectiveBinding(SomeComponent);
@ -422,9 +422,9 @@ export function main() {
describe('with template views', () => { describe('with template views', () => {
function createViewWithViewport() { function createViewWithViewport() {
var templateProtoView = new ProtoView( var templateProtoView = new ProtoView(null,
el('<div id="1"></div>'), new DynamicProtoChangeDetector(null, null), null); el('<div id="1"></div>'), new DynamicProtoChangeDetector(null, null), null);
var pv = new ProtoView(el('<someTmpl class="ng-binding"></someTmpl>'), var pv = new ProtoView(null, el('<someTmpl class="ng-binding"></someTmpl>'),
new DynamicProtoChangeDetector(null, null), new NativeShadowDomStrategy(null)); new DynamicProtoChangeDetector(null, null), new NativeShadowDomStrategy(null));
var binder = pv.bindElement(null, 0, new ProtoElementInjector(null, 0, [SomeViewport])); var binder = pv.bindElement(null, 0, new ProtoElementInjector(null, 0, [SomeViewport]));
binder.viewportDirective = someViewportDirective; binder.viewportDirective = someViewportDirective;
@ -470,7 +470,7 @@ export function main() {
} }
function createProtoView() { function createProtoView() {
var pv = new ProtoView(el('<div class="ng-binding"><div></div></div>'), var pv = new ProtoView(null, el('<div class="ng-binding"><div></div></div>'),
new DynamicProtoChangeDetector(null, null), null); new DynamicProtoChangeDetector(null, null), null);
pv.bindElement(null, 0, new TestProtoElementInjector(null, 0, [])); pv.bindElement(null, 0, new TestProtoElementInjector(null, 0, []));
pv.bindEvent('click', parser.parseBinding('callMe($event)', null)); pv.bindEvent('click', parser.parseBinding('callMe($event)', null));
@ -505,7 +505,7 @@ export function main() {
}); });
it('should support custom event emitters', () => { it('should support custom event emitters', () => {
var pv = new ProtoView(el('<div class="ng-binding"><div></div></div>'), var pv = new ProtoView(null, el('<div class="ng-binding"><div></div></div>'),
new DynamicProtoChangeDetector(null, null), null); new DynamicProtoChangeDetector(null, null), null);
pv.bindElement(null, 0, new TestProtoElementInjector(null, 0, [EventEmitterDirective])); pv.bindElement(null, 0, new TestProtoElementInjector(null, 0, [EventEmitterDirective]));
pv.bindEvent('click', parser.parseBinding('callMe($event)', null)); pv.bindEvent('click', parser.parseBinding('callMe($event)', null));
@ -526,7 +526,7 @@ export function main() {
}); });
it('should bind to directive events', () => { it('should bind to directive events', () => {
var pv = new ProtoView(el('<div class="ng-binding"></div>'), var pv = new ProtoView(null, el('<div class="ng-binding"></div>'),
new DynamicProtoChangeDetector(null, null), null); new DynamicProtoChangeDetector(null, null), null);
pv.bindElement(null, 0, new ProtoElementInjector(null, 0, [SomeDirectiveWithEventHandler])); pv.bindElement(null, 0, new ProtoElementInjector(null, 0, [SomeDirectiveWithEventHandler]));
pv.bindEvent('click', parser.parseAction('onEvent($event)', null), 0); pv.bindEvent('click', parser.parseAction('onEvent($event)', null), 0);
@ -551,7 +551,7 @@ export function main() {
} }
it('should consume text node changes', () => { it('should consume text node changes', () => {
var pv = new ProtoView(el('<div class="ng-binding">{{}}</div>'), var pv = new ProtoView(null, el('<div class="ng-binding">{{}}</div>'),
new DynamicProtoChangeDetector(null, null), null); new DynamicProtoChangeDetector(null, null), null);
pv.bindElement(null, 0, null); pv.bindElement(null, 0, null);
pv.bindTextNode(0, parser.parseBinding('foo', null)); pv.bindTextNode(0, parser.parseBinding('foo', null));
@ -563,7 +563,7 @@ export function main() {
}); });
it('should consume element binding changes', () => { it('should consume element binding changes', () => {
var pv = new ProtoView(el('<div class="ng-binding"></div>'), var pv = new ProtoView(null, el('<div class="ng-binding"></div>'),
new DynamicProtoChangeDetector(null, null), null); new DynamicProtoChangeDetector(null, null), null);
pv.bindElement(null, 0, null); pv.bindElement(null, 0, null);
pv.bindElementProperty(parser.parseBinding('foo', null), 'id', reflector.setter('id')); pv.bindElementProperty(parser.parseBinding('foo', null), 'id', reflector.setter('id'));
@ -575,7 +575,7 @@ export function main() {
}); });
it('should consume directive watch expression change', () => { it('should consume directive watch expression change', () => {
var pv = new ProtoView(el('<div class="ng-binding"></div>'), var pv = new ProtoView(null, el('<div class="ng-binding"></div>'),
new DynamicProtoChangeDetector(null, null), null); new DynamicProtoChangeDetector(null, null), null);
pv.bindElement(null, 0, new ProtoElementInjector(null, 0, [SomeDirective])); pv.bindElement(null, 0, new ProtoElementInjector(null, 0, [SomeDirective]));
pv.bindDirectiveProperty(0, parser.parseBinding('foo', null), 'prop', reflector.setter('prop')); pv.bindDirectiveProperty(0, parser.parseBinding('foo', null), 'prop', reflector.setter('prop'));
@ -587,7 +587,7 @@ export function main() {
}); });
it('should notify a directive about changes after all its properties have been set', () => { it('should notify a directive about changes after all its properties have been set', () => {
var pv = new ProtoView(el('<div class="ng-binding"></div>'), var pv = new ProtoView(null, el('<div class="ng-binding"></div>'),
new DynamicProtoChangeDetector(null, null), null); new DynamicProtoChangeDetector(null, null), null);
pv.bindElement(null, 0, new ProtoElementInjector(null, 0, [ pv.bindElement(null, 0, new ProtoElementInjector(null, 0, [
@ -607,7 +607,7 @@ export function main() {
}); });
it('should provide a map of updated properties using onChange callback', () => { it('should provide a map of updated properties using onChange callback', () => {
var pv = new ProtoView(el('<div class="ng-binding"></div>'), var pv = new ProtoView(null, el('<div class="ng-binding"></div>'),
new DynamicProtoChangeDetector(null, null), null); new DynamicProtoChangeDetector(null, null), null);
pv.bindElement(null, 0, new ProtoElementInjector(null, 0, [ pv.bindElement(null, 0, new ProtoElementInjector(null, 0, [
@ -634,7 +634,7 @@ export function main() {
}); });
it('should invoke the onAllChangesDone callback', () => { it('should invoke the onAllChangesDone callback', () => {
var pv = new ProtoView(el('<div class="ng-binding"></div>'), var pv = new ProtoView(null, el('<div class="ng-binding"></div>'),
new DynamicProtoChangeDetector(null, null), null); new DynamicProtoChangeDetector(null, null), null);
pv.bindElement(null, 0, new ProtoElementInjector(null, 0, [ pv.bindElement(null, 0, new ProtoElementInjector(null, 0, [
@ -651,32 +651,6 @@ export function main() {
}); });
}); });
describe('protoView createRootProtoView', () => {
var element, pv;
beforeEach(() => {
element = DOM.createElement('div');
pv = new ProtoView(el('<div>hi</div>'), new DynamicProtoChangeDetector(null, null),
new NativeShadowDomStrategy(null));
});
it('should create the root component when instantiated', () => {
var rootProtoView = ProtoView.createRootProtoView(pv, element,
someComponentDirective, new DynamicProtoChangeDetector(null, null),
new NativeShadowDomStrategy(null));
var view = rootProtoView.instantiate(null, null);
view.hydrate(new Injector([]), null, null, null, null);
expect(view.rootElementInjectors[0].get(SomeComponent)).not.toBe(null);
});
it('should inject the protoView into the shadowDom', () => {
var rootProtoView = ProtoView.createRootProtoView(pv, element,
someComponentDirective, new DynamicProtoChangeDetector(null, null),
new NativeShadowDomStrategy(null));
var view = rootProtoView.instantiate(null, null);
view.hydrate(new Injector([]), null, null, null, null);
expect(element.shadowRoot.childNodes[0].childNodes[0].nodeValue).toEqual('hi');
});
});
}); });
} }

View File

@ -21,7 +21,11 @@ import {IntegrationTestbed, LoggingEventDispatcher, FakeEvent} from './integrati
export function main() { export function main() {
describe('DirectDomRenderer integration', () => { describe('DirectDomRenderer integration', () => {
var testbed, renderer, rootEl, rootProtoViewRef, eventPlugin, compile; var testbed, renderer, eventPlugin, compile, rootEl;
beforeEach(() => {
rootEl = el('<div></div>');
});
function createRenderer({urlData, viewCacheCapacity, shadowDomStrategy, templates}={}) { function createRenderer({urlData, viewCacheCapacity, shadowDomStrategy, templates}={}) {
testbed = new IntegrationTestbed({ testbed = new IntegrationTestbed({
@ -31,54 +35,66 @@ export function main() {
templates: templates templates: templates
}); });
renderer = testbed.renderer; renderer = testbed.renderer;
rootEl = testbed.rootEl;
rootProtoViewRef = testbed.rootProtoViewRef;
eventPlugin = testbed.eventPlugin; eventPlugin = testbed.eventPlugin;
compile = (template, directives) => testbed.compile(template, directives); compile = (rootEl, componentId) => testbed.compile(rootEl, componentId);
} }
it('should create root views while using the given elements in place', () => { it('should create root views while using the given elements in place', inject([AsyncTestCompleter], (async) => {
createRenderer(); createRenderer();
var viewRefs = renderer.createView(rootProtoViewRef); renderer.createRootProtoView(rootEl, 'someComponentId').then( (rootProtoView) => {
expect(viewRefs.length).toBe(1); expect(rootProtoView.elementBinders[0].directives[0].directiveIndex).toBe(0);
expect(viewRefs[0].delegate.rootNodes[0]).toEqual(rootEl); var viewRefs = renderer.createView(rootProtoView.render);
}); expect(viewRefs.length).toBe(1);
expect(viewRefs[0].delegate.rootNodes[0]).toEqual(rootEl);
async.done();
});
}));
it('should add a static component', inject([AsyncTestCompleter], (async) => { it('should add a static component', inject([AsyncTestCompleter], (async) => {
createRenderer(); createRenderer();
var template = new Template({ renderer.createRootProtoView(rootEl, 'someComponentId').then( (rootProtoView) => {
componentId: 'someComponent', var template = new Template({
inline: 'hello', componentId: 'someComponent',
directives: [] inline: 'hello',
}); directives: []
renderer.compile(template).then( (pv) => { });
var mergedProtoViewRefs = renderer.mergeChildComponentProtoViews(rootProtoViewRef, [pv.render]); renderer.compile(template).then( (pv) => {
renderer.createView(mergedProtoViewRefs[0]); renderer.mergeChildComponentProtoViews(rootProtoView.render, [pv.render]);
expect(rootEl).toHaveText('hello'); renderer.createView(rootProtoView.render);
async.done(); expect(rootEl).toHaveText('hello');
async.done();
});
}); });
})); }));
it('should add a a dynamic component', inject([AsyncTestCompleter], (async) => { it('should add a a dynamic component', inject([AsyncTestCompleter], (async) => {
createRenderer(); createRenderer();
var template = new Template({ renderer.createRootProtoView(rootEl, 'someComponentId').then( (rootProtoView) => {
componentId: 'someComponent', var template = new Template({
inline: 'hello', componentId: 'someComponent',
directives: [] inline: 'hello',
}); directives: []
renderer.compile(template).then( (pv) => { });
var rootViewRef = renderer.createView(rootProtoViewRef)[0]; renderer.compile(template).then( (pv) => {
var childComponentViewRef = renderer.createView(pv.render)[0]; var rootViewRef = renderer.createView(rootProtoView.render)[0];
renderer.setDynamicComponentView(rootViewRef, 0, childComponentViewRef); var childComponentViewRef = renderer.createView(pv.render)[0];
expect(rootEl).toHaveText('hello'); renderer.setDynamicComponentView(rootViewRef, 0, childComponentViewRef);
async.done(); expect(rootEl).toHaveText('hello');
async.done();
});
}); });
})); }));
it('should update text nodes', inject([AsyncTestCompleter], (async) => { it('should update text nodes', inject([AsyncTestCompleter], (async) => {
createRenderer(); createRenderer({
compile('{{a}}', [someComponent]).then( (pvRefs) => { templates: [new Template({
var viewRefs = renderer.createView(pvRefs[0]); componentId: 'someComponent',
inline: '{{a}}',
directives: []
})]
});
compile(rootEl, 'someComponent').then( (rootProtoView) => {
var viewRefs = renderer.createView(rootProtoView.render);
renderer.setText(viewRefs[1], 0, 'hello'); renderer.setText(viewRefs[1], 0, 'hello');
expect(rootEl).toHaveText('hello'); expect(rootEl).toHaveText('hello');
async.done(); async.done();
@ -86,9 +102,15 @@ export function main() {
})); }));
it('should update element properties', inject([AsyncTestCompleter], (async) => { it('should update element properties', inject([AsyncTestCompleter], (async) => {
createRenderer(); createRenderer({
compile('<input [value]="someProp">', []).then( (pvRefs) => { templates: [new Template({
var viewRefs = renderer.createView(pvRefs[0]); componentId: 'someComponent',
inline: '<input [value]="someProp">',
directives: []
})]
});
compile(rootEl, 'someComponent').then( (rootProtoView) => {
var viewRefs = renderer.createView(rootProtoView.render);
renderer.setElementProperty(viewRefs[1], 0, 'value', 'hello'); renderer.setElementProperty(viewRefs[1], 0, 'value', 'hello');
expect(DOM.childNodes(rootEl)[0].value).toEqual('hello'); expect(DOM.childNodes(rootEl)[0].value).toEqual('hello');
async.done(); async.done();
@ -96,10 +118,17 @@ export function main() {
})); }));
it('should add and remove views to and from containers', inject([AsyncTestCompleter], (async) => { it('should add and remove views to and from containers', inject([AsyncTestCompleter], (async) => {
createRenderer(); createRenderer({
compile('<template>hello</template>', []).then( (pvRefs) => { templates: [new Template({
var viewRef = renderer.createView(pvRefs[0])[1]; componentId: 'someComponent',
var vcProtoViewRef = pvRefs[2]; inline: '<template>hello</template>',
directives: []
})]
});
compile(rootEl, 'someComponent').then( (rootProtoView) => {
var viewRef = renderer.createView(rootProtoView.render)[1];
var vcProtoViewRef = rootProtoView.elementBinders[0]
.nestedProtoView.elementBinders[0].nestedProtoView.render;
var vcRef = new ViewContainerRef(viewRef, 0); var vcRef = new ViewContainerRef(viewRef, 0);
var childViewRef = renderer.createView(vcProtoViewRef)[0]; var childViewRef = renderer.createView(vcProtoViewRef)[0];
@ -115,10 +144,17 @@ export function main() {
it('should cache views', inject([AsyncTestCompleter], (async) => { it('should cache views', inject([AsyncTestCompleter], (async) => {
createRenderer({ createRenderer({
templates: [new Template({
componentId: 'someComponent',
inline: '<template>hello</template>',
directives: []
})],
viewCacheCapacity: 2 viewCacheCapacity: 2
}); });
compile('<template>hello</template>', []).then( (pvRefs) => { compile(rootEl, 'someComponent').then( (rootProtoView) => {
var vcProtoViewRef = pvRefs[2]; var vcProtoViewRef = rootProtoView.elementBinders[0]
.nestedProtoView.elementBinders[0].nestedProtoView.render;
var viewRef1 = renderer.createView(vcProtoViewRef)[0]; var viewRef1 = renderer.createView(vcProtoViewRef)[0];
renderer.destroyView(viewRef1); renderer.destroyView(viewRef1);
var viewRef2 = renderer.createView(vcProtoViewRef)[0]; var viewRef2 = renderer.createView(vcProtoViewRef)[0];
@ -133,9 +169,15 @@ export function main() {
// TODO(tbosch): This is not working yet as we commented out // TODO(tbosch): This is not working yet as we commented out
// the event expression processing... // the event expression processing...
xit('should handle events', inject([AsyncTestCompleter], (async) => { xit('should handle events', inject([AsyncTestCompleter], (async) => {
createRenderer(); createRenderer({
compile('<input (change)="$event.target.value">', []).then( (pvRefs) => { templates: [new Template({
var viewRef = renderer.createView(pvRefs[0])[1]; componentId: 'someComponent',
inline: '<input (change)="$event.target.value">',
directives: []
})]
});
compile(rootEl, 'someComponent').then( (rootProtoView) => {
var viewRef = renderer.createView(rootProtoView.render)[1];
var dispatcher = new LoggingEventDispatcher(); var dispatcher = new LoggingEventDispatcher();
renderer.setEventDispatcher(viewRef, dispatcher); renderer.setEventDispatcher(viewRef, dispatcher);
var inputEl = DOM.childNodes(rootEl)[0]; var inputEl = DOM.childNodes(rootEl)[0];

View File

@ -1,7 +1,3 @@
import {
el
} from 'angular2/test_lib';
import {isBlank, isPresent, BaseException} from 'angular2/src/facade/lang'; import {isBlank, isPresent, BaseException} from 'angular2/src/facade/lang';
import {MapWrapper, ListWrapper, List} from 'angular2/src/facade/collection'; import {MapWrapper, ListWrapper, List} from 'angular2/src/facade/collection';
import {PromiseWrapper, Promise} from 'angular2/src/facade/async'; import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
@ -23,11 +19,8 @@ import {ViewFactory} from 'angular2/src/render/dom/view/view_factory';
export class IntegrationTestbed { export class IntegrationTestbed {
renderer; renderer;
parser; parser;
rootEl;
rootProtoViewRef;
eventPlugin; eventPlugin;
_templates:Map<string, Template>; _templates:Map<string, Template>;
_compileCache:Map<string, Promise<List>>;
constructor({urlData, viewCacheCapacity, shadowDomStrategy, templates}) { constructor({urlData, viewCacheCapacity, shadowDomStrategy, templates}) {
this._templates = MapWrapper.create(); this._templates = MapWrapper.create();
@ -36,7 +29,6 @@ export class IntegrationTestbed {
MapWrapper.set(this._templates, template.componentId, template); MapWrapper.set(this._templates, template.componentId, template);
}); });
} }
this._compileCache = MapWrapper.create();
var parser = new Parser(new Lexer()); var parser = new Parser(new Lexer());
var urlResolver = new UrlResolver(); var urlResolver = new UrlResolver();
if (isBlank(shadowDomStrategy)) { if (isBlank(shadowDomStrategy)) {
@ -54,90 +46,66 @@ export class IntegrationTestbed {
var eventManager = new EventManager([this.eventPlugin], new FakeVmTurnZone()); var eventManager = new EventManager([this.eventPlugin], new FakeVmTurnZone());
var viewFactory = new ViewFactory(viewCacheCapacity, eventManager, shadowDomStrategy); var viewFactory = new ViewFactory(viewCacheCapacity, eventManager, shadowDomStrategy);
this.renderer = new DirectDomRenderer(compiler, viewFactory, shadowDomStrategy); this.renderer = new DirectDomRenderer(compiler, viewFactory, shadowDomStrategy);
this.rootEl = el('<div></div>');
this.rootProtoViewRef = this.renderer.createRootProtoView(this.rootEl);
} }
compile(templateHtml, directives):Promise<List<ProtoViewRef>> { compile(rootEl, componentId):Promise<ProtoView> {
return this._compileRecurse(new Template({ return this.renderer.createRootProtoView(rootEl, componentId).then( (rootProtoView) => {
componentId: 'root', return this._compileNestedProtoViews(rootProtoView, [
inline: templateHtml, new DirectiveMetadata({
directives: directives type: DirectiveMetadata.COMPONENT_TYPE,
})).then( (protoViewRefs) => { id: componentId
return this._flattenList([ })
this.renderer.mergeChildComponentProtoViews(this.rootProtoViewRef, [protoViewRefs[0]]),
protoViewRefs
]); ]);
}); });
} }
_compileRecurse(template):Promise<List<ProtoViewRef>> { _compile(template):Promise<ProtoView> {
var result = MapWrapper.get(this._compileCache, template.componentId); return this.renderer.compile(template).then( (protoView) => {
if (isPresent(result)) { return this._compileNestedProtoViews(protoView, template.directives);
return result;
}
result = this.renderer.compile(template).then( (pv) => {
var childComponentPromises = ListWrapper.map(
this._findNestedComponentIds(template, pv),
(componentId) => {
var childTemplate = MapWrapper.get(this._templates, componentId);
if (isBlank(childTemplate)) {
throw new BaseException(`Could not find template for ${componentId}!`);
}
return this._compileRecurse(childTemplate);
}
);
return PromiseWrapper.all(childComponentPromises).then(
(protoViewRefsWithChildren) => {
var protoViewRefs =
ListWrapper.map(protoViewRefsWithChildren, (arr) => arr[0]);
return this._flattenList([
this.renderer.mergeChildComponentProtoViews(pv.render, protoViewRefs),
protoViewRefsWithChildren
]);
}
);
}); });
MapWrapper.set(this._compileCache, template.componentId, result);
return result;
} }
_findNestedComponentIds(template, pv, target = null):List<string> { _compileNestedProtoViews(protoView, directives):Promise<ProtoView> {
if (isBlank(target)) { var childComponentRenderPvRefs = [];
target = []; var nestedPVPromises = [];
} ListWrapper.forEach(protoView.elementBinders, (elementBinder) => {
for (var binderIdx=0; binderIdx<pv.elementBinders.length; binderIdx++) { var nestedComponentId = null;
var eb = pv.elementBinders[binderIdx]; ListWrapper.forEach(elementBinder.directives, (db) => {
var componentDirective; var directiveMeta = directives[db.directiveIndex];
ListWrapper.forEach(eb.directives, (db) => { if (directiveMeta.type === DirectiveMetadata.COMPONENT_TYPE) {
var meta = template.directives[db.directiveIndex]; nestedComponentId = directiveMeta.id;
if (meta.type === DirectiveMetadata.COMPONENT_TYPE) {
componentDirective = meta;
} }
}); });
if (isPresent(componentDirective)) { var nestedCall;
ListWrapper.push(target, componentDirective.id); if (isPresent(nestedComponentId)) {
} else if (isPresent(eb.nestedProtoView)) { var childTemplate = MapWrapper.get(this._templates, nestedComponentId);
this._findNestedComponentIds(template, eb.nestedProtoView, target); if (isBlank(childTemplate)) {
throw new BaseException(`Could not find template for ${nestedComponentId}!`);
}
nestedCall = this._compile(childTemplate);
} else if (isPresent(elementBinder.nestedProtoView)) {
nestedCall = this._compileNestedProtoViews(elementBinder.nestedProtoView, directives);
} }
} if (isPresent(nestedCall)) {
return target; ListWrapper.push(
} nestedPVPromises,
nestedCall.then( (nestedPv) => {
_flattenList(tree:List, out:List = null):List { elementBinder.nestedProtoView = nestedPv;
if (isBlank(out)) { if (isPresent(nestedComponentId)) {
out = []; ListWrapper.push(childComponentRenderPvRefs, nestedPv.render);
} }
for (var i = 0; i < tree.length; i++) { })
var item = tree[i]; );
if (ListWrapper.isList(item)) {
this._flattenList(item, out);
} else {
ListWrapper.push(out, item);
} }
});
if (nestedPVPromises.length > 0) {
return PromiseWrapper.all(nestedPVPromises).then((_) => {
this.renderer.mergeChildComponentProtoViews(protoView.render, childComponentRenderPvRefs);
return protoView;
});
} else {
return PromiseWrapper.resolve(protoView);
} }
return out;
} }
} }

View File

@ -40,34 +40,45 @@ export function main() {
StringMapWrapper.set(strategies, "native", () => new NativeShadowDomStrategy(styleUrlResolver)); StringMapWrapper.set(strategies, "native", () => new NativeShadowDomStrategy(styleUrlResolver));
} }
beforeEach( () => {
urlResolver = new UrlResolver();
styleUrlResolver = new StyleUrlResolver(urlResolver);
styleInliner = new StyleInliner(null, styleUrlResolver, urlResolver);
});
StringMapWrapper.forEach(strategies, StringMapWrapper.forEach(strategies,
(strategyFactory, name) => { (strategyFactory, name) => {
describe(`${name} shadow dom strategy`, () => { describe(`${name} shadow dom strategy`, () => {
var testbed, renderer, rootEl, compile, strategy; var testbed, renderer, rootEl, compile;
beforeEach( () => { function createRenderer({templates}) {
urlResolver = new UrlResolver();
styleUrlResolver = new StyleUrlResolver(urlResolver);
styleInliner = new StyleInliner(null, styleUrlResolver, urlResolver);
strategy = strategyFactory();
testbed = new IntegrationTestbed({ testbed = new IntegrationTestbed({
shadowDomStrategy: strategy, shadowDomStrategy: strategyFactory(),
templates: templates templates: ListWrapper.concat(templates, componentTemplates)
}); });
renderer = testbed.renderer; renderer = testbed.renderer;
rootEl = testbed.rootEl; compile = (rootEl, componentId) => testbed.compile(rootEl, componentId);
compile = (template, directives) => testbed.compile(template, directives); }
beforeEach( () => {
rootEl = el('<div></div>');
}); });
it('should support simple components', inject([AsyncTestCompleter], (async) => { it('should support simple components', inject([AsyncTestCompleter], (async) => {
var temp = '<simple>' + createRenderer({
'<div>A</div>' + templates: [new Template({
'</simple>'; componentId: 'main',
inline: '<simple>' +
compile(temp, [simple]).then( (pvRefs) => { '<div>A</div>' +
renderer.createView(pvRefs[0]); '</simple>',
directives: [simple]
})]
});
compile(rootEl, 'main').then( (pv) => {
renderer.createView(pv.render);
expect(rootEl).toHaveText('SIMPLE(A)'); expect(rootEl).toHaveText('SIMPLE(A)');
@ -76,14 +87,19 @@ export function main() {
})); }));
it('should support multiple content tags', inject([AsyncTestCompleter], (async) => { it('should support multiple content tags', inject([AsyncTestCompleter], (async) => {
var temp = '<multiple-content-tags>' + createRenderer({
'<div>B</div>' + templates: [new Template({
'<div>C</div>' + componentId: 'main',
'<div class="left">A</div>' + inline: '<multiple-content-tags>' +
'</multiple-content-tags>'; '<div>B</div>' +
'<div>C</div>' +
compile(temp, [multipleContentTagsComponent]).then( (pvRefs) => { '<div class="left">A</div>' +
renderer.createView(pvRefs[0]); '</multiple-content-tags>',
directives: [multipleContentTagsComponent]
})]
});
compile(rootEl, 'main').then( (pv) => {
renderer.createView(pv.render);
expect(rootEl).toHaveText('(A, BC)'); expect(rootEl).toHaveText('(A, BC)');
@ -92,13 +108,18 @@ export function main() {
})); }));
it('should redistribute only direct children', inject([AsyncTestCompleter], (async) => { it('should redistribute only direct children', inject([AsyncTestCompleter], (async) => {
var temp = '<multiple-content-tags>' + createRenderer({
'<div>B<div class="left">A</div></div>' + templates: [new Template({
'<div>C</div>' + componentId: 'main',
'</multiple-content-tags>'; inline: '<multiple-content-tags>' +
'<div>B<div class="left">A</div></div>' +
compile(temp, [multipleContentTagsComponent]).then( (pvRefs) => { '<div>C</div>' +
renderer.createView(pvRefs[0]); '</multiple-content-tags>',
directives: [multipleContentTagsComponent]
})]
});
compile(rootEl, 'main').then( (pv) => {
renderer.createView(pv.render);
expect(rootEl).toHaveText('(, BAC)'); expect(rootEl).toHaveText('(, BAC)');
@ -107,15 +128,21 @@ export function main() {
})); }));
it("should redistribute direct child viewcontainers when the light dom changes", inject([AsyncTestCompleter], (async) => { it("should redistribute direct child viewcontainers when the light dom changes", inject([AsyncTestCompleter], (async) => {
var temp = '<multiple-content-tags>' + createRenderer({
'<div><div template="manual" class="left">A</div></div>' + templates: [new Template({
'<div>B</div>' + componentId: 'main',
'</multiple-content-tags>'; inline: '<multiple-content-tags>' +
'<div><div template="manual" class="left">A</div></div>' +
compile(temp, [multipleContentTagsComponent, manualViewportDirective]).then( (pvRefs) => { '<div>B</div>' +
var viewRefs = renderer.createView(pvRefs[0]); '</multiple-content-tags>',
directives: [multipleContentTagsComponent, manualViewportDirective]
})]
});
compile(rootEl, 'main').then( (pv) => {
var viewRefs = renderer.createView(pv.render);
var vcRef = new ViewContainerRef(viewRefs[1], 1); var vcRef = new ViewContainerRef(viewRefs[1], 1);
var vcProtoViewRef = pvRefs[2]; var vcProtoViewRef = pv.elementBinders[0].nestedProtoView
.elementBinders[1].nestedProtoView.render;
var childViewRef = renderer.createView(vcProtoViewRef)[0]; var childViewRef = renderer.createView(vcProtoViewRef)[0];
expect(rootEl).toHaveText('(, B)'); expect(rootEl).toHaveText('(, B)');
@ -133,15 +160,21 @@ export function main() {
})); }));
it("should redistribute when the light dom changes", inject([AsyncTestCompleter], (async) => { it("should redistribute when the light dom changes", inject([AsyncTestCompleter], (async) => {
var temp = '<multiple-content-tags>' + createRenderer({
'<div template="manual" class="left">A</div>' + templates: [new Template({
'<div>B</div>' + componentId: 'main',
'</multiple-content-tags>'; inline: '<multiple-content-tags>' +
'<div template="manual" class="left">A</div>' +
compile(temp, [multipleContentTagsComponent, manualViewportDirective]).then( (pvRefs) => { '<div>B</div>' +
var viewRefs = renderer.createView(pvRefs[0]); '</multiple-content-tags>',
directives: [multipleContentTagsComponent, manualViewportDirective]
})]
});
compile(rootEl, 'main').then( (pv) => {
var viewRefs = renderer.createView(pv.render);
var vcRef = new ViewContainerRef(viewRefs[1], 1); var vcRef = new ViewContainerRef(viewRefs[1], 1);
var vcProtoViewRef = pvRefs[2]; var vcProtoViewRef = pv.elementBinders[0].nestedProtoView
.elementBinders[1].nestedProtoView.render;
var childViewRef = renderer.createView(vcProtoViewRef)[0]; var childViewRef = renderer.createView(vcProtoViewRef)[0];
expect(rootEl).toHaveText('(, B)'); expect(rootEl).toHaveText('(, B)');
@ -159,13 +192,18 @@ export function main() {
})); }));
it("should support nested components", inject([AsyncTestCompleter], (async) => { it("should support nested components", inject([AsyncTestCompleter], (async) => {
var temp = '<outer-with-indirect-nested>' + createRenderer({
'<div>A</div>' + templates: [new Template({
'<div>B</div>' + componentId: 'main',
'</outer-with-indirect-nested>'; inline: '<outer-with-indirect-nested>' +
'<div>A</div>' +
compile(temp, [outerWithIndirectNestedComponent]).then( (pvRefs) => { '<div>B</div>' +
renderer.createView(pvRefs[0]); '</outer-with-indirect-nested>',
directives: [outerWithIndirectNestedComponent]
})]
});
compile(rootEl, 'main').then( (pv) => {
renderer.createView(pv.render);
expect(rootEl).toHaveText('OUTER(SIMPLE(AB))'); expect(rootEl).toHaveText('OUTER(SIMPLE(AB))');
@ -174,16 +212,22 @@ export function main() {
})); }));
it("should support nesting with content being direct child of a nested component", inject([AsyncTestCompleter], (async) => { it("should support nesting with content being direct child of a nested component", inject([AsyncTestCompleter], (async) => {
var temp = '<outer>' + createRenderer({
'<div template="manual" class="left">A</div>' + templates: [new Template({
'<div>B</div>' + componentId: 'main',
'<div>C</div>' + inline: '<outer>' +
'</outer>'; '<div template="manual" class="left">A</div>' +
'<div>B</div>' +
compile(temp, [outerComponent, manualViewportDirective]).then( (pvRefs) => { '<div>C</div>' +
var viewRefs = renderer.createView(pvRefs[0]); '</outer>',
directives: [outerComponent, manualViewportDirective]
})]
});
compile(rootEl, 'main').then( (pv) => {
var viewRefs = renderer.createView(pv.render);
var vcRef = new ViewContainerRef(viewRefs[1], 1); var vcRef = new ViewContainerRef(viewRefs[1], 1);
var vcProtoViewRef = pvRefs[2]; var vcProtoViewRef = pv.elementBinders[0].nestedProtoView
.elementBinders[1].nestedProtoView.render;
var childViewRef = renderer.createView(vcProtoViewRef)[0]; var childViewRef = renderer.createView(vcProtoViewRef)[0];
expect(rootEl).toHaveText('OUTER(INNER(INNERINNER(,BC)))'); expect(rootEl).toHaveText('OUTER(INNER(INNERINNER(,BC)))');
@ -196,16 +240,23 @@ export function main() {
})); }));
it('should redistribute when the shadow dom changes', inject([AsyncTestCompleter], (async) => { it('should redistribute when the shadow dom changes', inject([AsyncTestCompleter], (async) => {
var temp = '<conditional-content>' + createRenderer({
'<div class="left">A</div>' + templates: [new Template({
'<div>B</div>' + componentId: 'main',
'<div>C</div>' + inline: '<conditional-content>' +
'</conditional-content>'; '<div class="left">A</div>' +
'<div>B</div>' +
compile(temp, [conditionalContentComponent, autoViewportDirective]).then( (pvRefs) => { '<div>C</div>' +
var viewRefs = renderer.createView(pvRefs[0]); '</conditional-content>',
directives: [conditionalContentComponent]
})]
});
compile(rootEl, 'main').then( (pv) => {
var viewRefs = renderer.createView(pv.render);
var vcRef = new ViewContainerRef(viewRefs[2], 0); var vcRef = new ViewContainerRef(viewRefs[2], 0);
var vcProtoViewRef = pvRefs[3]; var vcProtoViewRef = pv.elementBinders[0].nestedProtoView
.elementBinders[0].nestedProtoView
.elementBinders[0].nestedProtoView.render;
var childViewRef = renderer.createView(vcProtoViewRef)[0]; var childViewRef = renderer.createView(vcProtoViewRef)[0];
expect(rootEl).toHaveText('(, ABC)'); expect(rootEl).toHaveText('(, ABC)');
@ -299,7 +350,7 @@ var autoViewportDirective = new DirectiveMetadata({
type: DirectiveMetadata.VIEWPORT_TYPE type: DirectiveMetadata.VIEWPORT_TYPE
}); });
var templates = [ var componentTemplates = [
new Template({ new Template({
componentId: 'simple', componentId: 'simple',
inline: 'SIMPLE(<content></content>)', inline: 'SIMPLE(<content></content>)',