feat(compiler): attach components and project light dom during compilation.
Closes #2529 BREAKING CHANGES: - shadow dom emulation no longer supports the `<content>` tag. Use the new `<ng-content>` instead (works with all shadow dom strategies). - removed `DomRenderer.setViewRootNodes` and `AppViewManager.getComponentView` -> use `DomRenderer.getNativeElementSync(elementRef)` and change shadow dom directly - the `Renderer` interface has changed: * `createView` now also has to support sub views * the notion of a container has been removed. Instead, the renderer has to implement methods to attach views next to elements or other views. * a RenderView now contains multiple RenderFragments. Fragments are used to move DOM nodes around. Internal changes / design changes: - Introduce notion of view fragments on render side - DomProtoViews and DomViews on render side are merged, AppProtoViews are not merged, AppViews are partially merged (they share arrays with the other merged AppViews but we keep individual AppView instances for now). - DomProtoViews always have a `<template>` element as root * needed for storing subviews * we have less chunks of DOM to clone now - remove fake ElementBinder / Bound element for root text bindings and model them explicitly. This removes a lot of special cases we had! - AppView shares data with nested component views - some methods in AppViewManager (create, hydrate, dehydrate) are iterative now * now possible as we have all child AppViews / ElementRefs already in an array!
This commit is contained in:
parent
d449ea5ca4
commit
b1df54501a
@ -58,11 +58,13 @@ export {
|
|||||||
|
|
||||||
export * from './http';
|
export * from './http';
|
||||||
export {
|
export {
|
||||||
EventDispatcher,
|
RenderEventDispatcher,
|
||||||
Renderer,
|
Renderer,
|
||||||
RenderElementRef,
|
RenderElementRef,
|
||||||
RenderViewRef,
|
RenderViewRef,
|
||||||
RenderProtoViewRef
|
RenderProtoViewRef,
|
||||||
|
RenderFragmentRef,
|
||||||
|
RenderViewWithFragments
|
||||||
} from 'angular2/src/render/api';
|
} from 'angular2/src/render/api';
|
||||||
export {
|
export {
|
||||||
DomRenderer,
|
DomRenderer,
|
||||||
|
@ -137,7 +137,7 @@ class Pane { | Component controller class
|
|||||||
<div class="outer">
|
<div class="outer">
|
||||||
<h1>{{title}}</h1>
|
<h1>{{title}}</h1>
|
||||||
<div class="inner" [hidden]="!open">
|
<div class="inner" [hidden]="!open">
|
||||||
<content></content>
|
<ng-content></ng-content>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
```
|
```
|
||||||
|
@ -15,8 +15,7 @@ import {List, ListWrapper, Map, MapWrapper} from 'angular2/src/facade/collection
|
|||||||
|
|
||||||
import {DirectiveResolver} from './directive_resolver';
|
import {DirectiveResolver} from './directive_resolver';
|
||||||
|
|
||||||
import {AppProtoView} from './view';
|
import {AppProtoView, AppProtoViewMergeMapping} from './view';
|
||||||
import {ElementBinder} from './element_binder';
|
|
||||||
import {ProtoViewRef} from './view_ref';
|
import {ProtoViewRef} from './view_ref';
|
||||||
import {DirectiveBinding} from './element_injector';
|
import {DirectiveBinding} from './element_injector';
|
||||||
import {ViewResolver} from './view_resolver';
|
import {ViewResolver} from './view_resolver';
|
||||||
@ -91,6 +90,7 @@ export class Compiler {
|
|||||||
private _appUrl: string;
|
private _appUrl: string;
|
||||||
private _render: renderApi.RenderCompiler;
|
private _render: renderApi.RenderCompiler;
|
||||||
private _protoViewFactory: ProtoViewFactory;
|
private _protoViewFactory: ProtoViewFactory;
|
||||||
|
private _unmergedCyclicEmbeddedProtoViews: RecursiveEmbeddedProtoView[] = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
@ -137,13 +137,17 @@ export class Compiler {
|
|||||||
Compiler._assertTypeIsComponent(componentBinding);
|
Compiler._assertTypeIsComponent(componentBinding);
|
||||||
|
|
||||||
var directiveMetadata = componentBinding.metadata;
|
var directiveMetadata = componentBinding.metadata;
|
||||||
hostPvPromise = this._render.compileHost(directiveMetadata)
|
hostPvPromise =
|
||||||
.then((hostRenderPv) => {
|
this._render.compileHost(directiveMetadata)
|
||||||
return this._compileNestedProtoViews(componentBinding, hostRenderPv,
|
.then((hostRenderPv) => {
|
||||||
[componentBinding]);
|
var protoView = this._protoViewFactory.createAppProtoViews(
|
||||||
});
|
componentBinding, hostRenderPv, [componentBinding]);
|
||||||
|
this._compilerCache.setHost(componentType, protoView);
|
||||||
|
return this._compileNestedProtoViews(hostRenderPv, protoView, componentType);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return hostPvPromise.then((hostAppProtoView) => { return new ProtoViewRef(hostAppProtoView); });
|
return hostPvPromise.then(hostAppProtoView => this._mergeCyclicEmbeddedProtoViews().then(
|
||||||
|
_ => new ProtoViewRef(hostAppProtoView)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private _compile(componentBinding: DirectiveBinding): Promise<AppProtoView>| AppProtoView {
|
private _compile(componentBinding: DirectiveBinding): Promise<AppProtoView>| AppProtoView {
|
||||||
@ -156,12 +160,12 @@ export class Compiler {
|
|||||||
return protoView;
|
return protoView;
|
||||||
}
|
}
|
||||||
|
|
||||||
var pvPromise = this._compiling.get(component);
|
var resultPromise = this._compiling.get(component);
|
||||||
if (isPresent(pvPromise)) {
|
if (isPresent(resultPromise)) {
|
||||||
// The component is already being compiled, attach to the existing Promise
|
// The component is already being compiled, attach to the existing Promise
|
||||||
// instead of re-compiling the component.
|
// instead of re-compiling the component.
|
||||||
// It happens when a template references a component multiple times.
|
// It happens when a template references a component multiple times.
|
||||||
return pvPromise;
|
return resultPromise;
|
||||||
}
|
}
|
||||||
var view = this._viewResolver.resolve(component);
|
var view = this._viewResolver.resolve(component);
|
||||||
|
|
||||||
@ -178,14 +182,19 @@ export class Compiler {
|
|||||||
ListWrapper.map(directives, (directive) => this._bindDirective(directive)));
|
ListWrapper.map(directives, (directive) => this._bindDirective(directive)));
|
||||||
|
|
||||||
var renderTemplate = this._buildRenderTemplate(component, view, boundDirectives);
|
var renderTemplate = this._buildRenderTemplate(component, view, boundDirectives);
|
||||||
pvPromise =
|
resultPromise = this._render.compile(renderTemplate)
|
||||||
this._render.compile(renderTemplate)
|
.then((renderPv) => {
|
||||||
.then((renderPv) => {
|
var protoView = this._protoViewFactory.createAppProtoViews(
|
||||||
return this._compileNestedProtoViews(componentBinding, renderPv, boundDirectives);
|
componentBinding, renderPv, boundDirectives);
|
||||||
});
|
// Populate the cache before compiling the nested components,
|
||||||
|
// so that components can reference themselves in their template.
|
||||||
|
this._compilerCache.set(component, protoView);
|
||||||
|
MapWrapper.delete(this._compiling, component);
|
||||||
|
|
||||||
this._compiling.set(component, pvPromise);
|
return this._compileNestedProtoViews(renderPv, protoView, component);
|
||||||
return pvPromise;
|
});
|
||||||
|
this._compiling.set(component, resultPromise);
|
||||||
|
return resultPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _removeDuplicatedDirectives(directives: List<DirectiveBinding>): List<DirectiveBinding> {
|
private _removeDuplicatedDirectives(directives: List<DirectiveBinding>): List<DirectiveBinding> {
|
||||||
@ -194,24 +203,11 @@ export class Compiler {
|
|||||||
return MapWrapper.values(directivesMap);
|
return MapWrapper.values(directivesMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _compileNestedProtoViews(componentBinding, renderPv, directives): Promise<AppProtoView>|
|
private _compileNestedProtoViews(renderProtoView: renderApi.ProtoViewDto,
|
||||||
AppProtoView {
|
appProtoView: AppProtoView,
|
||||||
var protoViews =
|
componentType: Type): Promise<AppProtoView> {
|
||||||
this._protoViewFactory.createAppProtoViews(componentBinding, renderPv, directives);
|
|
||||||
var protoView = protoViews[0];
|
|
||||||
if (isPresent(componentBinding)) {
|
|
||||||
var component = componentBinding.key.token;
|
|
||||||
if (renderPv.type === renderApi.ViewType.COMPONENT) {
|
|
||||||
// Populate the cache before compiling the nested components,
|
|
||||||
// so that components can reference themselves in their template.
|
|
||||||
this._compilerCache.set(component, protoView);
|
|
||||||
MapWrapper.delete(this._compiling, component);
|
|
||||||
} else {
|
|
||||||
this._compilerCache.setHost(component, protoView);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var nestedPVPromises = [];
|
var nestedPVPromises = [];
|
||||||
ListWrapper.forEach(this._collectComponentElementBinders(protoViews), (elementBinder) => {
|
this._loopComponentElementBinders(appProtoView, (parentPv, elementBinder) => {
|
||||||
var nestedComponent = elementBinder.componentDirective;
|
var nestedComponent = elementBinder.componentDirective;
|
||||||
var elementBinderDone =
|
var elementBinderDone =
|
||||||
(nestedPv: AppProtoView) => { elementBinder.nestedProtoView = nestedPv; };
|
(nestedPv: AppProtoView) => { elementBinder.nestedProtoView = nestedPv; };
|
||||||
@ -222,24 +218,85 @@ export class Compiler {
|
|||||||
elementBinderDone(<AppProtoView>nestedCall);
|
elementBinderDone(<AppProtoView>nestedCall);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return PromiseWrapper.all(nestedPVPromises)
|
||||||
if (nestedPVPromises.length > 0) {
|
.then((_) => {
|
||||||
return PromiseWrapper.all(nestedPVPromises).then((_) => protoView);
|
var appProtoViewsToMergeInto = [];
|
||||||
} else {
|
var mergeRenderProtoViews = this._collectMergeRenderProtoViewsRecurse(
|
||||||
return protoView;
|
renderProtoView, appProtoView, appProtoViewsToMergeInto);
|
||||||
}
|
if (isBlank(mergeRenderProtoViews)) {
|
||||||
|
throw new BaseException(`Unconditional component cycle in ${stringify(componentType)}`);
|
||||||
|
}
|
||||||
|
return this._mergeProtoViews(appProtoViewsToMergeInto, mergeRenderProtoViews);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _collectComponentElementBinders(protoViews: List<AppProtoView>): List<ElementBinder> {
|
private _mergeProtoViews(
|
||||||
var componentElementBinders = [];
|
appProtoViewsToMergeInto: AppProtoView[],
|
||||||
ListWrapper.forEach(protoViews, (protoView) => {
|
mergeRenderProtoViews:
|
||||||
ListWrapper.forEach(protoView.elementBinders, (elementBinder) => {
|
List<renderApi.RenderProtoViewRef | List<any>>): Promise<AppProtoView> {
|
||||||
if (isPresent(elementBinder.componentDirective)) {
|
return this._render.mergeProtoViewsRecursively(mergeRenderProtoViews)
|
||||||
componentElementBinders.push(elementBinder);
|
.then((mergeResults: List<renderApi.RenderProtoViewMergeMapping>) => {
|
||||||
|
// Note: We don't need to check for nulls here as we filtered them out before!
|
||||||
|
// (in RenderCompiler.mergeProtoViewsRecursively and
|
||||||
|
// _collectMergeRenderProtoViewsRecurse).
|
||||||
|
for (var i = 0; i < mergeResults.length; i++) {
|
||||||
|
appProtoViewsToMergeInto[i].mergeMapping =
|
||||||
|
new AppProtoViewMergeMapping(mergeResults[i]);
|
||||||
|
}
|
||||||
|
return appProtoViewsToMergeInto[0];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _loopComponentElementBinders(appProtoView: AppProtoView, callback: Function) {
|
||||||
|
appProtoView.elementBinders.forEach((elementBinder) => {
|
||||||
|
if (isPresent(elementBinder.componentDirective)) {
|
||||||
|
callback(appProtoView, elementBinder);
|
||||||
|
} else if (isPresent(elementBinder.nestedProtoView)) {
|
||||||
|
this._loopComponentElementBinders(elementBinder.nestedProtoView, callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _collectMergeRenderProtoViewsRecurse(
|
||||||
|
renderProtoView: renderApi.ProtoViewDto, appProtoView: AppProtoView,
|
||||||
|
targetAppProtoViews: AppProtoView[]): List<renderApi.RenderProtoViewRef | List<any>> {
|
||||||
|
targetAppProtoViews.push(appProtoView);
|
||||||
|
var result = [renderProtoView.render];
|
||||||
|
for (var i = 0; i < appProtoView.elementBinders.length; i++) {
|
||||||
|
var binder = appProtoView.elementBinders[i];
|
||||||
|
if (binder.hasStaticComponent()) {
|
||||||
|
if (isBlank(binder.nestedProtoView.mergeMapping)) {
|
||||||
|
// cycle via an embedded ProtoView. store the AppProtoView and ProtoViewDto for later.
|
||||||
|
this._unmergedCyclicEmbeddedProtoViews.push(
|
||||||
|
new RecursiveEmbeddedProtoView(appProtoView, renderProtoView));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
result.push(binder.nestedProtoView.mergeMapping.renderProtoViewRef);
|
||||||
|
} else if (binder.hasEmbeddedProtoView()) {
|
||||||
|
result.push(this._collectMergeRenderProtoViewsRecurse(
|
||||||
|
renderProtoView.elementBinders[i].nestedProtoView, binder.nestedProtoView,
|
||||||
|
targetAppProtoViews));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _mergeCyclicEmbeddedProtoViews() {
|
||||||
|
var pvs = this._unmergedCyclicEmbeddedProtoViews;
|
||||||
|
this._unmergedCyclicEmbeddedProtoViews = [];
|
||||||
|
var promises = pvs.map(entry => {
|
||||||
|
var appProtoView = entry.appProtoView;
|
||||||
|
var mergeRenderProtoViews = [entry.renderProtoView.render];
|
||||||
|
appProtoView.elementBinders.forEach((binder) => {
|
||||||
|
if (binder.hasStaticComponent()) {
|
||||||
|
mergeRenderProtoViews.push(binder.nestedProtoView.mergeMapping.renderProtoViewRef);
|
||||||
|
} else if (binder.hasEmbeddedProtoView()) {
|
||||||
|
mergeRenderProtoViews.push(null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return this._mergeProtoViews([appProtoView], mergeRenderProtoViews);
|
||||||
});
|
});
|
||||||
return componentElementBinders;
|
return PromiseWrapper.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _buildRenderTemplate(component, view, directives): renderApi.ViewDefinition {
|
private _buildRenderTemplate(component, view, directives): renderApi.ViewDefinition {
|
||||||
@ -299,3 +356,7 @@ export class Compiler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class RecursiveEmbeddedProtoView {
|
||||||
|
constructor(public appProtoView: AppProtoView, public renderProtoView: renderApi.ProtoViewDto) {}
|
||||||
|
}
|
||||||
|
@ -278,7 +278,7 @@ export class DirectiveBinding extends ResolvedBinding {
|
|||||||
// TODO(rado): benchmark and consider rolling in as ElementInjector fields.
|
// TODO(rado): benchmark and consider rolling in as ElementInjector fields.
|
||||||
export class PreBuiltObjects {
|
export class PreBuiltObjects {
|
||||||
constructor(public viewManager: avmModule.AppViewManager, public view: viewModule.AppView,
|
constructor(public viewManager: avmModule.AppViewManager, public view: viewModule.AppView,
|
||||||
public protoView: viewModule.AppProtoView) {}
|
public elementRef: ElementRef, public protoView: viewModule.AppProtoView) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EventEmitterAccessor {
|
export class EventEmitterAccessor {
|
||||||
@ -575,7 +575,7 @@ export class ElementInjector extends TreeNode<ElementInjector> implements Depend
|
|||||||
|
|
||||||
getComponent(): any { return this._strategy.getComponent(); }
|
getComponent(): any { return this._strategy.getComponent(); }
|
||||||
|
|
||||||
getElementRef(): ElementRef { return this._preBuiltObjects.view.elementRefs[this._proto.index]; }
|
getElementRef(): ElementRef { return this._preBuiltObjects.elementRef; }
|
||||||
|
|
||||||
getViewContainerRef(): ViewContainerRef {
|
getViewContainerRef(): ViewContainerRef {
|
||||||
return new ViewContainerRef(this._preBuiltObjects.viewManager, this.getElementRef());
|
return new ViewContainerRef(this._preBuiltObjects.viewManager, this.getElementRef());
|
||||||
@ -606,7 +606,8 @@ export class ElementInjector extends TreeNode<ElementInjector> implements Depend
|
|||||||
// We provide the component's view change detector to components and
|
// We provide the component's view change detector to components and
|
||||||
// the surrounding component's change detector to directives.
|
// the surrounding component's change detector to directives.
|
||||||
if (dirBin.metadata.type === DirectiveMetadata.COMPONENT_TYPE) {
|
if (dirBin.metadata.type === DirectiveMetadata.COMPONENT_TYPE) {
|
||||||
var componentView = this._preBuiltObjects.view.componentChildViews[this._proto.index];
|
var componentView = this._preBuiltObjects.view.getNestedView(
|
||||||
|
this._preBuiltObjects.elementRef.boundElementIndex);
|
||||||
return componentView.changeDetector.ref;
|
return componentView.changeDetector.ref;
|
||||||
} else {
|
} else {
|
||||||
return this._preBuiltObjects.view.changeDetector.ref;
|
return this._preBuiltObjects.view.changeDetector.ref;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {BaseException} from 'angular2/src/facade/lang';
|
import {BaseException, isPresent} from 'angular2/src/facade/lang';
|
||||||
import {ViewRef} from './view_ref';
|
import {ViewRef} from './view_ref';
|
||||||
import {RenderViewRef, RenderElementRef, Renderer} from 'angular2/src/render/api';
|
import {RenderViewRef, RenderElementRef, Renderer} from 'angular2/src/render/api';
|
||||||
|
|
||||||
@ -24,9 +24,18 @@ export class ElementRef implements RenderElementRef {
|
|||||||
*/
|
*/
|
||||||
boundElementIndex: number;
|
boundElementIndex: number;
|
||||||
|
|
||||||
constructor(parentView: ViewRef, boundElementIndex: number, private _renderer: Renderer) {
|
/**
|
||||||
|
* Index of the element inside the {@link RenderViewRef}.
|
||||||
|
*
|
||||||
|
* This is used internally by the Angular framework to locate elements.
|
||||||
|
*/
|
||||||
|
renderBoundElementIndex: number;
|
||||||
|
|
||||||
|
constructor(parentView: ViewRef, boundElementIndex: number, renderBoundElementIndex: number,
|
||||||
|
private _renderer: Renderer) {
|
||||||
this.parentView = parentView;
|
this.parentView = parentView;
|
||||||
this.boundElementIndex = boundElementIndex;
|
this.boundElementIndex = boundElementIndex;
|
||||||
|
this.renderBoundElementIndex = renderBoundElementIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -11,7 +11,8 @@ import {
|
|||||||
DirectiveRecord,
|
DirectiveRecord,
|
||||||
ProtoChangeDetector,
|
ProtoChangeDetector,
|
||||||
DEFAULT,
|
DEFAULT,
|
||||||
ChangeDetectorDefinition
|
ChangeDetectorDefinition,
|
||||||
|
ASTWithSource
|
||||||
} from 'angular2/change_detection';
|
} from 'angular2/change_detection';
|
||||||
|
|
||||||
import * as renderApi from 'angular2/src/render/api';
|
import * as renderApi from 'angular2/src/render/api';
|
||||||
@ -21,16 +22,16 @@ import {ProtoElementInjector, DirectiveBinding} from './element_injector';
|
|||||||
|
|
||||||
class BindingRecordsCreator {
|
class BindingRecordsCreator {
|
||||||
_directiveRecordsMap: Map<number, DirectiveRecord> = new Map();
|
_directiveRecordsMap: Map<number, DirectiveRecord> = new Map();
|
||||||
_textNodeIndex: number = 0;
|
|
||||||
|
|
||||||
getBindingRecords(elementBinders: List<renderApi.ElementBinder>,
|
getBindingRecords(textBindings: List<ASTWithSource>,
|
||||||
|
elementBinders: List<renderApi.ElementBinder>,
|
||||||
allDirectiveMetadatas: List<renderApi.DirectiveMetadata>): List<BindingRecord> {
|
allDirectiveMetadatas: List<renderApi.DirectiveMetadata>): List<BindingRecord> {
|
||||||
var bindings = [];
|
var bindings = [];
|
||||||
|
|
||||||
|
this._createTextNodeRecords(bindings, textBindings);
|
||||||
for (var boundElementIndex = 0; boundElementIndex < elementBinders.length;
|
for (var boundElementIndex = 0; boundElementIndex < elementBinders.length;
|
||||||
boundElementIndex++) {
|
boundElementIndex++) {
|
||||||
var renderElementBinder = elementBinders[boundElementIndex];
|
var renderElementBinder = elementBinders[boundElementIndex];
|
||||||
this._createTextNodeRecords(bindings, renderElementBinder);
|
|
||||||
this._createElementPropertyRecords(bindings, boundElementIndex, renderElementBinder);
|
this._createElementPropertyRecords(bindings, boundElementIndex, renderElementBinder);
|
||||||
this._createDirectiveRecords(bindings, boundElementIndex, renderElementBinder.directives,
|
this._createDirectiveRecords(bindings, boundElementIndex, renderElementBinder.directives,
|
||||||
allDirectiveMetadatas);
|
allDirectiveMetadatas);
|
||||||
@ -55,13 +56,10 @@ class BindingRecordsCreator {
|
|||||||
return directiveRecords;
|
return directiveRecords;
|
||||||
}
|
}
|
||||||
|
|
||||||
_createTextNodeRecords(bindings: List<BindingRecord>,
|
_createTextNodeRecords(bindings: List<BindingRecord>, textBindings: List<ASTWithSource>) {
|
||||||
renderElementBinder: renderApi.ElementBinder) {
|
for (var i = 0; i < textBindings.length; i++) {
|
||||||
if (isBlank(renderElementBinder.textBindings)) return;
|
bindings.push(BindingRecord.createForTextNode(textBindings[i], i));
|
||||||
|
}
|
||||||
ListWrapper.forEach(renderElementBinder.textBindings, (b) => {
|
|
||||||
bindings.push(BindingRecord.createForTextNode(b, this._textNodeIndex++));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_createElementPropertyRecords(bindings: List<BindingRecord>, boundElementIndex: number,
|
_createElementPropertyRecords(bindings: List<BindingRecord>, boundElementIndex: number,
|
||||||
@ -162,7 +160,7 @@ export class ProtoViewFactory {
|
|||||||
|
|
||||||
createAppProtoViews(hostComponentBinding: DirectiveBinding,
|
createAppProtoViews(hostComponentBinding: DirectiveBinding,
|
||||||
rootRenderProtoView: renderApi.ProtoViewDto,
|
rootRenderProtoView: renderApi.ProtoViewDto,
|
||||||
allDirectives: List<DirectiveBinding>): List<AppProtoView> {
|
allDirectives: List<DirectiveBinding>): AppProtoView {
|
||||||
var allRenderDirectiveMetadata =
|
var allRenderDirectiveMetadata =
|
||||||
ListWrapper.map(allDirectives, directiveBinding => directiveBinding.metadata);
|
ListWrapper.map(allDirectives, directiveBinding => directiveBinding.metadata);
|
||||||
var nestedPvsWithIndex = _collectNestedProtoViews(rootRenderProtoView);
|
var nestedPvsWithIndex = _collectNestedProtoViews(rootRenderProtoView);
|
||||||
@ -176,7 +174,7 @@ export class ProtoViewFactory {
|
|||||||
changeDetectorDefs,
|
changeDetectorDefs,
|
||||||
changeDetectorDef => this._changeDetection.createProtoChangeDetector(changeDetectorDef));
|
changeDetectorDef => this._changeDetection.createProtoChangeDetector(changeDetectorDef));
|
||||||
var appProtoViews = ListWrapper.createFixedSize(nestedPvsWithIndex.length);
|
var appProtoViews = ListWrapper.createFixedSize(nestedPvsWithIndex.length);
|
||||||
ListWrapper.forEach(nestedPvsWithIndex, (pvWithIndex) => {
|
ListWrapper.forEach(nestedPvsWithIndex, (pvWithIndex: RenderProtoViewWithIndex) => {
|
||||||
var appProtoView =
|
var appProtoView =
|
||||||
_createAppProtoView(pvWithIndex.renderProtoView, protoChangeDetectors[pvWithIndex.index],
|
_createAppProtoView(pvWithIndex.renderProtoView, protoChangeDetectors[pvWithIndex.index],
|
||||||
nestedPvVariableBindings[pvWithIndex.index], allDirectives);
|
nestedPvVariableBindings[pvWithIndex.index], allDirectives);
|
||||||
@ -186,7 +184,7 @@ export class ProtoViewFactory {
|
|||||||
}
|
}
|
||||||
appProtoViews[pvWithIndex.index] = appProtoView;
|
appProtoViews[pvWithIndex.index] = appProtoView;
|
||||||
});
|
});
|
||||||
return appProtoViews;
|
return appProtoViews[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,6 +207,7 @@ function _collectNestedProtoViews(
|
|||||||
if (isBlank(result)) {
|
if (isBlank(result)) {
|
||||||
result = [];
|
result = [];
|
||||||
}
|
}
|
||||||
|
// reserve the place in the array
|
||||||
result.push(
|
result.push(
|
||||||
new RenderProtoViewWithIndex(renderProtoView, result.length, parentIndex, boundElementIndex));
|
new RenderProtoViewWithIndex(renderProtoView, result.length, parentIndex, boundElementIndex));
|
||||||
var currentIndex = result.length - 1;
|
var currentIndex = result.length - 1;
|
||||||
@ -230,8 +229,8 @@ function _getChangeDetectorDefinitions(
|
|||||||
return ListWrapper.map(nestedPvsWithIndex, (pvWithIndex) => {
|
return ListWrapper.map(nestedPvsWithIndex, (pvWithIndex) => {
|
||||||
var elementBinders = pvWithIndex.renderProtoView.elementBinders;
|
var elementBinders = pvWithIndex.renderProtoView.elementBinders;
|
||||||
var bindingRecordsCreator = new BindingRecordsCreator();
|
var bindingRecordsCreator = new BindingRecordsCreator();
|
||||||
var bindingRecords =
|
var bindingRecords = bindingRecordsCreator.getBindingRecords(
|
||||||
bindingRecordsCreator.getBindingRecords(elementBinders, allRenderDirectiveMetadata);
|
pvWithIndex.renderProtoView.textBindings, elementBinders, allRenderDirectiveMetadata);
|
||||||
var directiveRecords =
|
var directiveRecords =
|
||||||
bindingRecordsCreator.getDirectiveRecords(elementBinders, allRenderDirectiveMetadata);
|
bindingRecordsCreator.getDirectiveRecords(elementBinders, allRenderDirectiveMetadata);
|
||||||
var strategyName = DEFAULT;
|
var strategyName = DEFAULT;
|
||||||
@ -255,8 +254,9 @@ function _createAppProtoView(
|
|||||||
renderProtoView: renderApi.ProtoViewDto, protoChangeDetector: ProtoChangeDetector,
|
renderProtoView: renderApi.ProtoViewDto, protoChangeDetector: ProtoChangeDetector,
|
||||||
variableBindings: Map<string, string>, allDirectives: List<DirectiveBinding>): AppProtoView {
|
variableBindings: Map<string, string>, allDirectives: List<DirectiveBinding>): AppProtoView {
|
||||||
var elementBinders = renderProtoView.elementBinders;
|
var elementBinders = renderProtoView.elementBinders;
|
||||||
var protoView = new AppProtoView(renderProtoView.render, protoChangeDetector, variableBindings,
|
var protoView = new AppProtoView(renderProtoView.type, protoChangeDetector, variableBindings,
|
||||||
createVariableLocations(elementBinders));
|
createVariableLocations(elementBinders),
|
||||||
|
renderProtoView.textBindings.length);
|
||||||
_createElementBinders(protoView, elementBinders, allDirectives);
|
_createElementBinders(protoView, elementBinders, allDirectives);
|
||||||
_bindDirectiveEvents(protoView, elementBinders);
|
_bindDirectiveEvents(protoView, elementBinders);
|
||||||
|
|
||||||
|
@ -20,10 +20,46 @@ import {
|
|||||||
import {ElementBinder} from './element_binder';
|
import {ElementBinder} from './element_binder';
|
||||||
import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
|
import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
|
||||||
import * as renderApi from 'angular2/src/render/api';
|
import * as renderApi from 'angular2/src/render/api';
|
||||||
import {EventDispatcher} from 'angular2/src/render/api';
|
import {RenderEventDispatcher} from 'angular2/src/render/api';
|
||||||
import {ViewRef} from './view_ref';
|
import {ViewRef, internalView} from './view_ref';
|
||||||
import {ElementRef} from './element_ref';
|
import {ElementRef} from './element_ref';
|
||||||
|
|
||||||
|
export class AppProtoViewMergeMapping {
|
||||||
|
renderProtoViewRef: renderApi.RenderProtoViewRef;
|
||||||
|
renderFragmentCount: number;
|
||||||
|
renderElementIndices: number[];
|
||||||
|
renderInverseElementIndices: number[];
|
||||||
|
renderTextIndices: number[];
|
||||||
|
nestedViewIndicesByElementIndex: number[];
|
||||||
|
hostElementIndicesByViewIndex: number[];
|
||||||
|
constructor(renderProtoViewMergeMapping: renderApi.RenderProtoViewMergeMapping) {
|
||||||
|
this.renderProtoViewRef = renderProtoViewMergeMapping.mergedProtoViewRef;
|
||||||
|
this.renderFragmentCount = renderProtoViewMergeMapping.fragmentCount;
|
||||||
|
this.renderElementIndices = renderProtoViewMergeMapping.mappedElementIndices;
|
||||||
|
this.renderInverseElementIndices =
|
||||||
|
inverseIndexMapping(this.renderElementIndices, this.renderElementIndices.length);
|
||||||
|
this.renderTextIndices = renderProtoViewMergeMapping.mappedTextIndices;
|
||||||
|
this.hostElementIndicesByViewIndex = renderProtoViewMergeMapping.hostElementIndicesByViewIndex;
|
||||||
|
this.nestedViewIndicesByElementIndex =
|
||||||
|
inverseIndexMapping(this.hostElementIndicesByViewIndex, this.renderElementIndices.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
get viewCount() { return this.hostElementIndicesByViewIndex.length; }
|
||||||
|
|
||||||
|
get elementCount() { return this.renderElementIndices.length; }
|
||||||
|
}
|
||||||
|
|
||||||
|
function inverseIndexMapping(input: number[], resultLength: number): number[] {
|
||||||
|
var result = ListWrapper.createFixedSize(resultLength);
|
||||||
|
for (var i = 0; i < input.length; i++) {
|
||||||
|
var value = input[i];
|
||||||
|
if (isPresent(value)) {
|
||||||
|
result[input[i]] = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
export class AppViewContainer {
|
export class AppViewContainer {
|
||||||
// The order in this list matches the DOM order.
|
// The order in this list matches the DOM order.
|
||||||
views: List<AppView> = [];
|
views: List<AppView> = [];
|
||||||
@ -33,17 +69,34 @@ export class AppViewContainer {
|
|||||||
* Cost of making objects: http://jsperf.com/instantiate-size-of-object
|
* Cost of making objects: http://jsperf.com/instantiate-size-of-object
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export class AppView implements ChangeDispatcher, EventDispatcher {
|
export class AppView implements ChangeDispatcher, RenderEventDispatcher {
|
||||||
render: renderApi.RenderViewRef = null;
|
// AppViews that have been merged in depth first order.
|
||||||
/// This list matches the _nodes list. It is sparse, since only Elements have ElementInjector
|
// This list is shared between all merged views. Use this.elementOffset to get the local
|
||||||
|
// entries.
|
||||||
|
views: List<AppView> = null;
|
||||||
|
// root elementInjectors of this AppView
|
||||||
|
// This list is local to this AppView and not shared with other Views.
|
||||||
rootElementInjectors: List<ElementInjector>;
|
rootElementInjectors: List<ElementInjector>;
|
||||||
|
// ElementInjectors of all AppViews in views grouped by view.
|
||||||
|
// This list is shared between all merged views. Use this.elementOffset to get the local
|
||||||
|
// entries.
|
||||||
elementInjectors: List<ElementInjector> = null;
|
elementInjectors: List<ElementInjector> = null;
|
||||||
changeDetector: ChangeDetector = null;
|
// ViewContainers of all AppViews in views grouped by view.
|
||||||
componentChildViews: List<AppView> = null;
|
// This list is shared between all merged views. Use this.elementOffset to get the local
|
||||||
viewContainers: List<AppViewContainer>;
|
// entries.
|
||||||
|
viewContainers: List<AppViewContainer> = null;
|
||||||
|
// PreBuiltObjects of all AppViews in views grouped by view.
|
||||||
|
// This list is shared between all merged views. Use this.elementOffset to get the local
|
||||||
|
// entries.
|
||||||
preBuiltObjects: List<PreBuiltObjects> = null;
|
preBuiltObjects: List<PreBuiltObjects> = null;
|
||||||
|
// ElementRef of all AppViews in views grouped by view.
|
||||||
|
// This list is shared between all merged views. Use this.elementOffset to get the local
|
||||||
|
// entries.
|
||||||
elementRefs: List<ElementRef>;
|
elementRefs: List<ElementRef>;
|
||||||
|
|
||||||
ref: ViewRef;
|
ref: ViewRef;
|
||||||
|
changeDetector: ChangeDetector = null;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The context against which data-binding expressions in this view are evaluated against.
|
* The context against which data-binding expressions in this view are evaluated against.
|
||||||
@ -60,24 +113,26 @@ export class AppView implements ChangeDispatcher, EventDispatcher {
|
|||||||
locals: Locals;
|
locals: Locals;
|
||||||
|
|
||||||
constructor(public renderer: renderApi.Renderer, public proto: AppProtoView,
|
constructor(public renderer: renderApi.Renderer, public proto: AppProtoView,
|
||||||
protoLocals: Map<string, any>) {
|
public mainMergeMapping: AppProtoViewMergeMapping, public viewOffset: number,
|
||||||
this.viewContainers = ListWrapper.createFixedSize(this.proto.elementBinders.length);
|
public elementOffset: number, public textOffset: number,
|
||||||
this.elementRefs = ListWrapper.createFixedSize(this.proto.elementBinders.length);
|
protoLocals: Map<string, any>, public render: renderApi.RenderViewRef,
|
||||||
|
public renderFragment: renderApi.RenderFragmentRef) {
|
||||||
this.ref = new ViewRef(this);
|
this.ref = new ViewRef(this);
|
||||||
for (var i = 0; i < this.elementRefs.length; i++) {
|
|
||||||
this.elementRefs[i] = new ElementRef(this.ref, i, renderer);
|
|
||||||
}
|
|
||||||
this.locals = new Locals(null, MapWrapper.clone(protoLocals)); // TODO optimize this
|
this.locals = new Locals(null, MapWrapper.clone(protoLocals)); // TODO optimize this
|
||||||
}
|
}
|
||||||
|
|
||||||
init(changeDetector: ChangeDetector, elementInjectors: List<ElementInjector>,
|
init(changeDetector: ChangeDetector, elementInjectors: List<ElementInjector>,
|
||||||
rootElementInjectors: List<ElementInjector>, preBuiltObjects: List<PreBuiltObjects>,
|
rootElementInjectors: List<ElementInjector>, preBuiltObjects: List<PreBuiltObjects>,
|
||||||
componentChildViews: List<AppView>) {
|
views: List<AppView>, elementRefs: List<ElementRef>,
|
||||||
|
viewContainers: List<AppViewContainer>) {
|
||||||
this.changeDetector = changeDetector;
|
this.changeDetector = changeDetector;
|
||||||
this.elementInjectors = elementInjectors;
|
this.elementInjectors = elementInjectors;
|
||||||
this.rootElementInjectors = rootElementInjectors;
|
this.rootElementInjectors = rootElementInjectors;
|
||||||
this.preBuiltObjects = preBuiltObjects;
|
this.preBuiltObjects = preBuiltObjects;
|
||||||
this.componentChildViews = componentChildViews;
|
this.views = views;
|
||||||
|
this.elementRefs = elementRefs;
|
||||||
|
this.viewContainers = viewContainers;
|
||||||
}
|
}
|
||||||
|
|
||||||
setLocal(contextName: string, value): void {
|
setLocal(contextName: string, value): void {
|
||||||
@ -98,49 +153,57 @@ export class AppView implements ChangeDispatcher, EventDispatcher {
|
|||||||
*
|
*
|
||||||
* @param {string} eventName
|
* @param {string} eventName
|
||||||
* @param {*} eventObj
|
* @param {*} eventObj
|
||||||
* @param {int} binderIndex
|
* @param {int} boundElementIndex
|
||||||
*/
|
*/
|
||||||
triggerEventHandlers(eventName: string, eventObj, binderIndex: int): void {
|
triggerEventHandlers(eventName: string, eventObj, boundElementIndex: int): void {
|
||||||
var locals = new Map();
|
var locals = new Map();
|
||||||
locals.set('$event', eventObj);
|
locals.set('$event', eventObj);
|
||||||
this.dispatchEvent(binderIndex, eventName, locals);
|
this.dispatchEvent(boundElementIndex, eventName, locals);
|
||||||
}
|
}
|
||||||
|
|
||||||
// dispatch to element injector or text nodes based on context
|
// dispatch to element injector or text nodes based on context
|
||||||
notifyOnBinding(b: BindingRecord, currentValue: any): void {
|
notifyOnBinding(b: BindingRecord, currentValue: any): void {
|
||||||
if (b.isElementProperty()) {
|
if (b.isTextNode()) {
|
||||||
this.renderer.setElementProperty(this.elementRefs[b.elementIndex], b.propertyName,
|
this.renderer.setText(
|
||||||
currentValue);
|
this.render, this.mainMergeMapping.renderTextIndices[b.elementIndex + this.textOffset],
|
||||||
} else if (b.isElementAttribute()) {
|
currentValue);
|
||||||
this.renderer.setElementAttribute(this.elementRefs[b.elementIndex], b.propertyName,
|
|
||||||
currentValue);
|
|
||||||
} else if (b.isElementClass()) {
|
|
||||||
this.renderer.setElementClass(this.elementRefs[b.elementIndex], b.propertyName, currentValue);
|
|
||||||
} else if (b.isElementStyle()) {
|
|
||||||
var unit = isPresent(b.propertyUnit) ? b.propertyUnit : '';
|
|
||||||
this.renderer.setElementStyle(this.elementRefs[b.elementIndex], b.propertyName,
|
|
||||||
`${currentValue}${unit}`);
|
|
||||||
} else if (b.isTextNode()) {
|
|
||||||
this.renderer.setText(this.render, b.elementIndex, currentValue);
|
|
||||||
} else {
|
} else {
|
||||||
throw new BaseException('Unsupported directive record');
|
var elementRef = this.elementRefs[this.elementOffset + b.elementIndex];
|
||||||
|
if (b.isElementProperty()) {
|
||||||
|
this.renderer.setElementProperty(elementRef, b.propertyName, currentValue);
|
||||||
|
} else if (b.isElementAttribute()) {
|
||||||
|
this.renderer.setElementAttribute(elementRef, b.propertyName, currentValue);
|
||||||
|
} else if (b.isElementClass()) {
|
||||||
|
this.renderer.setElementClass(elementRef, b.propertyName, currentValue);
|
||||||
|
} else if (b.isElementStyle()) {
|
||||||
|
var unit = isPresent(b.propertyUnit) ? b.propertyUnit : '';
|
||||||
|
this.renderer.setElementStyle(elementRef, b.propertyName, `${currentValue}${unit}`);
|
||||||
|
} else {
|
||||||
|
throw new BaseException('Unsupported directive record');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyOnAllChangesDone(): void {
|
notifyOnAllChangesDone(): void {
|
||||||
|
var eiCount = this.proto.elementBinders.length;
|
||||||
var ei = this.elementInjectors;
|
var ei = this.elementInjectors;
|
||||||
for (var i = ei.length - 1; i >= 0; i--) {
|
for (var i = eiCount - 1; i >= 0; i--) {
|
||||||
if (isPresent(ei[i])) ei[i].onAllChangesDone();
|
if (isPresent(ei[i + this.elementOffset])) ei[i + this.elementOffset].onAllChangesDone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getDirectiveFor(directive: DirectiveIndex): any {
|
getDirectiveFor(directive: DirectiveIndex): any {
|
||||||
var elementInjector = this.elementInjectors[directive.elementIndex];
|
var elementInjector = this.elementInjectors[this.elementOffset + directive.elementIndex];
|
||||||
return elementInjector.getDirectiveAtIndex(directive.directiveIndex);
|
return elementInjector.getDirectiveAtIndex(directive.directiveIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getNestedView(boundElementIndex: number): AppView {
|
||||||
|
var viewIndex = this.mainMergeMapping.nestedViewIndicesByElementIndex[boundElementIndex];
|
||||||
|
return isPresent(viewIndex) ? this.views[viewIndex] : null;
|
||||||
|
}
|
||||||
|
|
||||||
getDetectorFor(directive: DirectiveIndex): any {
|
getDetectorFor(directive: DirectiveIndex): any {
|
||||||
var childView = this.componentChildViews[directive.elementIndex];
|
var childView = this.getNestedView(this.elementOffset + directive.elementIndex);
|
||||||
return isPresent(childView) ? childView.changeDetector : null;
|
return isPresent(childView) ? childView.changeDetector : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,15 +211,24 @@ export class AppView implements ChangeDispatcher, EventDispatcher {
|
|||||||
this.renderer.invokeElementMethod(this.elementRefs[elementIndex], methodName, args);
|
this.renderer.invokeElementMethod(this.elementRefs[elementIndex], methodName, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
// implementation of EventDispatcher#dispatchEvent
|
// implementation of RenderEventDispatcher#dispatchRenderEvent
|
||||||
|
dispatchRenderEvent(renderElementIndex: number, eventName: string,
|
||||||
|
locals: Map<string, any>): boolean {
|
||||||
|
var elementRef =
|
||||||
|
this.elementRefs[this.proto.mergeMapping.renderInverseElementIndices[renderElementIndex]];
|
||||||
|
var view = internalView(elementRef.parentView);
|
||||||
|
return view.dispatchEvent(elementRef.boundElementIndex, eventName, locals);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// returns false if preventDefault must be applied to the DOM event
|
// returns false if preventDefault must be applied to the DOM event
|
||||||
dispatchEvent(elementIndex: number, eventName: string, locals: Map<string, any>): boolean {
|
dispatchEvent(boundElementIndex: number, eventName: string, locals: Map<string, any>): boolean {
|
||||||
// Most of the time the event will be fired only when the view is in the live document.
|
// Most of the time the event will be fired only when the view is in the live document.
|
||||||
// However, in a rare circumstance the view might get dehydrated, in between the event
|
// However, in a rare circumstance the view might get dehydrated, in between the event
|
||||||
// queuing up and firing.
|
// queuing up and firing.
|
||||||
var allowDefaultBehavior = true;
|
var allowDefaultBehavior = true;
|
||||||
if (this.hydrated()) {
|
if (this.hydrated()) {
|
||||||
var elBinder = this.proto.elementBinders[elementIndex];
|
var elBinder = this.proto.elementBinders[boundElementIndex - this.elementOffset];
|
||||||
if (isBlank(elBinder.hostListeners)) return allowDefaultBehavior;
|
if (isBlank(elBinder.hostListeners)) return allowDefaultBehavior;
|
||||||
var eventMap = elBinder.hostListeners[eventName];
|
var eventMap = elBinder.hostListeners[eventName];
|
||||||
if (isBlank(eventMap)) return allowDefaultBehavior;
|
if (isBlank(eventMap)) return allowDefaultBehavior;
|
||||||
@ -165,7 +237,7 @@ export class AppView implements ChangeDispatcher, EventDispatcher {
|
|||||||
if (directiveIndex === -1) {
|
if (directiveIndex === -1) {
|
||||||
context = this.context;
|
context = this.context;
|
||||||
} else {
|
} else {
|
||||||
context = this.elementInjectors[elementIndex].getDirectiveAtIndex(directiveIndex);
|
context = this.elementInjectors[boundElementIndex].getDirectiveAtIndex(directiveIndex);
|
||||||
}
|
}
|
||||||
var result = expr.eval(context, new Locals(this.locals, locals));
|
var result = expr.eval(context, new Locals(this.locals, locals));
|
||||||
if (isPresent(result)) {
|
if (isPresent(result)) {
|
||||||
@ -183,11 +255,11 @@ export class AppView implements ChangeDispatcher, EventDispatcher {
|
|||||||
export class AppProtoView {
|
export class AppProtoView {
|
||||||
elementBinders: List<ElementBinder> = [];
|
elementBinders: List<ElementBinder> = [];
|
||||||
protoLocals: Map<string, any> = new Map();
|
protoLocals: Map<string, any> = new Map();
|
||||||
|
mergeMapping: AppProtoViewMergeMapping;
|
||||||
|
|
||||||
constructor(public render: renderApi.RenderProtoViewRef,
|
constructor(public type: renderApi.ViewType, public protoChangeDetector: ProtoChangeDetector,
|
||||||
public protoChangeDetector: ProtoChangeDetector,
|
|
||||||
public variableBindings: Map<string, string>,
|
public variableBindings: Map<string, string>,
|
||||||
public variableLocations: Map<string, number>) {
|
public variableLocations: Map<string, number>, public textBindingCount: number) {
|
||||||
if (isPresent(variableBindings)) {
|
if (isPresent(variableBindings)) {
|
||||||
MapWrapper.forEach(variableBindings,
|
MapWrapper.forEach(variableBindings,
|
||||||
(templateName, _) => { this.protoLocals.set(templateName, null); });
|
(templateName, _) => { this.protoLocals.set(templateName, null); });
|
||||||
|
@ -4,7 +4,13 @@ import * as viewModule from './view';
|
|||||||
import {ElementRef} from './element_ref';
|
import {ElementRef} from './element_ref';
|
||||||
import {ProtoViewRef, ViewRef, internalView, internalProtoView} from './view_ref';
|
import {ProtoViewRef, ViewRef, internalView, internalProtoView} from './view_ref';
|
||||||
import {ViewContainerRef} from './view_container_ref';
|
import {ViewContainerRef} from './view_container_ref';
|
||||||
import {Renderer, RenderViewRef} from 'angular2/src/render/api';
|
import {
|
||||||
|
Renderer,
|
||||||
|
RenderViewRef,
|
||||||
|
RenderFragmentRef,
|
||||||
|
RenderViewWithFragments,
|
||||||
|
ViewType
|
||||||
|
} from 'angular2/src/render/api';
|
||||||
import {AppViewManagerUtils} from './view_manager_utils';
|
import {AppViewManagerUtils} from './view_manager_utils';
|
||||||
import {AppViewPool} from './view_pool';
|
import {AppViewPool} from './view_pool';
|
||||||
import {AppViewListener} from './view_listener';
|
import {AppViewListener} from './view_listener';
|
||||||
@ -22,18 +28,6 @@ export class AppViewManager {
|
|||||||
constructor(private _viewPool: AppViewPool, private _viewListener: AppViewListener,
|
constructor(private _viewPool: AppViewPool, private _viewListener: AppViewListener,
|
||||||
private _utils: AppViewManagerUtils, private _renderer: Renderer) {}
|
private _utils: AppViewManagerUtils, private _renderer: Renderer) {}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns associated Component {@link ViewRef} from {@link ElementRef}.
|
|
||||||
*
|
|
||||||
* If an {@link ElementRef} is from an element which has a component, this method returns
|
|
||||||
* the component's {@link ViewRef}.
|
|
||||||
*/
|
|
||||||
getComponentView(hostLocation: ElementRef): ViewRef {
|
|
||||||
var hostView: viewModule.AppView = internalView(hostLocation.parentView);
|
|
||||||
var boundElementIndex = hostLocation.boundElementIndex;
|
|
||||||
return hostView.componentChildViews[boundElementIndex].ref;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a {@link ViewContainerRef} at the {@link ElementRef} location.
|
* Returns a {@link ViewContainerRef} at the {@link ElementRef} location.
|
||||||
*/
|
*/
|
||||||
@ -47,7 +41,8 @@ export class AppViewManager {
|
|||||||
*/
|
*/
|
||||||
// TODO(misko): remove https://github.com/angular/angular/issues/2891
|
// TODO(misko): remove https://github.com/angular/angular/issues/2891
|
||||||
getHostElement(hostViewRef: ViewRef): ElementRef {
|
getHostElement(hostViewRef: ViewRef): ElementRef {
|
||||||
return internalView(hostViewRef).elementRefs[0];
|
var hostView = internalView(hostViewRef);
|
||||||
|
return hostView.elementRefs[hostView.elementOffset];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -62,15 +57,15 @@ export class AppViewManager {
|
|||||||
getNamedElementInComponentView(hostLocation: ElementRef, variableName: string): ElementRef {
|
getNamedElementInComponentView(hostLocation: ElementRef, variableName: string): ElementRef {
|
||||||
var hostView = internalView(hostLocation.parentView);
|
var hostView = internalView(hostLocation.parentView);
|
||||||
var boundElementIndex = hostLocation.boundElementIndex;
|
var boundElementIndex = hostLocation.boundElementIndex;
|
||||||
var componentView = hostView.componentChildViews[boundElementIndex];
|
var componentView = hostView.getNestedView(boundElementIndex);
|
||||||
if (isBlank(componentView)) {
|
if (isBlank(componentView)) {
|
||||||
throw new BaseException(`There is no component directive at element ${boundElementIndex}`);
|
throw new BaseException(`There is no component directive at element ${boundElementIndex}`);
|
||||||
}
|
}
|
||||||
var elementIndex = componentView.proto.variableLocations.get(variableName);
|
var binderIdx = componentView.proto.variableLocations.get(variableName);
|
||||||
if (isBlank(elementIndex)) {
|
if (isBlank(binderIdx)) {
|
||||||
throw new BaseException(`Could not find variable ${variableName}`);
|
throw new BaseException(`Could not find variable ${variableName}`);
|
||||||
}
|
}
|
||||||
return componentView.elementRefs[elementIndex];
|
return componentView.elementRefs[componentView.elementOffset + binderIdx];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -146,14 +141,13 @@ export class AppViewManager {
|
|||||||
if (isBlank(hostElementSelector)) {
|
if (isBlank(hostElementSelector)) {
|
||||||
hostElementSelector = hostProtoView.elementBinders[0].componentDirective.metadata.selector;
|
hostElementSelector = hostProtoView.elementBinders[0].componentDirective.metadata.selector;
|
||||||
}
|
}
|
||||||
var renderView = this._renderer.createRootHostView(hostProtoView.render, hostElementSelector);
|
var renderViewWithFragments = this._renderer.createRootHostView(
|
||||||
var hostView = this._utils.createView(hostProtoView, renderView, this, this._renderer);
|
hostProtoView.mergeMapping.renderProtoViewRef,
|
||||||
this._renderer.setEventDispatcher(hostView.render, hostView);
|
hostProtoView.mergeMapping.renderFragmentCount, hostElementSelector);
|
||||||
this._createViewRecurse(hostView);
|
var hostView = this._createMainView(hostProtoView, renderViewWithFragments);
|
||||||
this._viewListener.viewCreated(hostView);
|
|
||||||
|
|
||||||
|
this._renderer.hydrateView(hostView.render);
|
||||||
this._utils.hydrateRootHostView(hostView, injector);
|
this._utils.hydrateRootHostView(hostView, injector);
|
||||||
this._viewHydrateRecurse(hostView);
|
|
||||||
|
|
||||||
return hostView.ref;
|
return hostView.ref;
|
||||||
}
|
}
|
||||||
@ -162,14 +156,14 @@ export class AppViewManager {
|
|||||||
* Remove the View created with {@link AppViewManager#createRootHostView}.
|
* Remove the View created with {@link AppViewManager#createRootHostView}.
|
||||||
*/
|
*/
|
||||||
destroyRootHostView(hostViewRef: ViewRef) {
|
destroyRootHostView(hostViewRef: ViewRef) {
|
||||||
// Note: Don't detach the hostView as we want to leave the
|
// Note: Don't put the hostView into the view pool
|
||||||
// root element in place. Also don't put the hostView into the view pool
|
|
||||||
// as it is depending on the element for which it was created.
|
// as it is depending on the element for which it was created.
|
||||||
var hostView = internalView(hostViewRef);
|
var hostView = internalView(hostViewRef);
|
||||||
// We do want to destroy the component view though.
|
this._renderer.detachFragment(hostView.renderFragment);
|
||||||
this._viewDehydrateRecurse(hostView, true);
|
this._renderer.dehydrateView(hostView.render);
|
||||||
this._renderer.destroyView(hostView.render);
|
this._viewDehydrateRecurse(hostView);
|
||||||
this._viewListener.viewDestroyed(hostView);
|
this._viewListener.viewDestroyed(hostView);
|
||||||
|
this._renderer.destroyView(hostView.render);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -187,19 +181,42 @@ export class AppViewManager {
|
|||||||
if (isPresent(context)) {
|
if (isPresent(context)) {
|
||||||
contextView = internalView(context.parentView);
|
contextView = internalView(context.parentView);
|
||||||
contextBoundElementIndex = context.boundElementIndex;
|
contextBoundElementIndex = context.boundElementIndex;
|
||||||
|
} else {
|
||||||
|
contextView = parentView;
|
||||||
|
contextBoundElementIndex = boundElementIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
var view = this._createPooledView(protoView);
|
var embeddedFragmentView = contextView.getNestedView(contextBoundElementIndex);
|
||||||
|
var view;
|
||||||
this._renderer.attachViewInContainer(viewContainerLocation, atIndex, view.render);
|
if (isPresent(embeddedFragmentView) && !embeddedFragmentView.hydrated()) {
|
||||||
|
// Case 1: instantiate the first view of a template that has been merged into a parent
|
||||||
|
view = embeddedFragmentView;
|
||||||
|
this._attachRenderView(parentView, boundElementIndex, atIndex, view);
|
||||||
|
} else {
|
||||||
|
// Case 2: instantiate another copy of the template. This is a separate case
|
||||||
|
// as we only inline one copy of the template into the parent view.
|
||||||
|
view = this._createPooledView(protoView);
|
||||||
|
this._attachRenderView(parentView, boundElementIndex, atIndex, view);
|
||||||
|
this._renderer.hydrateView(view.render);
|
||||||
|
}
|
||||||
this._utils.attachViewInContainer(parentView, boundElementIndex, contextView,
|
this._utils.attachViewInContainer(parentView, boundElementIndex, contextView,
|
||||||
contextBoundElementIndex, atIndex, view);
|
contextBoundElementIndex, atIndex, view);
|
||||||
this._utils.hydrateViewInContainer(parentView, boundElementIndex, contextView,
|
this._utils.hydrateViewInContainer(parentView, boundElementIndex, contextView,
|
||||||
contextBoundElementIndex, atIndex, bindings);
|
contextBoundElementIndex, atIndex, bindings);
|
||||||
this._viewHydrateRecurse(view);
|
|
||||||
return view.ref;
|
return view.ref;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_attachRenderView(parentView: viewModule.AppView, boundElementIndex: number, atIndex: number,
|
||||||
|
view: viewModule.AppView) {
|
||||||
|
var elementRef = parentView.elementRefs[boundElementIndex];
|
||||||
|
if (atIndex === 0) {
|
||||||
|
this._renderer.attachFragmentAfterElement(elementRef, view.renderFragment);
|
||||||
|
} else {
|
||||||
|
var prevView = parentView.viewContainers[boundElementIndex].views[atIndex - 1];
|
||||||
|
this._renderer.attachFragmentAfterFragment(prevView.renderFragment, view.renderFragment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* See {@link AppViewManager#createViewInContainer}.
|
* See {@link AppViewManager#createViewInContainer}.
|
||||||
@ -226,7 +243,7 @@ export class AppViewManager {
|
|||||||
// Right now we are destroying any special
|
// Right now we are destroying any special
|
||||||
// context view that might have been used.
|
// context view that might have been used.
|
||||||
this._utils.attachViewInContainer(parentView, boundElementIndex, null, null, atIndex, view);
|
this._utils.attachViewInContainer(parentView, boundElementIndex, null, null, atIndex, view);
|
||||||
this._renderer.attachViewInContainer(viewContainerLocation, atIndex, view.render);
|
this._attachRenderView(parentView, boundElementIndex, atIndex, view);
|
||||||
return viewRef;
|
return viewRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,86 +257,65 @@ export class AppViewManager {
|
|||||||
var viewContainer = parentView.viewContainers[boundElementIndex];
|
var viewContainer = parentView.viewContainers[boundElementIndex];
|
||||||
var view = viewContainer.views[atIndex];
|
var view = viewContainer.views[atIndex];
|
||||||
this._utils.detachViewInContainer(parentView, boundElementIndex, atIndex);
|
this._utils.detachViewInContainer(parentView, boundElementIndex, atIndex);
|
||||||
this._renderer.detachViewInContainer(viewContainerLocation, atIndex, view.render);
|
this._renderer.detachFragment(view.renderFragment);
|
||||||
return view.ref;
|
return view.ref;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_createMainView(protoView: viewModule.AppProtoView,
|
||||||
|
renderViewWithFragments: RenderViewWithFragments): viewModule.AppView {
|
||||||
|
var mergedParentView =
|
||||||
|
this._utils.createView(protoView, renderViewWithFragments, this, this._renderer);
|
||||||
|
this._renderer.setEventDispatcher(mergedParentView.render, mergedParentView);
|
||||||
|
this._viewListener.viewCreated(mergedParentView);
|
||||||
|
return mergedParentView;
|
||||||
|
}
|
||||||
|
|
||||||
_createPooledView(protoView: viewModule.AppProtoView): viewModule.AppView {
|
_createPooledView(protoView: viewModule.AppProtoView): viewModule.AppView {
|
||||||
var view = this._viewPool.getView(protoView);
|
var view = this._viewPool.getView(protoView);
|
||||||
if (isBlank(view)) {
|
if (isBlank(view)) {
|
||||||
view = this._utils.createView(protoView, this._renderer.createView(protoView.render), this,
|
view = this._createMainView(
|
||||||
this._renderer);
|
protoView, this._renderer.createView(protoView.mergeMapping.renderProtoViewRef,
|
||||||
this._renderer.setEventDispatcher(view.render, view);
|
protoView.mergeMapping.renderFragmentCount));
|
||||||
this._createViewRecurse(view);
|
|
||||||
this._viewListener.viewCreated(view);
|
|
||||||
}
|
}
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
_createViewRecurse(view: viewModule.AppView) {
|
|
||||||
var binders = view.proto.elementBinders;
|
|
||||||
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
|
|
||||||
var binder = binders[binderIdx];
|
|
||||||
if (binder.hasStaticComponent()) {
|
|
||||||
var childView = this._createPooledView(binder.nestedProtoView);
|
|
||||||
this._renderer.attachComponentView(view.elementRefs[binderIdx], childView.render);
|
|
||||||
this._utils.attachComponentView(view, binderIdx, childView);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_destroyPooledView(view: viewModule.AppView) {
|
_destroyPooledView(view: viewModule.AppView) {
|
||||||
var wasReturned = this._viewPool.returnView(view);
|
var wasReturned = this._viewPool.returnView(view);
|
||||||
if (!wasReturned) {
|
if (!wasReturned) {
|
||||||
this._renderer.destroyView(view.render);
|
|
||||||
this._viewListener.viewDestroyed(view);
|
this._viewListener.viewDestroyed(view);
|
||||||
|
this._renderer.destroyView(view.render);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_destroyViewInContainer(parentView, boundElementIndex, atIndex: number) {
|
_destroyViewInContainer(parentView: viewModule.AppView, boundElementIndex: number,
|
||||||
|
atIndex: number) {
|
||||||
var viewContainer = parentView.viewContainers[boundElementIndex];
|
var viewContainer = parentView.viewContainers[boundElementIndex];
|
||||||
var view = viewContainer.views[atIndex];
|
var view = viewContainer.views[atIndex];
|
||||||
this._viewDehydrateRecurse(view, false);
|
|
||||||
|
this._viewDehydrateRecurse(view);
|
||||||
this._utils.detachViewInContainer(parentView, boundElementIndex, atIndex);
|
this._utils.detachViewInContainer(parentView, boundElementIndex, atIndex);
|
||||||
this._renderer.detachViewInContainer(parentView.elementRefs[boundElementIndex], atIndex,
|
if (view.viewOffset > 0) {
|
||||||
view.render);
|
// Case 1: a view that is part of another view.
|
||||||
this._destroyPooledView(view);
|
// Just detach the fragment
|
||||||
}
|
this._renderer.detachFragment(view.renderFragment);
|
||||||
|
} else {
|
||||||
_destroyComponentView(hostView, boundElementIndex, componentView) {
|
// Case 2: a view that is not part of another view.
|
||||||
this._viewDehydrateRecurse(componentView, false);
|
// dehydrate and destroy it.
|
||||||
this._renderer.detachComponentView(hostView.elementRefs[boundElementIndex],
|
this._renderer.dehydrateView(view.render);
|
||||||
componentView.render);
|
this._renderer.detachFragment(view.renderFragment);
|
||||||
this._utils.detachComponentView(hostView, boundElementIndex);
|
this._destroyPooledView(view);
|
||||||
this._destroyPooledView(componentView);
|
|
||||||
}
|
|
||||||
|
|
||||||
_viewHydrateRecurse(view: viewModule.AppView) {
|
|
||||||
this._renderer.hydrateView(view.render);
|
|
||||||
|
|
||||||
var binders = view.proto.elementBinders;
|
|
||||||
for (var i = 0; i < binders.length; ++i) {
|
|
||||||
if (binders[i].hasStaticComponent()) {
|
|
||||||
this._utils.hydrateComponentView(view, i);
|
|
||||||
this._viewHydrateRecurse(view.componentChildViews[i]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_viewDehydrateRecurse(view: viewModule.AppView, forceDestroyComponents) {
|
_viewDehydrateRecurse(view: viewModule.AppView) {
|
||||||
this._utils.dehydrateView(view);
|
if (view.hydrated()) {
|
||||||
this._renderer.dehydrateView(view.render);
|
this._utils.dehydrateView(view);
|
||||||
var binders = view.proto.elementBinders;
|
}
|
||||||
for (var i = 0; i < binders.length; i++) {
|
var viewContainers = view.viewContainers;
|
||||||
var componentView = view.componentChildViews[i];
|
for (var i = view.elementOffset, ii = view.elementOffset + view.proto.mergeMapping.elementCount;
|
||||||
if (isPresent(componentView)) {
|
i < ii; i++) {
|
||||||
if (forceDestroyComponents) {
|
var vc = viewContainers[i];
|
||||||
this._destroyComponentView(view, i, componentView);
|
|
||||||
} else {
|
|
||||||
this._viewDehydrateRecurse(componentView, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var vc = view.viewContainers[i];
|
|
||||||
if (isPresent(vc)) {
|
if (isPresent(vc)) {
|
||||||
for (var j = vc.views.length - 1; j >= 0; j--) {
|
for (var j = vc.views.length - 1; j >= 0; j--) {
|
||||||
this._destroyViewInContainer(view, i, j);
|
this._destroyViewInContainer(view, i, j);
|
||||||
|
@ -3,10 +3,12 @@ import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src
|
|||||||
import * as eli from './element_injector';
|
import * as eli from './element_injector';
|
||||||
import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
|
import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
|
||||||
import * as viewModule from './view';
|
import * as viewModule from './view';
|
||||||
|
import {internalView} from './view_ref';
|
||||||
import * as avmModule from './view_manager';
|
import * as avmModule from './view_manager';
|
||||||
import {Renderer} from 'angular2/src/render/api';
|
import {ElementRef} from './element_ref';
|
||||||
|
import {Renderer, RenderViewWithFragments} from 'angular2/src/render/api';
|
||||||
import {Locals} from 'angular2/change_detection';
|
import {Locals} from 'angular2/change_detection';
|
||||||
import {RenderViewRef} from 'angular2/src/render/api';
|
import {RenderViewRef, RenderFragmentRef, ViewType} from 'angular2/src/render/api';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AppViewManagerUtils {
|
export class AppViewManagerUtils {
|
||||||
@ -17,68 +19,84 @@ export class AppViewManagerUtils {
|
|||||||
return eli.getComponent();
|
return eli.getComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
createView(protoView: viewModule.AppProtoView, renderView: RenderViewRef,
|
createView(mergedParentViewProto: viewModule.AppProtoView,
|
||||||
|
renderViewWithFragments: RenderViewWithFragments,
|
||||||
viewManager: avmModule.AppViewManager, renderer: Renderer): viewModule.AppView {
|
viewManager: avmModule.AppViewManager, renderer: Renderer): viewModule.AppView {
|
||||||
var view = new viewModule.AppView(renderer, protoView, protoView.protoLocals);
|
var renderFragments = renderViewWithFragments.fragmentRefs;
|
||||||
// TODO(tbosch): pass RenderViewRef as argument to AppView!
|
var renderView = renderViewWithFragments.viewRef;
|
||||||
view.render = renderView;
|
|
||||||
|
|
||||||
var changeDetector = protoView.protoChangeDetector.instantiate(view);
|
var elementCount = mergedParentViewProto.mergeMapping.elementCount;
|
||||||
|
var viewCount = mergedParentViewProto.mergeMapping.viewCount;
|
||||||
|
var elementRefs: ElementRef[] = ListWrapper.createFixedSize(elementCount);
|
||||||
|
var viewContainers = ListWrapper.createFixedSize(elementCount);
|
||||||
|
var preBuiltObjects: eli.PreBuiltObjects[] = ListWrapper.createFixedSize(elementCount);
|
||||||
|
var elementInjectors = ListWrapper.createFixedSize(elementCount);
|
||||||
|
var views = ListWrapper.createFixedSize(viewCount);
|
||||||
|
|
||||||
var binders = protoView.elementBinders;
|
var elementOffset = 0;
|
||||||
var elementInjectors = ListWrapper.createFixedSize(binders.length);
|
var textOffset = 0;
|
||||||
var rootElementInjectors = [];
|
var fragmentIdx = 0;
|
||||||
var preBuiltObjects = ListWrapper.createFixedSize(binders.length);
|
for (var viewOffset = 0; viewOffset < viewCount; viewOffset++) {
|
||||||
var componentChildViews = ListWrapper.createFixedSize(binders.length);
|
var hostElementIndex =
|
||||||
|
mergedParentViewProto.mergeMapping.hostElementIndicesByViewIndex[viewOffset];
|
||||||
|
var parentView = isPresent(hostElementIndex) ?
|
||||||
|
internalView(elementRefs[hostElementIndex].parentView) :
|
||||||
|
null;
|
||||||
|
var protoView =
|
||||||
|
isPresent(hostElementIndex) ?
|
||||||
|
parentView.proto.elementBinders[hostElementIndex - parentView.elementOffset]
|
||||||
|
.nestedProtoView :
|
||||||
|
mergedParentViewProto;
|
||||||
|
var renderFragment = null;
|
||||||
|
if (viewOffset === 0 || protoView.type === ViewType.EMBEDDED) {
|
||||||
|
renderFragment = renderFragments[fragmentIdx++];
|
||||||
|
}
|
||||||
|
var currentView = new viewModule.AppView(
|
||||||
|
renderer, protoView, mergedParentViewProto.mergeMapping, viewOffset, elementOffset,
|
||||||
|
textOffset, protoView.protoLocals, renderView, renderFragment);
|
||||||
|
views[viewOffset] = currentView;
|
||||||
|
var rootElementInjectors = [];
|
||||||
|
for (var binderIdx = 0; binderIdx < protoView.elementBinders.length; binderIdx++) {
|
||||||
|
var binder = protoView.elementBinders[binderIdx];
|
||||||
|
var boundElementIndex = elementOffset + binderIdx;
|
||||||
|
var elementInjector = null;
|
||||||
|
|
||||||
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
|
// elementInjectors and rootElementInjectors
|
||||||
var binder = binders[binderIdx];
|
var protoElementInjector = binder.protoElementInjector;
|
||||||
var elementInjector = null;
|
if (isPresent(protoElementInjector)) {
|
||||||
|
if (isPresent(protoElementInjector.parent)) {
|
||||||
|
var parentElementInjector =
|
||||||
|
elementInjectors[elementOffset + protoElementInjector.parent.index];
|
||||||
|
elementInjector = protoElementInjector.instantiate(parentElementInjector);
|
||||||
|
} else {
|
||||||
|
elementInjector = protoElementInjector.instantiate(null);
|
||||||
|
rootElementInjectors.push(elementInjector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elementInjectors[boundElementIndex] = elementInjector;
|
||||||
|
|
||||||
// elementInjectors and rootElementInjectors
|
// elementRefs
|
||||||
var protoElementInjector = binder.protoElementInjector;
|
var el = new ElementRef(
|
||||||
if (isPresent(protoElementInjector)) {
|
currentView.ref, boundElementIndex,
|
||||||
if (isPresent(protoElementInjector.parent)) {
|
mergedParentViewProto.mergeMapping.renderElementIndices[boundElementIndex], renderer);
|
||||||
var parentElementInjector = elementInjectors[protoElementInjector.parent.index];
|
elementRefs[el.boundElementIndex] = el;
|
||||||
elementInjector = protoElementInjector.instantiate(parentElementInjector);
|
|
||||||
} else {
|
// preBuiltObjects
|
||||||
elementInjector = protoElementInjector.instantiate(null);
|
if (isPresent(elementInjector)) {
|
||||||
rootElementInjectors.push(elementInjector);
|
var embeddedProtoView = binder.hasEmbeddedProtoView() ? binder.nestedProtoView : null;
|
||||||
|
preBuiltObjects[boundElementIndex] =
|
||||||
|
new eli.PreBuiltObjects(viewManager, currentView, el, embeddedProtoView);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
elementInjectors[binderIdx] = elementInjector;
|
currentView.init(protoView.protoChangeDetector.instantiate(currentView), elementInjectors,
|
||||||
|
rootElementInjectors, preBuiltObjects, views, elementRefs, viewContainers);
|
||||||
// preBuiltObjects
|
if (isPresent(parentView) && protoView.type === ViewType.COMPONENT) {
|
||||||
if (isPresent(elementInjector)) {
|
parentView.changeDetector.addShadowDomChild(currentView.changeDetector);
|
||||||
var embeddedProtoView = binder.hasEmbeddedProtoView() ? binder.nestedProtoView : null;
|
|
||||||
preBuiltObjects[binderIdx] = new eli.PreBuiltObjects(viewManager, view, embeddedProtoView);
|
|
||||||
}
|
}
|
||||||
|
elementOffset += protoView.elementBinders.length;
|
||||||
|
textOffset += protoView.textBindingCount;
|
||||||
}
|
}
|
||||||
|
return views[0];
|
||||||
view.init(changeDetector, elementInjectors, rootElementInjectors, preBuiltObjects,
|
|
||||||
componentChildViews);
|
|
||||||
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
attachComponentView(hostView: viewModule.AppView, boundElementIndex: number,
|
|
||||||
componentView: viewModule.AppView) {
|
|
||||||
var childChangeDetector = componentView.changeDetector;
|
|
||||||
hostView.changeDetector.addShadowDomChild(childChangeDetector);
|
|
||||||
hostView.componentChildViews[boundElementIndex] = componentView;
|
|
||||||
}
|
|
||||||
|
|
||||||
detachComponentView(hostView: viewModule.AppView, boundElementIndex: number) {
|
|
||||||
var componentView = hostView.componentChildViews[boundElementIndex];
|
|
||||||
hostView.changeDetector.removeShadowDomChild(componentView.changeDetector);
|
|
||||||
hostView.componentChildViews[boundElementIndex] = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
hydrateComponentView(hostView: viewModule.AppView, boundElementIndex: number) {
|
|
||||||
var elementInjector = hostView.elementInjectors[boundElementIndex];
|
|
||||||
var componentView = hostView.componentChildViews[boundElementIndex];
|
|
||||||
var component = this.getComponentInstance(hostView, boundElementIndex);
|
|
||||||
this._hydrateView(componentView, null, elementInjector, component, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hydrateRootHostView(hostView: viewModule.AppView, injector: Injector) {
|
hydrateRootHostView(hostView: viewModule.AppView, injector: Injector) {
|
||||||
@ -94,7 +112,11 @@ export class AppViewManagerUtils {
|
|||||||
contextBoundElementIndex = boundElementIndex;
|
contextBoundElementIndex = boundElementIndex;
|
||||||
}
|
}
|
||||||
parentView.changeDetector.addChild(view.changeDetector);
|
parentView.changeDetector.addChild(view.changeDetector);
|
||||||
var viewContainer = this._getOrCreateViewContainer(parentView, boundElementIndex);
|
var viewContainer = parentView.viewContainers[boundElementIndex];
|
||||||
|
if (isBlank(viewContainer)) {
|
||||||
|
viewContainer = new viewModule.AppViewContainer();
|
||||||
|
parentView.viewContainers[boundElementIndex] = viewContainer;
|
||||||
|
}
|
||||||
ListWrapper.insert(viewContainer.views, atIndex, view);
|
ListWrapper.insert(viewContainer.views, atIndex, view);
|
||||||
var sibling;
|
var sibling;
|
||||||
if (atIndex == 0) {
|
if (atIndex == 0) {
|
||||||
@ -124,7 +146,9 @@ export class AppViewManagerUtils {
|
|||||||
inj.unlink();
|
inj.unlink();
|
||||||
} else {
|
} else {
|
||||||
var removeIdx = ListWrapper.indexOf(parentView.rootElementInjectors, inj);
|
var removeIdx = ListWrapper.indexOf(parentView.rootElementInjectors, inj);
|
||||||
ListWrapper.removeAt(parentView.rootElementInjectors, removeIdx);
|
if (removeIdx >= 0) {
|
||||||
|
ListWrapper.removeAt(parentView.rootElementInjectors, removeIdx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -145,25 +169,45 @@ export class AppViewManagerUtils {
|
|||||||
contextView.locals);
|
contextView.locals);
|
||||||
}
|
}
|
||||||
|
|
||||||
_hydrateView(view: viewModule.AppView, imperativelyCreatedInjector: Injector,
|
_hydrateView(initView: viewModule.AppView, imperativelyCreatedInjector: Injector,
|
||||||
hostElementInjector: eli.ElementInjector, context: Object, parentLocals: Locals) {
|
hostElementInjector: eli.ElementInjector, context: Object, parentLocals: Locals) {
|
||||||
view.context = context;
|
var viewIdx = initView.viewOffset;
|
||||||
view.locals.parent = parentLocals;
|
var endViewOffset = viewIdx + initView.proto.mergeMapping.viewCount;
|
||||||
|
while (viewIdx < endViewOffset) {
|
||||||
|
var currView = initView.views[viewIdx];
|
||||||
|
var currProtoView = currView.proto;
|
||||||
|
if (currView !== initView && currView.proto.type === ViewType.EMBEDDED) {
|
||||||
|
// Don't hydrate components of embedded fragment views.
|
||||||
|
viewIdx += currProtoView.mergeMapping.viewCount;
|
||||||
|
} else {
|
||||||
|
if (currView !== initView) {
|
||||||
|
// hydrate a nested component view
|
||||||
|
imperativelyCreatedInjector = null;
|
||||||
|
parentLocals = null;
|
||||||
|
var hostElementIndex = initView.mainMergeMapping.hostElementIndicesByViewIndex[viewIdx];
|
||||||
|
hostElementInjector = initView.elementInjectors[hostElementIndex];
|
||||||
|
context = hostElementInjector.getComponent();
|
||||||
|
}
|
||||||
|
currView.context = context;
|
||||||
|
currView.locals.parent = parentLocals;
|
||||||
|
var binders = currProtoView.elementBinders;
|
||||||
|
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
|
||||||
|
var boundElementIndex = binderIdx + currView.elementOffset;
|
||||||
|
var elementInjector = initView.elementInjectors[boundElementIndex];
|
||||||
|
|
||||||
var binders = view.proto.elementBinders;
|
if (isPresent(elementInjector)) {
|
||||||
for (var i = 0; i < binders.length; ++i) {
|
elementInjector.hydrate(imperativelyCreatedInjector, hostElementInjector,
|
||||||
var elementInjector = view.elementInjectors[i];
|
currView.preBuiltObjects[boundElementIndex]);
|
||||||
|
this._populateViewLocals(currView, elementInjector, boundElementIndex);
|
||||||
if (isPresent(elementInjector)) {
|
this._setUpEventEmitters(currView, elementInjector, boundElementIndex);
|
||||||
elementInjector.hydrate(imperativelyCreatedInjector, hostElementInjector,
|
this._setUpHostActions(currView, elementInjector, boundElementIndex);
|
||||||
view.preBuiltObjects[i]);
|
}
|
||||||
this._populateViewLocals(view, elementInjector);
|
}
|
||||||
this._setUpEventEmitters(view, elementInjector, i);
|
var pipes = this._getPipes(imperativelyCreatedInjector, hostElementInjector);
|
||||||
this._setUpHostActions(view, elementInjector, i);
|
currView.changeDetector.hydrate(currView.context, currView.locals, currView, pipes);
|
||||||
|
viewIdx++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var pipes = this._getPipes(imperativelyCreatedInjector, hostElementInjector);
|
|
||||||
view.changeDetector.hydrate(view.context, view.locals, view, pipes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_getPipes(imperativelyCreatedInjector: Injector, hostElementInjector: eli.ElementInjector) {
|
_getPipes(imperativelyCreatedInjector: Injector, hostElementInjector: eli.ElementInjector) {
|
||||||
@ -174,11 +218,12 @@ export class AppViewManagerUtils {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_populateViewLocals(view: viewModule.AppView, elementInjector: eli.ElementInjector): void {
|
_populateViewLocals(view: viewModule.AppView, elementInjector: eli.ElementInjector,
|
||||||
|
boundElementIdx: number): void {
|
||||||
if (isPresent(elementInjector.getDirectiveVariableBindings())) {
|
if (isPresent(elementInjector.getDirectiveVariableBindings())) {
|
||||||
MapWrapper.forEach(elementInjector.getDirectiveVariableBindings(), (directiveIndex, name) => {
|
MapWrapper.forEach(elementInjector.getDirectiveVariableBindings(), (directiveIndex, name) => {
|
||||||
if (isBlank(directiveIndex)) {
|
if (isBlank(directiveIndex)) {
|
||||||
view.locals.set(name, elementInjector.getElementRef().nativeElement);
|
view.locals.set(name, view.elementRefs[boundElementIdx].nativeElement);
|
||||||
} else {
|
} else {
|
||||||
view.locals.set(name, elementInjector.getDirectiveAtIndex(directiveIndex));
|
view.locals.set(name, elementInjector.getDirectiveAtIndex(directiveIndex));
|
||||||
}
|
}
|
||||||
@ -186,15 +231,6 @@ export class AppViewManagerUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_getOrCreateViewContainer(parentView: viewModule.AppView, boundElementIndex: number) {
|
|
||||||
var viewContainer = parentView.viewContainers[boundElementIndex];
|
|
||||||
if (isBlank(viewContainer)) {
|
|
||||||
viewContainer = new viewModule.AppViewContainer();
|
|
||||||
parentView.viewContainers[boundElementIndex] = viewContainer;
|
|
||||||
}
|
|
||||||
return viewContainer;
|
|
||||||
}
|
|
||||||
|
|
||||||
_setUpEventEmitters(view: viewModule.AppView, elementInjector: eli.ElementInjector,
|
_setUpEventEmitters(view: viewModule.AppView, elementInjector: eli.ElementInjector,
|
||||||
boundElementIndex: number) {
|
boundElementIndex: number) {
|
||||||
var emitters = elementInjector.getEventEmitterAccessors();
|
var emitters = elementInjector.getEventEmitterAccessors();
|
||||||
@ -223,18 +259,25 @@ export class AppViewManagerUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dehydrateView(view: viewModule.AppView) {
|
dehydrateView(initView: viewModule.AppView) {
|
||||||
var binders = view.proto.elementBinders;
|
for (var viewIdx = initView.viewOffset,
|
||||||
for (var i = 0; i < binders.length; ++i) {
|
endViewOffset = viewIdx + initView.proto.mergeMapping.viewCount;
|
||||||
var elementInjector = view.elementInjectors[i];
|
viewIdx < endViewOffset; viewIdx++) {
|
||||||
if (isPresent(elementInjector)) {
|
var currView = initView.views[viewIdx];
|
||||||
elementInjector.dehydrate();
|
if (currView.hydrated()) {
|
||||||
|
if (isPresent(currView.locals)) {
|
||||||
|
currView.locals.clearValues();
|
||||||
|
}
|
||||||
|
currView.context = null;
|
||||||
|
currView.changeDetector.dehydrate();
|
||||||
|
var binders = currView.proto.elementBinders;
|
||||||
|
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
|
||||||
|
var eli = initView.elementInjectors[currView.elementOffset + binderIdx];
|
||||||
|
if (isPresent(eli)) {
|
||||||
|
eli.dehydrate();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isPresent(view.locals)) {
|
|
||||||
view.locals.clearValues();
|
|
||||||
}
|
|
||||||
view.context = null;
|
|
||||||
view.changeDetector.dehydrate();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import {isPresent} from 'angular2/src/facade/lang';
|
import {isPresent} from 'angular2/src/facade/lang';
|
||||||
import * as viewModule from './view';
|
import * as viewModule from './view';
|
||||||
import {RenderViewRef} from 'angular2/src/render/api';
|
import {RenderViewRef, RenderFragmentRef} from 'angular2/src/render/api';
|
||||||
|
|
||||||
// This is a workaround for privacy in Dart as we don't have library parts
|
// This is a workaround for privacy in Dart as we don't have library parts
|
||||||
export function internalView(viewRef: ViewRef): viewModule.AppView {
|
export function internalView(viewRef: ViewRef): viewModule.AppView {
|
||||||
@ -71,6 +71,11 @@ export class ViewRef {
|
|||||||
*/
|
*/
|
||||||
get render(): RenderViewRef { return this._view.render; }
|
get render(): RenderViewRef { return this._view.render; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return {@link RenderFragmentRef}
|
||||||
|
*/
|
||||||
|
get renderFragment(): RenderFragmentRef { return this._view.renderFragment; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set local variable for a view.
|
* Set local variable for a view.
|
||||||
*
|
*
|
||||||
|
@ -45,9 +45,7 @@ export class DebugElement {
|
|||||||
* @return {List<DebugElement>}
|
* @return {List<DebugElement>}
|
||||||
*/
|
*/
|
||||||
get children(): List<DebugElement> {
|
get children(): List<DebugElement> {
|
||||||
var thisElementBinder = this._parentView.proto.elementBinders[this._boundElementIndex];
|
return this._getChildElements(this._parentView, this._boundElementIndex);
|
||||||
|
|
||||||
return this._getChildElements(this._parentView, thisElementBinder.index);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -57,7 +55,7 @@ export class DebugElement {
|
|||||||
* @return {List<DebugElement>}
|
* @return {List<DebugElement>}
|
||||||
*/
|
*/
|
||||||
get componentViewChildren(): List<DebugElement> {
|
get componentViewChildren(): List<DebugElement> {
|
||||||
var shadowView = this._parentView.componentChildViews[this._boundElementIndex];
|
var shadowView = this._parentView.getNestedView(this._boundElementIndex);
|
||||||
|
|
||||||
if (!isPresent(shadowView)) {
|
if (!isPresent(shadowView)) {
|
||||||
// The current element is not a component.
|
// The current element is not a component.
|
||||||
@ -120,14 +118,14 @@ export class DebugElement {
|
|||||||
var els = [];
|
var els = [];
|
||||||
var parentElementBinder = null;
|
var parentElementBinder = null;
|
||||||
if (isPresent(parentBoundElementIndex)) {
|
if (isPresent(parentBoundElementIndex)) {
|
||||||
parentElementBinder = view.proto.elementBinders[parentBoundElementIndex];
|
parentElementBinder = view.proto.elementBinders[parentBoundElementIndex - view.elementOffset];
|
||||||
}
|
}
|
||||||
for (var i = 0; i < view.proto.elementBinders.length; ++i) {
|
for (var i = 0; i < view.proto.elementBinders.length; ++i) {
|
||||||
var binder = view.proto.elementBinders[i];
|
var binder = view.proto.elementBinders[i];
|
||||||
if (binder.parent == parentElementBinder) {
|
if (binder.parent == parentElementBinder) {
|
||||||
els.push(new DebugElement(view, i));
|
els.push(new DebugElement(view, view.elementOffset + i));
|
||||||
|
|
||||||
var views = view.viewContainers[i];
|
var views = view.viewContainers[view.elementOffset + i];
|
||||||
if (isPresent(views)) {
|
if (isPresent(views)) {
|
||||||
ListWrapper.forEach(views.views, (nextView) => {
|
ListWrapper.forEach(views.views, (nextView) => {
|
||||||
els = ListWrapper.concat(els, this._getChildElements(nextView, null));
|
els = ListWrapper.concat(els, this._getChildElements(nextView, null));
|
||||||
@ -184,7 +182,11 @@ export class By {
|
|||||||
static all(): Function { return (debugElement) => true; }
|
static all(): Function { return (debugElement) => true; }
|
||||||
|
|
||||||
static css(selector: string): Predicate<DebugElement> {
|
static css(selector: string): Predicate<DebugElement> {
|
||||||
return (debugElement) => { return DOM.elementMatches(debugElement.nativeElement, selector); };
|
return (debugElement) => {
|
||||||
|
return isPresent(debugElement.nativeElement) ?
|
||||||
|
DOM.elementMatches(debugElement.nativeElement, selector) :
|
||||||
|
false;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
static directive(type: Type): Predicate<DebugElement> {
|
static directive(type: Type): Predicate<DebugElement> {
|
||||||
return (debugElement) => { return debugElement.hasDirective(type); };
|
return (debugElement) => { return debugElement.hasDirective(type); };
|
||||||
|
@ -56,7 +56,8 @@ export class DebugElementViewListener implements AppViewListener {
|
|||||||
_allViewsById.set(viewId, view);
|
_allViewsById.set(viewId, view);
|
||||||
_allIdsByView.set(view, viewId);
|
_allIdsByView.set(view, viewId);
|
||||||
for (var i = 0; i < view.elementRefs.length; i++) {
|
for (var i = 0; i < view.elementRefs.length; i++) {
|
||||||
_setElementId(this._renderer.getNativeElementSync(view.elementRefs[i]), [viewId, i]);
|
var el = view.elementRefs[i];
|
||||||
|
_setElementId(this._renderer.getNativeElementSync(el), [viewId, i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
|||||||
get attrToPropMap(): any { return _attrToPropMap; }
|
get attrToPropMap(): any { return _attrToPropMap; }
|
||||||
|
|
||||||
query(selector: string): any { return document.querySelector(selector); }
|
query(selector: string): any { return document.querySelector(selector); }
|
||||||
querySelector(el, selector: string): Node { return el.querySelector(selector); }
|
querySelector(el, selector: string): HTMLElement { return el.querySelector(selector); }
|
||||||
querySelectorAll(el, selector: string): List<any> { return el.querySelectorAll(selector); }
|
querySelectorAll(el, selector: string): List<any> { return el.querySelectorAll(selector); }
|
||||||
on(el, evt, listener) { el.addEventListener(evt, listener, false); }
|
on(el, evt, listener) { el.addEventListener(evt, listener, false); }
|
||||||
onAndCancel(el, evt, listener): Function {
|
onAndCancel(el, evt, listener): Function {
|
||||||
@ -112,17 +112,16 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
clearNodes(el) {
|
clearNodes(el) {
|
||||||
for (var i = 0; i < el.childNodes.length; i++) {
|
while (el.firstChild) {
|
||||||
this.remove(el.childNodes[i]);
|
el.firstChild.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
appendChild(el, node) { el.appendChild(node); }
|
appendChild(el, node) { el.appendChild(node); }
|
||||||
removeChild(el, node) { el.removeChild(node); }
|
removeChild(el, node) { el.removeChild(node); }
|
||||||
replaceChild(el: Node, newChild, oldChild) { el.replaceChild(newChild, oldChild); }
|
replaceChild(el: Node, newChild, oldChild) { el.replaceChild(newChild, oldChild); }
|
||||||
remove(el): Node {
|
remove(node): Node {
|
||||||
var parent = el.parentNode;
|
node.remove();
|
||||||
parent.removeChild(el);
|
return node;
|
||||||
return el;
|
|
||||||
}
|
}
|
||||||
insertBefore(el, node) { el.parentNode.insertBefore(node, el); }
|
insertBefore(el, node) { el.parentNode.insertBefore(node, el); }
|
||||||
insertAllBefore(el, nodes) {
|
insertAllBefore(el, nodes) {
|
||||||
|
@ -31,7 +31,7 @@ export class DomAdapter {
|
|||||||
|
|
||||||
parse(templateHtml: string) { throw _abstract(); }
|
parse(templateHtml: string) { throw _abstract(); }
|
||||||
query(selector: string): any { throw _abstract(); }
|
query(selector: string): any { throw _abstract(); }
|
||||||
querySelector(el, selector: string) { throw _abstract(); }
|
querySelector(el, selector: string): HTMLElement { throw _abstract(); }
|
||||||
querySelectorAll(el, selector: string): List<any> { throw _abstract(); }
|
querySelectorAll(el, selector: string): List<any> { throw _abstract(); }
|
||||||
on(el, evt, listener) { throw _abstract(); }
|
on(el, evt, listener) { throw _abstract(); }
|
||||||
onAndCancel(el, evt, listener): Function { throw _abstract(); }
|
onAndCancel(el, evt, listener): Function { throw _abstract(); }
|
||||||
@ -76,7 +76,7 @@ export class DomAdapter {
|
|||||||
getShadowRoot(el): any { throw _abstract(); }
|
getShadowRoot(el): any { throw _abstract(); }
|
||||||
getHost(el): any { throw _abstract(); }
|
getHost(el): any { throw _abstract(); }
|
||||||
getDistributedNodes(el): List<Node> { throw _abstract(); }
|
getDistributedNodes(el): List<Node> { throw _abstract(); }
|
||||||
clone(node: Node): Node { throw _abstract(); }
|
clone /*<T extends Node>*/ (node: Node /*T*/): Node /*T*/ { throw _abstract(); }
|
||||||
getElementsByClassName(element, name: string): List<HTMLElement> { throw _abstract(); }
|
getElementsByClassName(element, name: string): List<HTMLElement> { throw _abstract(); }
|
||||||
getElementsByTagName(element, name: string): List<HTMLElement> { throw _abstract(); }
|
getElementsByTagName(element, name: string): List<HTMLElement> { throw _abstract(); }
|
||||||
classList(element): List<any> { throw _abstract(); }
|
classList(element): List<any> { throw _abstract(); }
|
||||||
@ -105,7 +105,7 @@ export class DomAdapter {
|
|||||||
isElementNode(node): boolean { throw _abstract(); }
|
isElementNode(node): boolean { throw _abstract(); }
|
||||||
hasShadowRoot(node): boolean { throw _abstract(); }
|
hasShadowRoot(node): boolean { throw _abstract(); }
|
||||||
isShadowRoot(node): boolean { throw _abstract(); }
|
isShadowRoot(node): boolean { throw _abstract(); }
|
||||||
importIntoDoc(node) { throw _abstract(); }
|
importIntoDoc /*<T extends Node>*/ (node: Node /*T*/): Node /*T*/ { throw _abstract(); }
|
||||||
isPageRule(rule): boolean { throw _abstract(); }
|
isPageRule(rule): boolean { throw _abstract(); }
|
||||||
isStyleRule(rule): boolean { throw _abstract(); }
|
isStyleRule(rule): boolean { throw _abstract(); }
|
||||||
isMediaRule(rule): boolean { throw _abstract(); }
|
isMediaRule(rule): boolean { throw _abstract(); }
|
||||||
|
@ -19,6 +19,8 @@ var _attrToPropMap = {
|
|||||||
};
|
};
|
||||||
var defDoc = null;
|
var defDoc = null;
|
||||||
|
|
||||||
|
var mapProps = ['attribs', 'x-attribsNamespace', 'x-attribsPrefix'];
|
||||||
|
|
||||||
function _notImplemented(methodName) {
|
function _notImplemented(methodName) {
|
||||||
return new BaseException('This method is not implemented in Parse5DomAdapter: ' + methodName);
|
return new BaseException('This method is not implemented in Parse5DomAdapter: ' + methodName);
|
||||||
}
|
}
|
||||||
@ -271,18 +273,45 @@ export class Parse5DomAdapter extends DomAdapter {
|
|||||||
getHost(el): string { return el.host; }
|
getHost(el): string { return el.host; }
|
||||||
getDistributedNodes(el: any): List<Node> { throw _notImplemented('getDistributedNodes'); }
|
getDistributedNodes(el: any): List<Node> { throw _notImplemented('getDistributedNodes'); }
|
||||||
clone(node: Node): Node {
|
clone(node: Node): Node {
|
||||||
// e.g. document fragment
|
var _recursive = (node) => {
|
||||||
if ((<any>node).type === 'root') {
|
var nodeClone = Object.create(Object.getPrototypeOf(node));
|
||||||
var serialized = serializer.serialize(node);
|
for (var prop in node) {
|
||||||
var newParser = new parse5.Parser(parse5.TreeAdapters.htmlparser2);
|
var desc = Object.getOwnPropertyDescriptor(node, prop);
|
||||||
return newParser.parseFragment(serialized);
|
if (desc && 'value' in desc && typeof desc.value !== 'object') {
|
||||||
} else {
|
nodeClone[prop] = node[prop];
|
||||||
var temp = treeAdapter.createElement("template", null, []);
|
}
|
||||||
treeAdapter.appendChild(temp, node);
|
}
|
||||||
var serialized = serializer.serialize(temp);
|
nodeClone.parent = null;
|
||||||
var newParser = new parse5.Parser(parse5.TreeAdapters.htmlparser2);
|
nodeClone.prev = null;
|
||||||
return newParser.parseFragment(serialized).childNodes[0];
|
nodeClone.next = null;
|
||||||
}
|
nodeClone.children = null;
|
||||||
|
|
||||||
|
mapProps.forEach(mapName => {
|
||||||
|
if (isPresent(node[mapName])) {
|
||||||
|
nodeClone[mapName] = {};
|
||||||
|
for (var prop in node[mapName]) {
|
||||||
|
nodeClone[mapName][prop] = node[mapName][prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var cNodes = node.children;
|
||||||
|
if (cNodes) {
|
||||||
|
var cNodesClone = new Array(cNodes.length);
|
||||||
|
for (var i = 0; i < cNodes.length; i++) {
|
||||||
|
var childNode = cNodes[i];
|
||||||
|
var childNodeClone = _recursive(childNode);
|
||||||
|
cNodesClone[i] = childNodeClone;
|
||||||
|
if (i > 0) {
|
||||||
|
childNodeClone.prev = cNodesClone[i - 1];
|
||||||
|
cNodesClone[i - 1].next = childNodeClone;
|
||||||
|
}
|
||||||
|
childNodeClone.parent = nodeClone;
|
||||||
|
}
|
||||||
|
nodeClone.children = cNodesClone;
|
||||||
|
}
|
||||||
|
return nodeClone;
|
||||||
|
};
|
||||||
|
return _recursive(node);
|
||||||
}
|
}
|
||||||
getElementsByClassName(element, name: string): List<HTMLElement> {
|
getElementsByClassName(element, name: string): List<HTMLElement> {
|
||||||
return this.querySelectorAll(element, "." + name);
|
return this.querySelectorAll(element, "." + name);
|
||||||
|
@ -221,13 +221,17 @@ bool isJsObject(o) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _assertionsEnabled = null;
|
||||||
bool assertionsEnabled() {
|
bool assertionsEnabled() {
|
||||||
try {
|
if (_assertionsEnabled == null) {
|
||||||
assert(false);
|
try {
|
||||||
return false;
|
assert(false);
|
||||||
} catch (e) {
|
_assertionsEnabled = false;
|
||||||
return true;
|
} catch (e) {
|
||||||
|
_assertionsEnabled = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return _assertionsEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Can't be all uppercase as our transpiler would think it is a special directive...
|
// Can't be all uppercase as our transpiler would think it is a special directive...
|
||||||
|
@ -48,11 +48,10 @@ export class ElementBinder {
|
|||||||
// that replaced the values that should be extracted from the element
|
// that replaced the values that should be extracted from the element
|
||||||
// with a local name
|
// with a local name
|
||||||
eventBindings: List<EventBinding>;
|
eventBindings: List<EventBinding>;
|
||||||
textBindings: List<ASTWithSource>;
|
|
||||||
readAttributes: Map<string, string>;
|
readAttributes: Map<string, string>;
|
||||||
|
|
||||||
constructor({index, parentIndex, distanceToParent, directives, nestedProtoView, propertyBindings,
|
constructor({index, parentIndex, distanceToParent, directives, nestedProtoView, propertyBindings,
|
||||||
variableBindings, eventBindings, textBindings, readAttributes}: {
|
variableBindings, eventBindings, readAttributes}: {
|
||||||
index?: number,
|
index?: number,
|
||||||
parentIndex?: number,
|
parentIndex?: number,
|
||||||
distanceToParent?: number,
|
distanceToParent?: number,
|
||||||
@ -61,7 +60,6 @@ export class ElementBinder {
|
|||||||
propertyBindings?: List<ElementPropertyBinding>,
|
propertyBindings?: List<ElementPropertyBinding>,
|
||||||
variableBindings?: Map<string, string>,
|
variableBindings?: Map<string, string>,
|
||||||
eventBindings?: List<EventBinding>,
|
eventBindings?: List<EventBinding>,
|
||||||
textBindings?: List<ASTWithSource>,
|
|
||||||
readAttributes?: Map<string, string>
|
readAttributes?: Map<string, string>
|
||||||
} = {}) {
|
} = {}) {
|
||||||
this.index = index;
|
this.index = index;
|
||||||
@ -72,7 +70,6 @@ export class ElementBinder {
|
|||||||
this.propertyBindings = propertyBindings;
|
this.propertyBindings = propertyBindings;
|
||||||
this.variableBindings = variableBindings;
|
this.variableBindings = variableBindings;
|
||||||
this.eventBindings = eventBindings;
|
this.eventBindings = eventBindings;
|
||||||
this.textBindings = textBindings;
|
|
||||||
this.readAttributes = readAttributes;
|
this.readAttributes = readAttributes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -116,17 +113,20 @@ export class ProtoViewDto {
|
|||||||
elementBinders: List<ElementBinder>;
|
elementBinders: List<ElementBinder>;
|
||||||
variableBindings: Map<string, string>;
|
variableBindings: Map<string, string>;
|
||||||
type: ViewType;
|
type: ViewType;
|
||||||
|
textBindings: List<ASTWithSource>;
|
||||||
|
|
||||||
constructor({render, elementBinders, variableBindings, type}: {
|
constructor({render, elementBinders, variableBindings, type, textBindings}: {
|
||||||
render?: RenderProtoViewRef,
|
render?: RenderProtoViewRef,
|
||||||
elementBinders?: List<ElementBinder>,
|
elementBinders?: List<ElementBinder>,
|
||||||
variableBindings?: Map<string, string>,
|
variableBindings?: Map<string, string>,
|
||||||
type?: ViewType
|
type?: ViewType,
|
||||||
|
textBindings?: List<ASTWithSource>
|
||||||
}) {
|
}) {
|
||||||
this.render = render;
|
this.render = render;
|
||||||
this.elementBinders = elementBinders;
|
this.elementBinders = elementBinders;
|
||||||
this.variableBindings = variableBindings;
|
this.variableBindings = variableBindings;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
|
this.textBindings = textBindings;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,10 +260,13 @@ export class DirectiveMetadata {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// An opaque reference to a DomProtoView
|
// An opaque reference to a render proto ivew
|
||||||
export class RenderProtoViewRef {}
|
export class RenderProtoViewRef {}
|
||||||
|
|
||||||
// An opaque reference to a DomView
|
// An opaque reference to a part of a view
|
||||||
|
export class RenderFragmentRef {}
|
||||||
|
|
||||||
|
// An opaque reference to a view
|
||||||
export class RenderViewRef {}
|
export class RenderViewRef {}
|
||||||
|
|
||||||
export class ViewDefinition {
|
export class ViewDefinition {
|
||||||
@ -291,6 +294,23 @@ export class ViewDefinition {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class RenderProtoViewMergeMapping {
|
||||||
|
constructor(public mergedProtoViewRef: RenderProtoViewRef,
|
||||||
|
// Number of fragments in the merged ProtoView.
|
||||||
|
// Fragments are stored in depth first order of nested ProtoViews.
|
||||||
|
public fragmentCount: number,
|
||||||
|
// Mapping from app element index to render element index.
|
||||||
|
// Mappings of nested ProtoViews are in depth first order, with all
|
||||||
|
// indices for one ProtoView in a consecuitve block.
|
||||||
|
public mappedElementIndices: number[],
|
||||||
|
// Mapping from app text index to render text index.
|
||||||
|
// Mappings of nested ProtoViews are in depth first order, with all
|
||||||
|
// indices for one ProtoView in a consecuitve block.
|
||||||
|
public mappedTextIndices: number[],
|
||||||
|
// Mapping from view index to app element index
|
||||||
|
public hostElementIndicesByViewIndex: number[]) {}
|
||||||
|
}
|
||||||
|
|
||||||
export class RenderCompiler {
|
export class RenderCompiler {
|
||||||
/**
|
/**
|
||||||
* Creats a ProtoViewDto that contains a single nested component with the given componentId.
|
* Creats a ProtoViewDto that contains a single nested component with the given componentId.
|
||||||
@ -303,6 +323,24 @@ export class RenderCompiler {
|
|||||||
* but only the needed ones based on previous calls.
|
* but only the needed ones based on previous calls.
|
||||||
*/
|
*/
|
||||||
compile(view: ViewDefinition): Promise<ProtoViewDto> { return null; }
|
compile(view: ViewDefinition): Promise<ProtoViewDto> { return null; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merges ProtoViews.
|
||||||
|
* The first entry of the array is the protoview into which all the other entries of the array
|
||||||
|
* should be merged.
|
||||||
|
* If the array contains other arrays, they will be merged before processing the parent array.
|
||||||
|
* The array must contain an entry for every component and embedded ProtoView of the first entry.
|
||||||
|
* @param protoViewRefs List of ProtoViewRefs or nested
|
||||||
|
* @return the merge result for every input array in depth first order.
|
||||||
|
*/
|
||||||
|
mergeProtoViewsRecursively(
|
||||||
|
protoViewRefs: List<RenderProtoViewRef | List<any>>): Promise<RenderProtoViewMergeMapping[]> {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RenderViewWithFragments {
|
||||||
|
constructor(public viewRef: RenderViewRef, public fragmentRefs: RenderFragmentRef[]) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -315,33 +353,39 @@ export interface RenderElementRef {
|
|||||||
* Reference to the {@link RenderViewRef} where the `RenderElementRef` is inside of.
|
* Reference to the {@link RenderViewRef} where the `RenderElementRef` is inside of.
|
||||||
*/
|
*/
|
||||||
renderView: RenderViewRef;
|
renderView: RenderViewRef;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Index of the element inside the {@link ViewRef}.
|
* Index of the element inside the {@link RenderViewRef}.
|
||||||
*
|
*
|
||||||
* This is used internally by the Angular framework to locate elements.
|
* This is used internally by the Angular framework to locate elements.
|
||||||
*/
|
*/
|
||||||
boundElementIndex: number;
|
renderBoundElementIndex: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Renderer {
|
export class Renderer {
|
||||||
/**
|
/**
|
||||||
* Creates a root host view that includes the given element.
|
* Creates a root host view that includes the given element.
|
||||||
|
* Note that the fragmentCount needs to be passed in so that we can create a result
|
||||||
|
* synchronously even when dealing with webworkers!
|
||||||
|
*
|
||||||
* @param {RenderProtoViewRef} hostProtoViewRef a RenderProtoViewRef of type
|
* @param {RenderProtoViewRef} hostProtoViewRef a RenderProtoViewRef of type
|
||||||
* ProtoViewDto.HOST_VIEW_TYPE
|
* ProtoViewDto.HOST_VIEW_TYPE
|
||||||
* @param {any} hostElementSelector css selector for the host element (will be queried against the
|
* @param {any} hostElementSelector css selector for the host element (will be queried against the
|
||||||
* main document)
|
* main document)
|
||||||
* @return {RenderViewRef} the created view
|
* @return {RenderViewWithFragments} the created view including fragments
|
||||||
*/
|
*/
|
||||||
createRootHostView(hostProtoViewRef: RenderProtoViewRef,
|
createRootHostView(hostProtoViewRef: RenderProtoViewRef, fragmentCount: number,
|
||||||
hostElementSelector: string): RenderViewRef {
|
hostElementSelector: string): RenderViewWithFragments {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a regular view out of the given ProtoView
|
* Creates a regular view out of the given ProtoView.
|
||||||
|
* Note that the fragmentCount needs to be passed in so that we can create a result
|
||||||
|
* synchronously even when dealing with webworkers!
|
||||||
*/
|
*/
|
||||||
createView(protoViewRef: RenderProtoViewRef): RenderViewRef { return null; }
|
createView(protoViewRef: RenderProtoViewRef, fragmentCount: number): RenderViewWithFragments {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Destroys the given view after it has been dehydrated and detached
|
* Destroys the given view after it has been dehydrated and detached
|
||||||
@ -349,27 +393,20 @@ export class Renderer {
|
|||||||
destroyView(viewRef: RenderViewRef) {}
|
destroyView(viewRef: RenderViewRef) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attaches a componentView into the given hostView at the given element
|
* Attaches a fragment after another fragment.
|
||||||
*/
|
*/
|
||||||
attachComponentView(location: RenderElementRef, componentViewRef: RenderViewRef) {}
|
attachFragmentAfterFragment(previousFragmentRef: RenderFragmentRef,
|
||||||
|
fragmentRef: RenderFragmentRef) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detaches a componentView into the given hostView at the given element
|
* Attaches a fragment after an element.
|
||||||
*/
|
*/
|
||||||
detachComponentView(location: RenderElementRef, componentViewRef: RenderViewRef) {}
|
attachFragmentAfterElement(elementRef: RenderElementRef, fragmentRef: RenderFragmentRef) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attaches a view into a ViewContainer (in the given parentView at the given element) at the
|
* Detaches a fragment.
|
||||||
* given index.
|
|
||||||
*/
|
*/
|
||||||
attachViewInContainer(location: RenderElementRef, atIndex: number, viewRef: RenderViewRef) {}
|
detachFragment(fragmentRef: RenderFragmentRef) {}
|
||||||
|
|
||||||
/**
|
|
||||||
* Detaches a view into a ViewContainer (in the given parentView at the given element) at the
|
|
||||||
* given index.
|
|
||||||
*/
|
|
||||||
// TODO(tbosch): this should return a promise as it can be animated!
|
|
||||||
detachViewInContainer(location: RenderElementRef, atIndex: number, viewRef: RenderViewRef) {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hydrates a view after it has been attached. Hydration/dehydration is used for reusing views
|
* Hydrates a view after it has been attached. Hydration/dehydration is used for reusing views
|
||||||
@ -422,18 +459,18 @@ export class Renderer {
|
|||||||
/**
|
/**
|
||||||
* Sets the dispatcher for all events of the given view
|
* Sets the dispatcher for all events of the given view
|
||||||
*/
|
*/
|
||||||
setEventDispatcher(viewRef: RenderViewRef, dispatcher: EventDispatcher) {}
|
setEventDispatcher(viewRef: RenderViewRef, dispatcher: RenderEventDispatcher) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A dispatcher for all events happening in a view.
|
* A dispatcher for all events happening in a view.
|
||||||
*/
|
*/
|
||||||
export interface EventDispatcher {
|
export interface RenderEventDispatcher {
|
||||||
/**
|
/**
|
||||||
* Called when an event was triggered for a on-* attribute on an element.
|
* Called when an event was triggered for a on-* attribute on an element.
|
||||||
* @param {Map<string, any>} locals Locals to be used to evaluate the
|
* @param {Map<string, any>} locals Locals to be used to evaluate the
|
||||||
* event expressions
|
* event expressions
|
||||||
*/
|
*/
|
||||||
dispatchEvent(elementIndex: number, eventName: string, locals: Map<string, any>);
|
dispatchRenderEvent(elementIndex: number, eventName: string, locals: Map<string, any>);
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,9 @@ import {ProtoViewDto, ViewType} from '../../api';
|
|||||||
*/
|
*/
|
||||||
export class CompilePipeline {
|
export class CompilePipeline {
|
||||||
_control: CompileControl;
|
_control: CompileControl;
|
||||||
constructor(steps: List<CompileStep>) { this._control = new CompileControl(steps); }
|
constructor(steps: List<CompileStep>, private _useNativeShadowDom: boolean = false) {
|
||||||
|
this._control = new CompileControl(steps);
|
||||||
|
}
|
||||||
|
|
||||||
process(rootElement, protoViewType: ViewType = null,
|
process(rootElement, protoViewType: ViewType = null,
|
||||||
compilationCtxtDescription: string = ''): List<CompileElement> {
|
compilationCtxtDescription: string = ''): List<CompileElement> {
|
||||||
@ -22,7 +24,8 @@ export class CompilePipeline {
|
|||||||
}
|
}
|
||||||
var results = [];
|
var results = [];
|
||||||
var rootCompileElement = new CompileElement(rootElement, compilationCtxtDescription);
|
var rootCompileElement = new CompileElement(rootElement, compilationCtxtDescription);
|
||||||
rootCompileElement.inheritedProtoView = new ProtoViewBuilder(rootElement, protoViewType);
|
rootCompileElement.inheritedProtoView =
|
||||||
|
new ProtoViewBuilder(rootElement, protoViewType, this._useNativeShadowDom);
|
||||||
rootCompileElement.isViewRoot = true;
|
rootCompileElement.isViewRoot = true;
|
||||||
this._process(results, null, rootCompileElement, compilationCtxtDescription);
|
this._process(results, null, rootCompileElement, compilationCtxtDescription);
|
||||||
return results;
|
return results;
|
||||||
|
@ -10,13 +10,15 @@ import {
|
|||||||
ViewType,
|
ViewType,
|
||||||
DirectiveMetadata,
|
DirectiveMetadata,
|
||||||
RenderCompiler,
|
RenderCompiler,
|
||||||
RenderProtoViewRef
|
RenderProtoViewRef,
|
||||||
|
RenderProtoViewMergeMapping
|
||||||
} from '../../api';
|
} from '../../api';
|
||||||
import {CompilePipeline} from './compile_pipeline';
|
import {CompilePipeline} from './compile_pipeline';
|
||||||
import {ViewLoader} from 'angular2/src/render/dom/compiler/view_loader';
|
import {ViewLoader} from 'angular2/src/render/dom/compiler/view_loader';
|
||||||
import {CompileStepFactory, DefaultStepFactory} from './compile_step_factory';
|
import {CompileStepFactory, DefaultStepFactory} from './compile_step_factory';
|
||||||
import {Parser} from 'angular2/change_detection';
|
import {Parser} from 'angular2/change_detection';
|
||||||
import {ShadowDomStrategy} from '../shadow_dom/shadow_dom_strategy';
|
import {ShadowDomStrategy} from '../shadow_dom/shadow_dom_strategy';
|
||||||
|
import * as pvm from '../view/proto_view_merger';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The compiler loads and translates the html templates of components into
|
* The compiler loads and translates the html templates of components into
|
||||||
@ -24,7 +26,10 @@ import {ShadowDomStrategy} from '../shadow_dom/shadow_dom_strategy';
|
|||||||
* the CompilePipeline and the CompileSteps.
|
* the CompilePipeline and the CompileSteps.
|
||||||
*/
|
*/
|
||||||
export class DomCompiler extends RenderCompiler {
|
export class DomCompiler extends RenderCompiler {
|
||||||
constructor(public _stepFactory: CompileStepFactory, public _viewLoader: ViewLoader) { super(); }
|
constructor(public _stepFactory: CompileStepFactory, public _viewLoader: ViewLoader,
|
||||||
|
public _useNativeShadowDom: boolean) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
compile(view: ViewDefinition): Promise<ProtoViewDto> {
|
compile(view: ViewDefinition): Promise<ProtoViewDto> {
|
||||||
var tplPromise = this._viewLoader.load(view);
|
var tplPromise = this._viewLoader.load(view);
|
||||||
@ -42,13 +47,21 @@ export class DomCompiler extends RenderCompiler {
|
|||||||
styleAbsUrls: null,
|
styleAbsUrls: null,
|
||||||
directives: [directiveMetadata]
|
directives: [directiveMetadata]
|
||||||
});
|
});
|
||||||
var element = DOM.createElement(directiveMetadata.selector);
|
var template = DOM.createTemplate('');
|
||||||
return this._compileTemplate(hostViewDef, element, ViewType.HOST);
|
DOM.appendChild(DOM.content(template), DOM.createElement(directiveMetadata.selector));
|
||||||
|
return this._compileTemplate(hostViewDef, template, ViewType.HOST);
|
||||||
|
}
|
||||||
|
|
||||||
|
mergeProtoViewsRecursively(
|
||||||
|
protoViewRefs:
|
||||||
|
List<RenderProtoViewRef | List<any>>): Promise<List<RenderProtoViewMergeMapping>> {
|
||||||
|
return PromiseWrapper.resolve(pvm.mergeProtoViewsRecursively(protoViewRefs));
|
||||||
}
|
}
|
||||||
|
|
||||||
_compileTemplate(viewDef: ViewDefinition, tplElement,
|
_compileTemplate(viewDef: ViewDefinition, tplElement,
|
||||||
protoViewType: ViewType): Promise<ProtoViewDto> {
|
protoViewType: ViewType): Promise<ProtoViewDto> {
|
||||||
var pipeline = new CompilePipeline(this._stepFactory.createSteps(viewDef));
|
var pipeline =
|
||||||
|
new CompilePipeline(this._stepFactory.createSteps(viewDef), this._useNativeShadowDom);
|
||||||
var compileElements = pipeline.process(tplElement, protoViewType, viewDef.componentId);
|
var compileElements = pipeline.process(tplElement, protoViewType, viewDef.componentId);
|
||||||
|
|
||||||
return PromiseWrapper.resolve(compileElements[0].inheritedProtoView.build());
|
return PromiseWrapper.resolve(compileElements[0].inheritedProtoView.build());
|
||||||
@ -58,6 +71,7 @@ export class DomCompiler extends RenderCompiler {
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class DefaultDomCompiler extends DomCompiler {
|
export class DefaultDomCompiler extends DomCompiler {
|
||||||
constructor(parser: Parser, shadowDomStrategy: ShadowDomStrategy, viewLoader: ViewLoader) {
|
constructor(parser: Parser, shadowDomStrategy: ShadowDomStrategy, viewLoader: ViewLoader) {
|
||||||
super(new DefaultStepFactory(parser, shadowDomStrategy), viewLoader);
|
super(new DefaultStepFactory(parser, shadowDomStrategy), viewLoader,
|
||||||
|
shadowDomStrategy.hasNativeContentElement());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,11 +27,11 @@ var _SELECTOR_REGEXP = RegExpWrapper.create(
|
|||||||
*/
|
*/
|
||||||
export class CssSelector {
|
export class CssSelector {
|
||||||
element: string = null;
|
element: string = null;
|
||||||
classNames: List<string> = [];
|
classNames: string[] = [];
|
||||||
attrs: List<string> = [];
|
attrs: string[] = [];
|
||||||
notSelectors: List<CssSelector> = [];
|
notSelectors: CssSelector[] = [];
|
||||||
|
|
||||||
static parse(selector: string): List<CssSelector> {
|
static parse(selector: string): CssSelector[] {
|
||||||
var results: CssSelector[] = [];
|
var results: CssSelector[] = [];
|
||||||
var _addResult = (res: CssSelector[], cssSel) => {
|
var _addResult = (res: CssSelector[], cssSel) => {
|
||||||
if (cssSel.notSelectors.length > 0 && isBlank(cssSel.element) &&
|
if (cssSel.notSelectors.length > 0 && isBlank(cssSel.element) &&
|
||||||
@ -135,21 +135,21 @@ export class CssSelector {
|
|||||||
* are contained in a given CssSelector.
|
* are contained in a given CssSelector.
|
||||||
*/
|
*/
|
||||||
export class SelectorMatcher {
|
export class SelectorMatcher {
|
||||||
static createNotMatcher(notSelectors: List<CssSelector>): SelectorMatcher {
|
static createNotMatcher(notSelectors: CssSelector[]): SelectorMatcher {
|
||||||
var notMatcher = new SelectorMatcher();
|
var notMatcher = new SelectorMatcher();
|
||||||
notMatcher.addSelectables(notSelectors, null);
|
notMatcher.addSelectables(notSelectors, null);
|
||||||
return notMatcher;
|
return notMatcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _elementMap: Map<string, List<SelectorContext>> = new Map();
|
private _elementMap: Map<string, SelectorContext[]> = new Map();
|
||||||
private _elementPartialMap: Map<string, SelectorMatcher> = new Map();
|
private _elementPartialMap: Map<string, SelectorMatcher> = new Map();
|
||||||
private _classMap: Map<string, List<SelectorContext>> = new Map();
|
private _classMap: Map<string, SelectorContext[]> = new Map();
|
||||||
private _classPartialMap: Map<string, SelectorMatcher> = new Map();
|
private _classPartialMap: Map<string, SelectorMatcher> = new Map();
|
||||||
private _attrValueMap: Map<string, Map<string, List<SelectorContext>>> = new Map();
|
private _attrValueMap: Map<string, Map<string, SelectorContext[]>> = new Map();
|
||||||
private _attrValuePartialMap: Map<string, Map<string, SelectorMatcher>> = new Map();
|
private _attrValuePartialMap: Map<string, Map<string, SelectorMatcher>> = new Map();
|
||||||
private _listContexts: List<SelectorListContext> = [];
|
private _listContexts: SelectorListContext[] = [];
|
||||||
|
|
||||||
addSelectables(cssSelectors: List<CssSelector>, callbackCtxt?: any) {
|
addSelectables(cssSelectors: CssSelector[], callbackCtxt?: any) {
|
||||||
var listContext = null;
|
var listContext = null;
|
||||||
if (cssSelectors.length > 1) {
|
if (cssSelectors.length > 1) {
|
||||||
listContext = new SelectorListContext(cssSelectors);
|
listContext = new SelectorListContext(cssSelectors);
|
||||||
@ -220,7 +220,7 @@ export class SelectorMatcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _addTerminal(map: Map<string, List<SelectorContext>>, name: string,
|
private _addTerminal(map: Map<string, SelectorContext[]>, name: string,
|
||||||
selectable: SelectorContext) {
|
selectable: SelectorContext) {
|
||||||
var terminalList = map.get(name);
|
var terminalList = map.get(name);
|
||||||
if (isBlank(terminalList)) {
|
if (isBlank(terminalList)) {
|
||||||
@ -298,7 +298,7 @@ export class SelectorMatcher {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
_matchTerminal(map: Map<string, List<SelectorContext>>, name, cssSelector: CssSelector,
|
_matchTerminal(map: Map<string, SelectorContext[]>, name, cssSelector: CssSelector,
|
||||||
matchedCallback: (CssSelector, any) => void): boolean {
|
matchedCallback: (CssSelector, any) => void): boolean {
|
||||||
if (isBlank(map) || isBlank(name)) {
|
if (isBlank(map) || isBlank(name)) {
|
||||||
return false;
|
return false;
|
||||||
@ -341,12 +341,12 @@ export class SelectorMatcher {
|
|||||||
class SelectorListContext {
|
class SelectorListContext {
|
||||||
alreadyMatched: boolean = false;
|
alreadyMatched: boolean = false;
|
||||||
|
|
||||||
constructor(public selectors: List<CssSelector>) {}
|
constructor(public selectors: CssSelector[]) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store context to pass back selector and context when a selector is matched
|
// Store context to pass back selector and context when a selector is matched
|
||||||
class SelectorContext {
|
class SelectorContext {
|
||||||
notSelectors: List<CssSelector>;
|
notSelectors: CssSelector[];
|
||||||
|
|
||||||
constructor(public selector: CssSelector, public cbContext: any,
|
constructor(public selector: CssSelector, public cbContext: any,
|
||||||
public listContext: SelectorListContext) {
|
public listContext: SelectorListContext) {
|
||||||
|
@ -26,7 +26,11 @@ export class TextInterpolationParser implements CompileStep {
|
|||||||
var expr = this._parser.parseInterpolation(text, current.elementDescription);
|
var expr = this._parser.parseInterpolation(text, current.elementDescription);
|
||||||
if (isPresent(expr)) {
|
if (isPresent(expr)) {
|
||||||
DOM.setText(node, ' ');
|
DOM.setText(node, ' ');
|
||||||
current.bindElement().bindText(node, expr);
|
if (current.element === current.inheritedProtoView.rootElement) {
|
||||||
|
current.inheritedProtoView.bindRootText(node, expr);
|
||||||
|
} else {
|
||||||
|
current.bindElement().bindText(node, expr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,23 +65,31 @@ export class ViewSplitter implements CompileStep {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (hasTemplateBinding) {
|
if (hasTemplateBinding) {
|
||||||
var newParent = new CompileElement(DOM.createTemplate(''));
|
var anchor = new CompileElement(DOM.createTemplate(''));
|
||||||
newParent.inheritedProtoView = current.inheritedProtoView;
|
anchor.inheritedProtoView = current.inheritedProtoView;
|
||||||
newParent.inheritedElementBinder = current.inheritedElementBinder;
|
anchor.inheritedElementBinder = current.inheritedElementBinder;
|
||||||
newParent.distanceToInheritedBinder = current.distanceToInheritedBinder;
|
anchor.distanceToInheritedBinder = current.distanceToInheritedBinder;
|
||||||
// newParent doesn't appear in the original template, so we associate
|
// newParent doesn't appear in the original template, so we associate
|
||||||
// the current element description to get a more meaningful message in case of error
|
// the current element description to get a more meaningful message in case of error
|
||||||
newParent.elementDescription = current.elementDescription;
|
anchor.elementDescription = current.elementDescription;
|
||||||
|
|
||||||
current.inheritedProtoView = newParent.bindElement().bindNestedProtoView(current.element);
|
var viewRoot = new CompileElement(DOM.createTemplate(''));
|
||||||
|
viewRoot.inheritedProtoView = anchor.bindElement().bindNestedProtoView(viewRoot.element);
|
||||||
|
// viewRoot doesn't appear in the original template, so we associate
|
||||||
|
// the current element description to get a more meaningful message in case of error
|
||||||
|
viewRoot.elementDescription = current.elementDescription;
|
||||||
|
viewRoot.isViewRoot = true;
|
||||||
|
|
||||||
|
current.inheritedProtoView = viewRoot.inheritedProtoView;
|
||||||
current.inheritedElementBinder = null;
|
current.inheritedElementBinder = null;
|
||||||
current.distanceToInheritedBinder = 0;
|
current.distanceToInheritedBinder = 0;
|
||||||
current.isViewRoot = true;
|
|
||||||
this._parseTemplateBindings(templateBindings, newParent);
|
|
||||||
|
|
||||||
this._addParentElement(current.element, newParent.element);
|
this._parseTemplateBindings(templateBindings, anchor);
|
||||||
control.addParent(newParent);
|
DOM.insertBefore(current.element, anchor.element);
|
||||||
DOM.remove(current.element);
|
control.addParent(anchor);
|
||||||
|
|
||||||
|
DOM.appendChild(DOM.content(viewRoot.element), current.element);
|
||||||
|
control.addParent(viewRoot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -94,11 +102,6 @@ export class ViewSplitter implements CompileStep {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_addParentElement(currentElement, newParentElement) {
|
|
||||||
DOM.insertBefore(currentElement, newParentElement);
|
|
||||||
DOM.appendChild(newParentElement, currentElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
_parseTemplateBindings(templateBindings: string, compileElement: CompileElement) {
|
_parseTemplateBindings(templateBindings: string, compileElement: CompileElement) {
|
||||||
var bindings =
|
var bindings =
|
||||||
this._parser.parseTemplateBindings(templateBindings, compileElement.elementDescription);
|
this._parser.parseTemplateBindings(templateBindings, compileElement.elementDescription);
|
||||||
|
@ -10,17 +10,26 @@ import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src
|
|||||||
|
|
||||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||||
|
|
||||||
import {Content} from './shadow_dom/content_tag';
|
|
||||||
import {ShadowDomStrategy} from './shadow_dom/shadow_dom_strategy';
|
|
||||||
import {EventManager} from './events/event_manager';
|
import {EventManager} from './events/event_manager';
|
||||||
|
|
||||||
import {DomProtoView, DomProtoViewRef, resolveInternalDomProtoView} from './view/proto_view';
|
import {DomProtoView, DomProtoViewRef, resolveInternalDomProtoView} from './view/proto_view';
|
||||||
import {DomView, DomViewRef, resolveInternalDomView} from './view/view';
|
import {DomView, DomViewRef, resolveInternalDomView} from './view/view';
|
||||||
import {DomElement} from './view/element';
|
import {DomFragmentRef, resolveInternalDomFragment} from './view/fragment';
|
||||||
import {DomViewContainer} from './view/view_container';
|
import {
|
||||||
import {NG_BINDING_CLASS_SELECTOR, NG_BINDING_CLASS, camelCaseToDashCase} from './util';
|
NG_BINDING_CLASS_SELECTOR,
|
||||||
|
NG_BINDING_CLASS,
|
||||||
|
cloneAndQueryProtoView,
|
||||||
|
camelCaseToDashCase
|
||||||
|
} from './util';
|
||||||
|
|
||||||
import {Renderer, RenderProtoViewRef, RenderViewRef, RenderElementRef} from '../api';
|
import {
|
||||||
|
Renderer,
|
||||||
|
RenderProtoViewRef,
|
||||||
|
RenderViewRef,
|
||||||
|
RenderElementRef,
|
||||||
|
RenderFragmentRef,
|
||||||
|
RenderViewWithFragments
|
||||||
|
} from '../api';
|
||||||
|
|
||||||
export const DOCUMENT_TOKEN = CONST_EXPR(new OpaqueToken('DocumentToken'));
|
export const DOCUMENT_TOKEN = CONST_EXPR(new OpaqueToken('DocumentToken'));
|
||||||
export const DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES =
|
export const DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES =
|
||||||
@ -32,8 +41,7 @@ export class DomRenderer extends Renderer {
|
|||||||
_document;
|
_document;
|
||||||
_reflectPropertiesAsAttributes: boolean;
|
_reflectPropertiesAsAttributes: boolean;
|
||||||
|
|
||||||
constructor(public _eventManager: EventManager, public _shadowDomStrategy: ShadowDomStrategy,
|
constructor(public _eventManager: EventManager, @Inject(DOCUMENT_TOKEN) document,
|
||||||
@Inject(DOCUMENT_TOKEN) document,
|
|
||||||
@Inject(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES) reflectPropertiesAsAttributes:
|
@Inject(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES) reflectPropertiesAsAttributes:
|
||||||
boolean) {
|
boolean) {
|
||||||
super();
|
super();
|
||||||
@ -41,109 +49,57 @@ export class DomRenderer extends Renderer {
|
|||||||
this._document = document;
|
this._document = document;
|
||||||
}
|
}
|
||||||
|
|
||||||
createRootHostView(hostProtoViewRef: RenderProtoViewRef,
|
createRootHostView(hostProtoViewRef: RenderProtoViewRef, fragmentCount: number,
|
||||||
hostElementSelector: string): RenderViewRef {
|
hostElementSelector: string): RenderViewWithFragments {
|
||||||
var hostProtoView = resolveInternalDomProtoView(hostProtoViewRef);
|
var hostProtoView = resolveInternalDomProtoView(hostProtoViewRef);
|
||||||
var element = DOM.querySelector(this._document, hostElementSelector);
|
var element = DOM.querySelector(this._document, hostElementSelector);
|
||||||
if (isBlank(element)) {
|
if (isBlank(element)) {
|
||||||
throw new BaseException(`The selector "${hostElementSelector}" did not match any elements`);
|
throw new BaseException(`The selector "${hostElementSelector}" did not match any elements`);
|
||||||
}
|
}
|
||||||
return new DomViewRef(this._createView(hostProtoView, element));
|
return this._createView(hostProtoView, element);
|
||||||
}
|
}
|
||||||
|
|
||||||
createView(protoViewRef: RenderProtoViewRef): RenderViewRef {
|
createView(protoViewRef: RenderProtoViewRef, fragmentCount: number): RenderViewWithFragments {
|
||||||
var protoView = resolveInternalDomProtoView(protoViewRef);
|
var protoView = resolveInternalDomProtoView(protoViewRef);
|
||||||
return new DomViewRef(this._createView(protoView, null));
|
return this._createView(protoView, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
destroyView(view: RenderViewRef) {
|
destroyView(viewRef: RenderViewRef) {
|
||||||
// noop for now
|
// noop for now
|
||||||
}
|
}
|
||||||
|
|
||||||
getNativeElementSync(location: RenderElementRef): any {
|
getNativeElementSync(location: RenderElementRef): any {
|
||||||
|
if (isBlank(location.renderBoundElementIndex)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return resolveInternalDomView(location.renderView)
|
return resolveInternalDomView(location.renderView)
|
||||||
.boundElements[location.boundElementIndex]
|
.boundElements[location.renderBoundElementIndex];
|
||||||
.element;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
attachComponentView(location: RenderElementRef, componentViewRef: RenderViewRef) {
|
getRootNodes(fragment: RenderFragmentRef): List<Node> {
|
||||||
var hostView = resolveInternalDomView(location.renderView);
|
return resolveInternalDomFragment(fragment);
|
||||||
var componentView = resolveInternalDomView(componentViewRef);
|
}
|
||||||
var element = hostView.boundElements[location.boundElementIndex].element;
|
|
||||||
var lightDom = hostView.boundElements[location.boundElementIndex].lightDom;
|
attachFragmentAfterFragment(previousFragmentRef: RenderFragmentRef,
|
||||||
if (isPresent(lightDom)) {
|
fragmentRef: RenderFragmentRef) {
|
||||||
lightDom.attachShadowDomView(componentView);
|
var previousFragmentNodes = resolveInternalDomFragment(previousFragmentRef);
|
||||||
|
var sibling = previousFragmentNodes[previousFragmentNodes.length - 1];
|
||||||
|
moveNodesAfterSibling(sibling, resolveInternalDomFragment(fragmentRef));
|
||||||
|
}
|
||||||
|
|
||||||
|
attachFragmentAfterElement(elementRef: RenderElementRef, fragmentRef: RenderFragmentRef) {
|
||||||
|
if (isBlank(elementRef.renderBoundElementIndex)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
var shadowRoot = this._shadowDomStrategy.prepareShadowRoot(element);
|
var parentView = resolveInternalDomView(elementRef.renderView);
|
||||||
this._moveViewNodesIntoParent(shadowRoot, componentView);
|
var element = parentView.boundElements[elementRef.renderBoundElementIndex];
|
||||||
componentView.hostLightDom = lightDom;
|
moveNodesAfterSibling(element, resolveInternalDomFragment(fragmentRef));
|
||||||
componentView.shadowRoot = shadowRoot;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setComponentViewRootNodes(componentViewRef: RenderViewRef, rootNodes: List</*node*/ any>) {
|
detachFragment(fragmentRef: RenderFragmentRef) {
|
||||||
var componentView = resolveInternalDomView(componentViewRef);
|
var fragmentNodes = resolveInternalDomFragment(fragmentRef);
|
||||||
this._removeViewNodes(componentView);
|
for (var i = 0; i < fragmentNodes.length; i++) {
|
||||||
componentView.rootNodes = rootNodes;
|
DOM.remove(fragmentNodes[i]);
|
||||||
this._moveViewNodesIntoParent(componentView.shadowRoot, componentView);
|
|
||||||
}
|
|
||||||
|
|
||||||
getRootNodes(viewRef: RenderViewRef): List</*node*/ any> {
|
|
||||||
return resolveInternalDomView(viewRef).rootNodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
detachComponentView(location: RenderElementRef, componentViewRef: RenderViewRef) {
|
|
||||||
var hostView = resolveInternalDomView(location.renderView);
|
|
||||||
var componentView = resolveInternalDomView(componentViewRef);
|
|
||||||
this._removeViewNodes(componentView);
|
|
||||||
var lightDom = hostView.boundElements[location.boundElementIndex].lightDom;
|
|
||||||
if (isPresent(lightDom)) {
|
|
||||||
lightDom.detachShadowDomView();
|
|
||||||
}
|
|
||||||
componentView.hostLightDom = null;
|
|
||||||
componentView.shadowRoot = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
attachViewInContainer(location: RenderElementRef, atIndex: number, viewRef: RenderViewRef) {
|
|
||||||
var parentView = resolveInternalDomView(location.renderView);
|
|
||||||
var view = resolveInternalDomView(viewRef);
|
|
||||||
var viewContainer = this._getOrCreateViewContainer(parentView, location.boundElementIndex);
|
|
||||||
ListWrapper.insert(viewContainer.views, atIndex, view);
|
|
||||||
view.hostLightDom = parentView.hostLightDom;
|
|
||||||
|
|
||||||
var directParentLightDom = this._directParentLightDom(parentView, location.boundElementIndex);
|
|
||||||
if (isBlank(directParentLightDom)) {
|
|
||||||
var siblingToInsertAfter;
|
|
||||||
if (atIndex == 0) {
|
|
||||||
siblingToInsertAfter = parentView.boundElements[location.boundElementIndex].element;
|
|
||||||
} else {
|
|
||||||
siblingToInsertAfter = ListWrapper.last(viewContainer.views[atIndex - 1].rootNodes);
|
|
||||||
}
|
|
||||||
this._moveViewNodesAfterSibling(siblingToInsertAfter, view);
|
|
||||||
} else {
|
|
||||||
directParentLightDom.redistribute();
|
|
||||||
}
|
|
||||||
// new content tags might have appeared, we need to redistribute.
|
|
||||||
if (isPresent(parentView.hostLightDom)) {
|
|
||||||
parentView.hostLightDom.redistribute();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
detachViewInContainer(location: RenderElementRef, atIndex: number, viewRef: RenderViewRef) {
|
|
||||||
var parentView = resolveInternalDomView(location.renderView);
|
|
||||||
var view = resolveInternalDomView(viewRef);
|
|
||||||
var viewContainer = parentView.boundElements[location.boundElementIndex].viewContainer;
|
|
||||||
var detachedView = viewContainer.views[atIndex];
|
|
||||||
ListWrapper.removeAt(viewContainer.views, atIndex);
|
|
||||||
var directParentLightDom = this._directParentLightDom(parentView, location.boundElementIndex);
|
|
||||||
if (isBlank(directParentLightDom)) {
|
|
||||||
this._removeViewNodes(detachedView);
|
|
||||||
} else {
|
|
||||||
directParentLightDom.redistribute();
|
|
||||||
}
|
|
||||||
view.hostLightDom = null;
|
|
||||||
// content tags might have disappeared we need to do redistribution.
|
|
||||||
if (isPresent(parentView.hostLightDom)) {
|
|
||||||
parentView.hostLightDom.redistribute();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,13 +108,6 @@ export class DomRenderer extends Renderer {
|
|||||||
if (view.hydrated) throw new BaseException('The view is already hydrated.');
|
if (view.hydrated) throw new BaseException('The view is already hydrated.');
|
||||||
view.hydrated = true;
|
view.hydrated = true;
|
||||||
|
|
||||||
for (var i = 0; i < view.boundElements.length; ++i) {
|
|
||||||
var lightDom = view.boundElements[i].lightDom;
|
|
||||||
if (isPresent(lightDom)) {
|
|
||||||
lightDom.redistribute();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add global events
|
// add global events
|
||||||
view.eventHandlerRemovers = [];
|
view.eventHandlerRemovers = [];
|
||||||
var binders = view.proto.elementBinders;
|
var binders = view.proto.elementBinders;
|
||||||
@ -173,9 +122,6 @@ export class DomRenderer extends Renderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isPresent(view.hostLightDom)) {
|
|
||||||
view.hostLightDom.redistribute();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dehydrateView(viewRef: RenderViewRef) {
|
dehydrateView(viewRef: RenderViewRef) {
|
||||||
@ -191,8 +137,11 @@ export class DomRenderer extends Renderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setElementProperty(location: RenderElementRef, propertyName: string, propertyValue: any): void {
|
setElementProperty(location: RenderElementRef, propertyName: string, propertyValue: any): void {
|
||||||
|
if (isBlank(location.renderBoundElementIndex)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
var view = resolveInternalDomView(location.renderView);
|
var view = resolveInternalDomView(location.renderView);
|
||||||
view.setElementProperty(location.boundElementIndex, propertyName, propertyValue);
|
view.setElementProperty(location.renderBoundElementIndex, propertyName, propertyValue);
|
||||||
// Reflect the property value as an attribute value with ng-reflect- prefix.
|
// Reflect the property value as an attribute value with ng-reflect- prefix.
|
||||||
if (this._reflectPropertiesAsAttributes) {
|
if (this._reflectPropertiesAsAttributes) {
|
||||||
this.setElementAttribute(location, `${REFLECT_PREFIX}${camelCaseToDashCase(propertyName)}`,
|
this.setElementAttribute(location, `${REFLECT_PREFIX}${camelCaseToDashCase(propertyName)}`,
|
||||||
@ -202,26 +151,41 @@ export class DomRenderer extends Renderer {
|
|||||||
|
|
||||||
setElementAttribute(location: RenderElementRef, attributeName: string, attributeValue: string):
|
setElementAttribute(location: RenderElementRef, attributeName: string, attributeValue: string):
|
||||||
void {
|
void {
|
||||||
|
if (isBlank(location.renderBoundElementIndex)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
var view = resolveInternalDomView(location.renderView);
|
var view = resolveInternalDomView(location.renderView);
|
||||||
view.setElementAttribute(location.boundElementIndex, attributeName, attributeValue);
|
view.setElementAttribute(location.renderBoundElementIndex, attributeName, attributeValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
setElementClass(location: RenderElementRef, className: string, isAdd: boolean): void {
|
setElementClass(location: RenderElementRef, className: string, isAdd: boolean): void {
|
||||||
|
if (isBlank(location.renderBoundElementIndex)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
var view = resolveInternalDomView(location.renderView);
|
var view = resolveInternalDomView(location.renderView);
|
||||||
view.setElementClass(location.boundElementIndex, className, isAdd);
|
view.setElementClass(location.renderBoundElementIndex, className, isAdd);
|
||||||
}
|
}
|
||||||
|
|
||||||
setElementStyle(location: RenderElementRef, styleName: string, styleValue: string): void {
|
setElementStyle(location: RenderElementRef, styleName: string, styleValue: string): void {
|
||||||
|
if (isBlank(location.renderBoundElementIndex)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
var view = resolveInternalDomView(location.renderView);
|
var view = resolveInternalDomView(location.renderView);
|
||||||
view.setElementStyle(location.boundElementIndex, styleName, styleValue);
|
view.setElementStyle(location.renderBoundElementIndex, styleName, styleValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
invokeElementMethod(location: RenderElementRef, methodName: string, args: List<any>): void {
|
invokeElementMethod(location: RenderElementRef, methodName: string, args: List<any>): void {
|
||||||
|
if (isBlank(location.renderBoundElementIndex)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
var view = resolveInternalDomView(location.renderView);
|
var view = resolveInternalDomView(location.renderView);
|
||||||
view.invokeElementMethod(location.boundElementIndex, methodName, args);
|
view.invokeElementMethod(location.renderBoundElementIndex, methodName, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
setText(viewRef: RenderViewRef, textNodeIndex: number, text: string): void {
|
setText(viewRef: RenderViewRef, textNodeIndex: number, text: string): void {
|
||||||
|
if (isBlank(textNodeIndex)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
var view = resolveInternalDomView(viewRef);
|
var view = resolveInternalDomView(viewRef);
|
||||||
DOM.setText(view.boundTextNodes[textNodeIndex], text);
|
DOM.setText(view.boundTextNodes[textNodeIndex], text);
|
||||||
}
|
}
|
||||||
@ -231,99 +195,50 @@ export class DomRenderer extends Renderer {
|
|||||||
view.eventDispatcher = dispatcher;
|
view.eventDispatcher = dispatcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
_createView(protoView: DomProtoView, inplaceElement): DomView {
|
_createView(protoView: DomProtoView, inplaceElement: HTMLElement): RenderViewWithFragments {
|
||||||
var rootElementClone;
|
var clonedProtoView = cloneAndQueryProtoView(protoView, true);
|
||||||
var elementsWithBindingsDynamic;
|
|
||||||
var viewRootNodes;
|
var boundElements = clonedProtoView.boundElements;
|
||||||
|
|
||||||
|
// adopt inplaceElement
|
||||||
if (isPresent(inplaceElement)) {
|
if (isPresent(inplaceElement)) {
|
||||||
rootElementClone = inplaceElement;
|
if (protoView.fragmentsRootNodeCount[0] !== 1) {
|
||||||
elementsWithBindingsDynamic = [];
|
throw new BaseException('Root proto views can only contain one element!');
|
||||||
viewRootNodes = [inplaceElement];
|
|
||||||
} else if (protoView.isTemplateElement) {
|
|
||||||
rootElementClone = DOM.importIntoDoc(DOM.content(protoView.element));
|
|
||||||
elementsWithBindingsDynamic =
|
|
||||||
DOM.querySelectorAll(rootElementClone, NG_BINDING_CLASS_SELECTOR);
|
|
||||||
viewRootNodes = ListWrapper.createFixedSize(protoView.rootNodeCount);
|
|
||||||
// Note: An explicit loop is the fastest way to convert a DOM array into a JS array!
|
|
||||||
var childNode = DOM.firstChild(rootElementClone);
|
|
||||||
for (var i = 0; i < protoView.rootNodeCount; i++, childNode = DOM.nextSibling(childNode)) {
|
|
||||||
viewRootNodes[i] = childNode;
|
|
||||||
}
|
}
|
||||||
} else {
|
DOM.clearNodes(inplaceElement);
|
||||||
rootElementClone = DOM.importIntoDoc(protoView.element);
|
var tempRoot = clonedProtoView.fragments[0][0];
|
||||||
elementsWithBindingsDynamic = DOM.getElementsByClassName(rootElementClone, NG_BINDING_CLASS);
|
moveChildNodes(tempRoot, inplaceElement);
|
||||||
viewRootNodes = [rootElementClone];
|
if (boundElements.length > 0 && boundElements[0] === tempRoot) {
|
||||||
|
boundElements[0] = inplaceElement;
|
||||||
|
}
|
||||||
|
clonedProtoView.fragments[0][0] = inplaceElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var view = new DomView(protoView, clonedProtoView.boundTextNodes, boundElements);
|
||||||
|
|
||||||
var binders = protoView.elementBinders;
|
var binders = protoView.elementBinders;
|
||||||
var boundTextNodes = ListWrapper.createFixedSize(protoView.boundTextNodeCount);
|
|
||||||
var boundElements = ListWrapper.createFixedSize(binders.length);
|
|
||||||
var boundTextNodeIdx = 0;
|
|
||||||
|
|
||||||
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
|
|
||||||
var binder = binders[binderIdx];
|
|
||||||
var element;
|
|
||||||
var childNodes;
|
|
||||||
if (binderIdx === 0 && protoView.rootBindingOffset === 1) {
|
|
||||||
// Note: if the root element was a template,
|
|
||||||
// the rootElementClone is a document fragment,
|
|
||||||
// which will be empty as soon as the view gets appended
|
|
||||||
// to a parent. So we store null in the boundElements array.
|
|
||||||
element = protoView.isTemplateElement ? null : rootElementClone;
|
|
||||||
childNodes = DOM.childNodes(rootElementClone);
|
|
||||||
} else {
|
|
||||||
element = elementsWithBindingsDynamic[binderIdx - protoView.rootBindingOffset];
|
|
||||||
childNodes = DOM.childNodes(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
// boundTextNodes
|
|
||||||
var textNodeIndices = binder.textNodeIndices;
|
|
||||||
for (var i = 0; i < textNodeIndices.length; i++) {
|
|
||||||
boundTextNodes[boundTextNodeIdx++] = childNodes[textNodeIndices[i]];
|
|
||||||
}
|
|
||||||
|
|
||||||
// contentTags
|
|
||||||
var contentTag = null;
|
|
||||||
if (isPresent(binder.contentTagSelector)) {
|
|
||||||
contentTag = new Content(element, binder.contentTagSelector);
|
|
||||||
}
|
|
||||||
boundElements[binderIdx] = new DomElement(binder, element, contentTag);
|
|
||||||
}
|
|
||||||
|
|
||||||
var view = new DomView(protoView, viewRootNodes, boundTextNodes, boundElements);
|
|
||||||
|
|
||||||
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
|
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
|
||||||
var binder = binders[binderIdx];
|
var binder = binders[binderIdx];
|
||||||
var element = boundElements[binderIdx];
|
var element = boundElements[binderIdx];
|
||||||
var domEl = element.element;
|
|
||||||
|
|
||||||
// lightDoms
|
// native shadow DOM
|
||||||
var lightDom = null;
|
if (binder.hasNativeShadowRoot) {
|
||||||
// Note: for the root element we can't use the binder.elementIsEmpty
|
var shadowRootWrapper = DOM.firstChild(element);
|
||||||
// information as we don't use the element from the ProtoView
|
moveChildNodes(shadowRootWrapper, DOM.createShadowRoot(element));
|
||||||
// but an element from the document.
|
DOM.remove(shadowRootWrapper);
|
||||||
if (isPresent(binder.componentId) && (!binder.elementIsEmpty || isPresent(inplaceElement))) {
|
|
||||||
lightDom = this._shadowDomStrategy.constructLightDom(view, domEl);
|
|
||||||
}
|
|
||||||
element.lightDom = lightDom;
|
|
||||||
|
|
||||||
// init contentTags
|
|
||||||
var contentTag = element.contentTag;
|
|
||||||
if (isPresent(contentTag)) {
|
|
||||||
var directParentLightDom = this._directParentLightDom(view, binderIdx);
|
|
||||||
contentTag.init(directParentLightDom);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// events
|
// events
|
||||||
if (isPresent(binder.eventLocals) && isPresent(binder.localEvents)) {
|
if (isPresent(binder.eventLocals) && isPresent(binder.localEvents)) {
|
||||||
for (var i = 0; i < binder.localEvents.length; i++) {
|
for (var i = 0; i < binder.localEvents.length; i++) {
|
||||||
this._createEventListener(view, domEl, binderIdx, binder.localEvents[i].name,
|
this._createEventListener(view, element, binderIdx, binder.localEvents[i].name,
|
||||||
binder.eventLocals);
|
binder.eventLocals);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return view;
|
return new RenderViewWithFragments(
|
||||||
|
new DomViewRef(view), clonedProtoView.fragments.map(nodes => new DomFragmentRef(nodes)));
|
||||||
}
|
}
|
||||||
|
|
||||||
_createEventListener(view, element, elementIndex, eventName, eventLocals) {
|
_createEventListener(view, element, elementIndex, eventName, eventLocals) {
|
||||||
@ -331,45 +246,26 @@ export class DomRenderer extends Renderer {
|
|||||||
element, eventName, (event) => { view.dispatchEvent(elementIndex, eventName, event); });
|
element, eventName, (event) => { view.dispatchEvent(elementIndex, eventName, event); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
_moveViewNodesAfterSibling(sibling, view) {
|
|
||||||
for (var i = view.rootNodes.length - 1; i >= 0; --i) {
|
|
||||||
DOM.insertAfter(sibling, view.rootNodes[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_moveViewNodesIntoParent(parent, view) {
|
|
||||||
for (var i = 0; i < view.rootNodes.length; ++i) {
|
|
||||||
DOM.appendChild(parent, view.rootNodes[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_removeViewNodes(view) {
|
|
||||||
var len = view.rootNodes.length;
|
|
||||||
if (len == 0) return;
|
|
||||||
var parent = view.rootNodes[0].parentNode;
|
|
||||||
for (var i = len - 1; i >= 0; --i) {
|
|
||||||
DOM.removeChild(parent, view.rootNodes[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_getOrCreateViewContainer(parentView: DomView, boundElementIndex) {
|
|
||||||
var el = parentView.boundElements[boundElementIndex];
|
|
||||||
var vc = el.viewContainer;
|
|
||||||
if (isBlank(vc)) {
|
|
||||||
vc = new DomViewContainer();
|
|
||||||
el.viewContainer = vc;
|
|
||||||
}
|
|
||||||
return vc;
|
|
||||||
}
|
|
||||||
|
|
||||||
_directParentLightDom(view: DomView, boundElementIndex: number) {
|
|
||||||
var directParentEl = view.getDirectParentElement(boundElementIndex);
|
|
||||||
return isPresent(directParentEl) ? directParentEl.lightDom : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
_createGlobalEventListener(view, elementIndex, eventName, eventTarget, fullName): Function {
|
_createGlobalEventListener(view, elementIndex, eventName, eventTarget, fullName): Function {
|
||||||
return this._eventManager.addGlobalEventListener(
|
return this._eventManager.addGlobalEventListener(
|
||||||
eventTarget, eventName, (event) => { view.dispatchEvent(elementIndex, fullName, event); });
|
eventTarget, eventName, (event) => { view.dispatchEvent(elementIndex, fullName, event); });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function moveNodesAfterSibling(sibling, nodes) {
|
||||||
|
if (isPresent(DOM.parentElement(sibling))) {
|
||||||
|
for (var i = 0; i < nodes.length; i++) {
|
||||||
|
DOM.insertBefore(sibling, nodes[i]);
|
||||||
|
}
|
||||||
|
DOM.insertBefore(nodes[nodes.length - 1], sibling);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveChildNodes(source: Node, target: Node) {
|
||||||
|
var currChild = DOM.firstChild(source);
|
||||||
|
while (isPresent(currChild)) {
|
||||||
|
var nextChild = DOM.nextSibling(currChild);
|
||||||
|
DOM.appendChild(target, currChild);
|
||||||
|
currChild = nextChild;
|
||||||
|
}
|
||||||
|
}
|
@ -1,75 +0,0 @@
|
|||||||
import * as ldModule from './light_dom';
|
|
||||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
|
||||||
import {isPresent} from 'angular2/src/facade/lang';
|
|
||||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
|
||||||
|
|
||||||
class ContentStrategy {
|
|
||||||
nodes: List</*node*/ any>;
|
|
||||||
insert(nodes: List</*node*/ any>) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An implementation of the content tag that is used by transcluding components.
|
|
||||||
* It is used when the content tag is not a direct child of another component,
|
|
||||||
* and thus does not affect redistribution.
|
|
||||||
*/
|
|
||||||
class RenderedContent extends ContentStrategy {
|
|
||||||
beginScript: any;
|
|
||||||
endScript;
|
|
||||||
|
|
||||||
constructor(contentEl) {
|
|
||||||
super();
|
|
||||||
this.beginScript = contentEl;
|
|
||||||
this.endScript = DOM.nextSibling(this.beginScript);
|
|
||||||
this.nodes = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inserts the nodes in between the start and end scripts.
|
|
||||||
// Previous content is removed.
|
|
||||||
insert(nodes: List</*node*/ any>) {
|
|
||||||
this.nodes = nodes;
|
|
||||||
DOM.insertAllBefore(this.endScript, nodes);
|
|
||||||
this._removeNodesUntil(ListWrapper.isEmpty(nodes) ? this.endScript : nodes[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
_removeNodesUntil(node) {
|
|
||||||
var p = DOM.parentElement(this.beginScript);
|
|
||||||
for (var next = DOM.nextSibling(this.beginScript); next !== node;
|
|
||||||
next = DOM.nextSibling(this.beginScript)) {
|
|
||||||
DOM.removeChild(p, next);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An implementation of the content tag that is used by transcluding components.
|
|
||||||
* It is used when the content tag is a direct child of another component,
|
|
||||||
* and thus does not get rendered but only affect the distribution of its parent component.
|
|
||||||
*/
|
|
||||||
class IntermediateContent extends ContentStrategy {
|
|
||||||
constructor(public destinationLightDom: ldModule.LightDom) {
|
|
||||||
super();
|
|
||||||
this.nodes = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
insert(nodes: List</*node*/ any>) {
|
|
||||||
this.nodes = nodes;
|
|
||||||
this.destinationLightDom.redistribute();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export class Content {
|
|
||||||
private _strategy: ContentStrategy = null;
|
|
||||||
|
|
||||||
constructor(public contentStartElement, public select: string) {}
|
|
||||||
|
|
||||||
init(destinationLightDom: ldModule.LightDom) {
|
|
||||||
this._strategy = isPresent(destinationLightDom) ? new IntermediateContent(destinationLightDom) :
|
|
||||||
new RenderedContent(this.contentStartElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
nodes(): List</*node*/ any> { return this._strategy.nodes; }
|
|
||||||
|
|
||||||
insert(nodes: List</*node*/ any>) { this._strategy.insert(nodes); }
|
|
||||||
}
|
|
@ -1,8 +1,5 @@
|
|||||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||||
|
|
||||||
import * as viewModule from '../view/view';
|
|
||||||
|
|
||||||
import {LightDom} from './light_dom';
|
|
||||||
import {ShadowDomStrategy} from './shadow_dom_strategy';
|
import {ShadowDomStrategy} from './shadow_dom_strategy';
|
||||||
import {insertSharedStyleText} from './util';
|
import {insertSharedStyleText} from './util';
|
||||||
|
|
||||||
@ -20,12 +17,6 @@ export class EmulatedUnscopedShadowDomStrategy extends ShadowDomStrategy {
|
|||||||
|
|
||||||
hasNativeContentElement(): boolean { return false; }
|
hasNativeContentElement(): boolean { return false; }
|
||||||
|
|
||||||
prepareShadowRoot(el): Node { return el; }
|
|
||||||
|
|
||||||
constructLightDom(lightDomView: viewModule.DomView, el): LightDom {
|
|
||||||
return new LightDom(lightDomView, el);
|
|
||||||
}
|
|
||||||
|
|
||||||
processStyleElement(hostComponentId: string, templateUrl: string, styleEl): void {
|
processStyleElement(hostComponentId: string, templateUrl: string, styleEl): void {
|
||||||
var cssText = DOM.getText(styleEl);
|
var cssText = DOM.getText(styleEl);
|
||||||
insertSharedStyleText(cssText, this.styleHost, styleEl);
|
insertSharedStyleText(cssText, this.styleHost, styleEl);
|
||||||
|
@ -1,141 +0,0 @@
|
|||||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
|
||||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
|
||||||
import {isBlank, isPresent} from 'angular2/src/facade/lang';
|
|
||||||
|
|
||||||
import * as viewModule from '../view/view';
|
|
||||||
import * as elModule from '../view/element';
|
|
||||||
import {Content} from './content_tag';
|
|
||||||
|
|
||||||
export class DestinationLightDom {}
|
|
||||||
|
|
||||||
class _Root {
|
|
||||||
constructor(public node, public boundElement: elModule.DomElement) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: LightDom should implement DestinationLightDom
|
|
||||||
// once interfaces are supported
|
|
||||||
export class LightDom {
|
|
||||||
// The light DOM of the element is enclosed inside the lightDomView
|
|
||||||
lightDomView: viewModule.DomView;
|
|
||||||
// The shadow DOM
|
|
||||||
shadowDomView: viewModule.DomView = null;
|
|
||||||
// The nodes of the light DOM
|
|
||||||
nodes: List</*node*/ any>;
|
|
||||||
private _roots: List<_Root> = null;
|
|
||||||
|
|
||||||
constructor(lightDomView: viewModule.DomView, element) {
|
|
||||||
this.lightDomView = lightDomView;
|
|
||||||
this.nodes = DOM.childNodesAsList(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
attachShadowDomView(shadowDomView: viewModule.DomView) { this.shadowDomView = shadowDomView; }
|
|
||||||
|
|
||||||
detachShadowDomView() { this.shadowDomView = null; }
|
|
||||||
|
|
||||||
redistribute() { redistributeNodes(this.contentTags(), this.expandedDomNodes()); }
|
|
||||||
|
|
||||||
contentTags(): List<Content> {
|
|
||||||
if (isPresent(this.shadowDomView)) {
|
|
||||||
return this._collectAllContentTags(this.shadowDomView, []);
|
|
||||||
} else {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collects the Content directives from the view and all its child views
|
|
||||||
private _collectAllContentTags(view: viewModule.DomView, acc: List<Content>): List<Content> {
|
|
||||||
// Note: exiting early here is important as we call this function for every view
|
|
||||||
// that is added, so we have O(n^2) runtime.
|
|
||||||
// TODO(tbosch): fix the root problem, see
|
|
||||||
// https://github.com/angular/angular/issues/2298
|
|
||||||
if (view.proto.transitiveContentTagCount === 0) {
|
|
||||||
return acc;
|
|
||||||
}
|
|
||||||
var els = view.boundElements;
|
|
||||||
for (var i = 0; i < els.length; i++) {
|
|
||||||
var el = els[i];
|
|
||||||
if (isPresent(el.contentTag)) {
|
|
||||||
acc.push(el.contentTag);
|
|
||||||
}
|
|
||||||
if (isPresent(el.viewContainer)) {
|
|
||||||
ListWrapper.forEach(el.viewContainer.contentTagContainers(),
|
|
||||||
(view) => { this._collectAllContentTags(view, acc); });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collects the nodes of the light DOM by merging:
|
|
||||||
// - nodes from enclosed ViewContainers,
|
|
||||||
// - nodes from enclosed content tags,
|
|
||||||
// - plain DOM nodes
|
|
||||||
expandedDomNodes(): List</*node*/ any> {
|
|
||||||
var res = [];
|
|
||||||
|
|
||||||
var roots = this._findRoots();
|
|
||||||
for (var i = 0; i < roots.length; ++i) {
|
|
||||||
var root = roots[i];
|
|
||||||
if (isPresent(root.boundElement)) {
|
|
||||||
var vc = root.boundElement.viewContainer;
|
|
||||||
var content = root.boundElement.contentTag;
|
|
||||||
if (isPresent(vc)) {
|
|
||||||
res = ListWrapper.concat(res, vc.nodes());
|
|
||||||
} else if (isPresent(content)) {
|
|
||||||
res = ListWrapper.concat(res, content.nodes());
|
|
||||||
} else {
|
|
||||||
res.push(root.node);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res.push(root.node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a list of Roots for all the nodes of the light DOM.
|
|
||||||
// The Root object contains the DOM node and its corresponding boundElement
|
|
||||||
private _findRoots() {
|
|
||||||
if (isPresent(this._roots)) return this._roots;
|
|
||||||
|
|
||||||
var boundElements = this.lightDomView.boundElements;
|
|
||||||
|
|
||||||
this._roots = ListWrapper.map(this.nodes, (n) => {
|
|
||||||
var boundElement = null;
|
|
||||||
for (var i = 0; i < boundElements.length; i++) {
|
|
||||||
var boundEl = boundElements[i];
|
|
||||||
if (isPresent(boundEl) && boundEl.element === n) {
|
|
||||||
boundElement = boundEl;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new _Root(n, boundElement);
|
|
||||||
});
|
|
||||||
|
|
||||||
return this._roots;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Projects the light DOM into the shadow DOM
|
|
||||||
function redistributeNodes(contents: List<Content>, nodes: List</*node*/ any>) {
|
|
||||||
for (var i = 0; i < contents.length; ++i) {
|
|
||||||
var content = contents[i];
|
|
||||||
var select = content.select;
|
|
||||||
|
|
||||||
// Empty selector is identical to <content/>
|
|
||||||
if (select.length === 0) {
|
|
||||||
content.insert(ListWrapper.clone(nodes));
|
|
||||||
ListWrapper.clear(nodes);
|
|
||||||
} else {
|
|
||||||
var matchSelector = (n) => DOM.elementMatches(n, select);
|
|
||||||
var matchingNodes = ListWrapper.filter(nodes, matchSelector);
|
|
||||||
content.insert(matchingNodes);
|
|
||||||
ListWrapper.removeAll(nodes, matchingNodes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (var i = 0; i < nodes.length; i++) {
|
|
||||||
var node = nodes[i];
|
|
||||||
if (isPresent(node.parentNode)) {
|
|
||||||
DOM.remove(nodes[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,4 @@
|
|||||||
import {Injectable} from 'angular2/di';
|
import {Injectable} from 'angular2/di';
|
||||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
|
||||||
import {ShadowDomStrategy} from './shadow_dom_strategy';
|
import {ShadowDomStrategy} from './shadow_dom_strategy';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -10,5 +9,5 @@ import {ShadowDomStrategy} from './shadow_dom_strategy';
|
|||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class NativeShadowDomStrategy extends ShadowDomStrategy {
|
export class NativeShadowDomStrategy extends ShadowDomStrategy {
|
||||||
prepareShadowRoot(el): Node { return DOM.createShadowRoot(el); }
|
hasNativeContentElement(): boolean { return true; }
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import {isBlank, isPresent, assertionsEnabled, isPromise} from 'angular2/src/facade/lang';
|
|
||||||
|
|
||||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||||
|
|
||||||
import {CompileStep} from '../compiler/compile_step';
|
import {CompileStep} from '../compiler/compile_step';
|
||||||
@ -15,8 +13,6 @@ export class ShadowDomCompileStep implements CompileStep {
|
|||||||
var tagName = DOM.tagName(current.element).toUpperCase();
|
var tagName = DOM.tagName(current.element).toUpperCase();
|
||||||
if (tagName == 'STYLE') {
|
if (tagName == 'STYLE') {
|
||||||
this._processStyleElement(current, control);
|
this._processStyleElement(current, control);
|
||||||
} else if (tagName == 'CONTENT') {
|
|
||||||
this._processContentElement(current);
|
|
||||||
} else {
|
} else {
|
||||||
var componentId = current.isBound() ? current.inheritedElementBinder.componentId : null;
|
var componentId = current.isBound() ? current.inheritedElementBinder.componentId : null;
|
||||||
this._shadowDomStrategy.processElement(this._view.componentId, componentId, current.element);
|
this._shadowDomStrategy.processElement(this._view.componentId, componentId, current.element);
|
||||||
@ -31,25 +27,4 @@ export class ShadowDomCompileStep implements CompileStep {
|
|||||||
// bindings. Skipping further compiler steps allow speeding up the compilation process.
|
// bindings. Skipping further compiler steps allow speeding up the compilation process.
|
||||||
control.ignoreCurrentElement();
|
control.ignoreCurrentElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
_processContentElement(current: CompileElement) {
|
|
||||||
if (this._shadowDomStrategy.hasNativeContentElement()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var attrs = current.attrs();
|
|
||||||
var selector = attrs.get('select');
|
|
||||||
selector = isPresent(selector) ? selector : '';
|
|
||||||
|
|
||||||
var contentStart = DOM.createScriptTag('type', 'ng/contentStart');
|
|
||||||
if (assertionsEnabled()) {
|
|
||||||
DOM.setAttribute(contentStart, 'select', selector);
|
|
||||||
}
|
|
||||||
var contentEnd = DOM.createScriptTag('type', 'ng/contentEnd');
|
|
||||||
DOM.insertBefore(current.element, contentStart);
|
|
||||||
DOM.insertBefore(current.element, contentEnd);
|
|
||||||
DOM.remove(current.element);
|
|
||||||
|
|
||||||
current.element = contentStart;
|
|
||||||
current.bindElement().setContentTagSelector(selector);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,9 @@
|
|||||||
import {isBlank, isPresent} from 'angular2/src/facade/lang';
|
import {isBlank, isPresent} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
import * as viewModule from '../view/view';
|
|
||||||
import {LightDom} from './light_dom';
|
|
||||||
|
|
||||||
export class ShadowDomStrategy {
|
export class ShadowDomStrategy {
|
||||||
// Whether the strategy understands the native <content> tag
|
// Whether the strategy understands the native <content> tag
|
||||||
hasNativeContentElement(): boolean { return true; }
|
hasNativeContentElement(): boolean { return true; }
|
||||||
|
|
||||||
// Prepares and returns the (emulated) shadow root for the given element.
|
|
||||||
prepareShadowRoot(el): any { return null; }
|
|
||||||
|
|
||||||
constructLightDom(lightDomView: viewModule.DomView, el): LightDom { return null; }
|
|
||||||
|
|
||||||
// An optional step that can modify the template style elements.
|
// An optional step that can modify the template style elements.
|
||||||
processStyleElement(hostComponentId: string, templateUrl: string, styleElement): void {}
|
processStyleElement(hostComponentId: string, templateUrl: string, styleElement): void {}
|
||||||
|
|
||||||
|
@ -1,13 +1,21 @@
|
|||||||
import {StringWrapper, isPresent} from 'angular2/src/facade/lang';
|
import {StringWrapper, isPresent, isBlank} from 'angular2/src/facade/lang';
|
||||||
|
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||||
|
import {ListWrapper} from 'angular2/src/facade/collection';
|
||||||
|
import {DomProtoView} from './view/proto_view';
|
||||||
|
import {DomElementBinder} from './view/element_binder';
|
||||||
|
|
||||||
export const NG_BINDING_CLASS_SELECTOR = '.ng-binding';
|
export const NG_BINDING_CLASS_SELECTOR = '.ng-binding';
|
||||||
export const NG_BINDING_CLASS = 'ng-binding';
|
export const NG_BINDING_CLASS = 'ng-binding';
|
||||||
|
|
||||||
export const EVENT_TARGET_SEPARATOR = ':';
|
export const EVENT_TARGET_SEPARATOR = ':';
|
||||||
|
|
||||||
|
export const NG_CONTENT_ELEMENT_NAME = 'ng-content';
|
||||||
|
export const NG_SHADOW_ROOT_ELEMENT_NAME = 'shadow-root';
|
||||||
|
|
||||||
var CAMEL_CASE_REGEXP = /([A-Z])/g;
|
var CAMEL_CASE_REGEXP = /([A-Z])/g;
|
||||||
var DASH_CASE_REGEXP = /-([a-z])/g;
|
var DASH_CASE_REGEXP = /-([a-z])/g;
|
||||||
|
|
||||||
|
|
||||||
export function camelCaseToDashCase(input: string): string {
|
export function camelCaseToDashCase(input: string): string {
|
||||||
return StringWrapper.replaceAllMapped(input, CAMEL_CASE_REGEXP,
|
return StringWrapper.replaceAllMapped(input, CAMEL_CASE_REGEXP,
|
||||||
(m) => { return '-' + m[1].toLowerCase(); });
|
(m) => { return '-' + m[1].toLowerCase(); });
|
||||||
@ -17,3 +25,101 @@ export function dashCaseToCamelCase(input: string): string {
|
|||||||
return StringWrapper.replaceAllMapped(input, DASH_CASE_REGEXP,
|
return StringWrapper.replaceAllMapped(input, DASH_CASE_REGEXP,
|
||||||
(m) => { return m[1].toUpperCase(); });
|
(m) => { return m[1].toUpperCase(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Attention: This is on the hot path, so don't use closures or default values!
|
||||||
|
export function queryBoundElements(templateContent: Node, isSingleElementChild: boolean):
|
||||||
|
Element[] {
|
||||||
|
var result;
|
||||||
|
var dynamicElementList;
|
||||||
|
var elementIdx = 0;
|
||||||
|
if (isSingleElementChild) {
|
||||||
|
var rootElement = DOM.firstChild(templateContent);
|
||||||
|
var rootHasBinding = DOM.hasClass(rootElement, NG_BINDING_CLASS);
|
||||||
|
dynamicElementList = DOM.getElementsByClassName(rootElement, NG_BINDING_CLASS);
|
||||||
|
result = ListWrapper.createFixedSize(dynamicElementList.length + (rootHasBinding ? 1 : 0));
|
||||||
|
if (rootHasBinding) {
|
||||||
|
result[elementIdx++] = rootElement;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dynamicElementList = DOM.querySelectorAll(templateContent, NG_BINDING_CLASS_SELECTOR);
|
||||||
|
result = ListWrapper.createFixedSize(dynamicElementList.length);
|
||||||
|
}
|
||||||
|
for (var i = 0; i < dynamicElementList.length; i++) {
|
||||||
|
result[elementIdx++] = dynamicElementList[i];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ClonedProtoView {
|
||||||
|
constructor(public original: DomProtoView, public fragments: Node[][],
|
||||||
|
public boundElements: Element[], public boundTextNodes: Node[]) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cloneAndQueryProtoView(pv: DomProtoView, importIntoDocument: boolean):
|
||||||
|
ClonedProtoView {
|
||||||
|
var templateContent = importIntoDocument ? DOM.importIntoDoc(DOM.content(pv.rootElement)) :
|
||||||
|
DOM.clone(DOM.content(pv.rootElement));
|
||||||
|
|
||||||
|
var boundElements = queryBoundElements(templateContent, pv.isSingleElementFragment);
|
||||||
|
var boundTextNodes = queryBoundTextNodes(templateContent, pv.rootTextNodeIndices, boundElements,
|
||||||
|
pv.elementBinders, pv.boundTextNodeCount);
|
||||||
|
|
||||||
|
var fragments = queryFragments(templateContent, pv.fragmentsRootNodeCount);
|
||||||
|
return new ClonedProtoView(pv, fragments, boundElements, boundTextNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
function queryFragments(templateContent: Node, fragmentsRootNodeCount: number[]): Node[][] {
|
||||||
|
var fragments = ListWrapper.createGrowableSize(fragmentsRootNodeCount.length);
|
||||||
|
|
||||||
|
// Note: An explicit loop is the fastest way to convert a DOM array into a JS array!
|
||||||
|
var childNode = DOM.firstChild(templateContent);
|
||||||
|
for (var fragmentIndex = 0; fragmentIndex < fragments.length; fragmentIndex++) {
|
||||||
|
var fragment = ListWrapper.createFixedSize(fragmentsRootNodeCount[fragmentIndex]);
|
||||||
|
fragments[fragmentIndex] = fragment;
|
||||||
|
for (var i = 0; i < fragment.length; i++) {
|
||||||
|
fragment[i] = childNode;
|
||||||
|
childNode = DOM.nextSibling(childNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fragments;
|
||||||
|
}
|
||||||
|
|
||||||
|
function queryBoundTextNodes(templateContent: Node, rootTextNodeIndices: number[],
|
||||||
|
boundElements: Element[], elementBinders: DomElementBinder[],
|
||||||
|
boundTextNodeCount: number): Node[] {
|
||||||
|
var boundTextNodes = ListWrapper.createFixedSize(boundTextNodeCount);
|
||||||
|
var textNodeIndex = 0;
|
||||||
|
if (rootTextNodeIndices.length > 0) {
|
||||||
|
var rootChildNodes = DOM.childNodes(templateContent);
|
||||||
|
for (var i = 0; i < rootTextNodeIndices.length; i++) {
|
||||||
|
boundTextNodes[textNodeIndex++] = rootChildNodes[rootTextNodeIndices[i]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var i = 0; i < elementBinders.length; i++) {
|
||||||
|
var binder = elementBinders[i];
|
||||||
|
var element: Node = boundElements[i];
|
||||||
|
if (binder.textNodeIndices.length > 0) {
|
||||||
|
var childNodes = DOM.childNodes(element);
|
||||||
|
for (var j = 0; j < binder.textNodeIndices.length; j++) {
|
||||||
|
boundTextNodes[textNodeIndex++] = childNodes[binder.textNodeIndices[j]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return boundTextNodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function isElementWithTag(node: Node, elementName: string): boolean {
|
||||||
|
return DOM.isElementNode(node) && DOM.tagName(node).toLowerCase() == elementName.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function queryBoundTextNodeIndices(parentNode: Node, boundTextNodes: Map<Node, any>,
|
||||||
|
resultCallback: Function) {
|
||||||
|
var childNodes = DOM.childNodes(parentNode);
|
||||||
|
for (var j = 0; j < childNodes.length; j++) {
|
||||||
|
var node = childNodes[j];
|
||||||
|
if (boundTextNodes.has(node)) {
|
||||||
|
resultCallback(node, j, boundTextNodes.get(node));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +0,0 @@
|
|||||||
import {ElementBinder} from './element_binder';
|
|
||||||
import {DomViewContainer} from './view_container';
|
|
||||||
import {LightDom} from '../shadow_dom/light_dom';
|
|
||||||
import {Content} from '../shadow_dom/content_tag';
|
|
||||||
|
|
||||||
export class DomElement {
|
|
||||||
viewContainer: DomViewContainer;
|
|
||||||
lightDom: LightDom;
|
|
||||||
constructor(public proto: ElementBinder, public element: any /* element */,
|
|
||||||
public contentTag: Content) {}
|
|
||||||
}
|
|
@ -1,42 +1,29 @@
|
|||||||
import {AST} from 'angular2/change_detection';
|
import {AST} from 'angular2/change_detection';
|
||||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||||
import * as protoViewModule from './proto_view';
|
|
||||||
|
|
||||||
export class ElementBinder {
|
export class DomElementBinder {
|
||||||
contentTagSelector: string;
|
|
||||||
textNodeIndices: List<number>;
|
textNodeIndices: List<number>;
|
||||||
nestedProtoView: protoViewModule.DomProtoView;
|
hasNestedProtoView: boolean;
|
||||||
eventLocals: AST;
|
eventLocals: AST;
|
||||||
localEvents: List<Event>;
|
localEvents: List<Event>;
|
||||||
globalEvents: List<Event>;
|
globalEvents: List<Event>;
|
||||||
componentId: string;
|
hasNativeShadowRoot: boolean;
|
||||||
parentIndex: number;
|
|
||||||
distanceToParent: number;
|
|
||||||
elementIsEmpty: boolean;
|
|
||||||
|
|
||||||
constructor({textNodeIndices, contentTagSelector, nestedProtoView, componentId, eventLocals,
|
constructor({textNodeIndices, hasNestedProtoView, eventLocals, localEvents, globalEvents,
|
||||||
localEvents, globalEvents, parentIndex, distanceToParent, elementIsEmpty}: {
|
hasNativeShadowRoot}: {
|
||||||
contentTagSelector?: string,
|
|
||||||
textNodeIndices?: List<number>,
|
textNodeIndices?: List<number>,
|
||||||
nestedProtoView?: protoViewModule.DomProtoView,
|
hasNestedProtoView?: boolean,
|
||||||
eventLocals?: AST,
|
eventLocals?: AST,
|
||||||
localEvents?: List<Event>,
|
localEvents?: List<Event>,
|
||||||
globalEvents?: List<Event>,
|
globalEvents?: List<Event>,
|
||||||
componentId?: string,
|
hasNativeShadowRoot?: boolean
|
||||||
parentIndex?: number,
|
|
||||||
distanceToParent?: number,
|
|
||||||
elementIsEmpty?: boolean
|
|
||||||
} = {}) {
|
} = {}) {
|
||||||
this.textNodeIndices = textNodeIndices;
|
this.textNodeIndices = textNodeIndices;
|
||||||
this.contentTagSelector = contentTagSelector;
|
this.hasNestedProtoView = hasNestedProtoView;
|
||||||
this.nestedProtoView = nestedProtoView;
|
|
||||||
this.componentId = componentId;
|
|
||||||
this.eventLocals = eventLocals;
|
this.eventLocals = eventLocals;
|
||||||
this.localEvents = localEvents;
|
this.localEvents = localEvents;
|
||||||
this.globalEvents = globalEvents;
|
this.globalEvents = globalEvents;
|
||||||
this.parentIndex = parentIndex;
|
this.hasNativeShadowRoot = hasNativeShadowRoot;
|
||||||
this.distanceToParent = distanceToParent;
|
|
||||||
this.elementIsEmpty = elementIsEmpty;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
9
modules/angular2/src/render/dom/view/fragment.ts
Normal file
9
modules/angular2/src/render/dom/view/fragment.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import {RenderFragmentRef} from '../../api';
|
||||||
|
|
||||||
|
export function resolveInternalDomFragment(fragmentRef: RenderFragmentRef): Node[] {
|
||||||
|
return (<DomFragmentRef>fragmentRef)._nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DomFragmentRef extends RenderFragmentRef {
|
||||||
|
constructor(public _nodes: Node[]) { super(); }
|
||||||
|
}
|
@ -1,12 +1,10 @@
|
|||||||
import {isPresent} from 'angular2/src/facade/lang';
|
import {isBlank} from 'angular2/src/facade/lang';
|
||||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
|
||||||
|
|
||||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||||
|
|
||||||
import {ElementBinder} from './element_binder';
|
import {DomElementBinder} from './element_binder';
|
||||||
import {NG_BINDING_CLASS} from '../util';
|
import {RenderProtoViewRef, ViewType} from '../../api';
|
||||||
|
|
||||||
import {RenderProtoViewRef} from '../../api';
|
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||||
|
|
||||||
export function resolveInternalDomProtoView(protoViewRef: RenderProtoViewRef): DomProtoView {
|
export function resolveInternalDomProtoView(protoViewRef: RenderProtoViewRef): DomProtoView {
|
||||||
return (<DomProtoViewRef>protoViewRef)._protoView;
|
return (<DomProtoViewRef>protoViewRef)._protoView;
|
||||||
@ -17,24 +15,40 @@ export class DomProtoViewRef extends RenderProtoViewRef {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class DomProtoView {
|
export class DomProtoView {
|
||||||
element;
|
static create(type: ViewType, rootElement: Element, fragmentsRootNodeCount: number[],
|
||||||
elementBinders: List<ElementBinder>;
|
rootTextNodeIndices: number[], elementBinders: List<DomElementBinder>,
|
||||||
isTemplateElement: boolean;
|
mappedElementIndices: number[], mappedTextIndices: number[],
|
||||||
rootBindingOffset: number;
|
hostElementIndicesByViewIndex: number[]): DomProtoView {
|
||||||
// the number of content tags seen in this or any child proto view.
|
var boundTextNodeCount = rootTextNodeIndices.length;
|
||||||
transitiveContentTagCount: number;
|
for (var i = 0; i < elementBinders.length; i++) {
|
||||||
boundTextNodeCount: number;
|
boundTextNodeCount += elementBinders[i].textNodeIndices.length;
|
||||||
rootNodeCount: number;
|
}
|
||||||
|
if (isBlank(mappedElementIndices)) {
|
||||||
constructor({elementBinders, element, transitiveContentTagCount, boundTextNodeCount}) {
|
mappedElementIndices = ListWrapper.createFixedSize(elementBinders.length);
|
||||||
this.element = element;
|
for (var i = 0; i < mappedElementIndices.length; i++) {
|
||||||
this.elementBinders = elementBinders;
|
mappedElementIndices[i] = i;
|
||||||
this.transitiveContentTagCount = transitiveContentTagCount;
|
}
|
||||||
this.isTemplateElement = DOM.isTemplateElement(this.element);
|
}
|
||||||
this.rootBindingOffset =
|
if (isBlank(mappedTextIndices)) {
|
||||||
(isPresent(this.element) && DOM.hasClass(this.element, NG_BINDING_CLASS)) ? 1 : 0;
|
mappedTextIndices = ListWrapper.createFixedSize(boundTextNodeCount);
|
||||||
this.boundTextNodeCount = boundTextNodeCount;
|
for (var i = 0; i < mappedTextIndices.length; i++) {
|
||||||
this.rootNodeCount =
|
mappedTextIndices[i] = i;
|
||||||
this.isTemplateElement ? DOM.childNodes(DOM.content(this.element)).length : 1;
|
}
|
||||||
|
}
|
||||||
|
if (isBlank(hostElementIndicesByViewIndex)) {
|
||||||
|
hostElementIndicesByViewIndex = [null];
|
||||||
|
}
|
||||||
|
var isSingleElementFragment = fragmentsRootNodeCount.length === 1 &&
|
||||||
|
fragmentsRootNodeCount[0] === 1 &&
|
||||||
|
DOM.isElementNode(DOM.firstChild(DOM.content(rootElement)));
|
||||||
|
return new DomProtoView(type, rootElement, elementBinders, rootTextNodeIndices,
|
||||||
|
boundTextNodeCount, fragmentsRootNodeCount, isSingleElementFragment,
|
||||||
|
mappedElementIndices, mappedTextIndices, hostElementIndicesByViewIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constructor(public type: ViewType, public rootElement: Element,
|
||||||
|
public elementBinders: List<DomElementBinder>, public rootTextNodeIndices: number[],
|
||||||
|
public boundTextNodeCount: number, public fragmentsRootNodeCount: number[],
|
||||||
|
public isSingleElementFragment: boolean, public mappedElementIndices: number[],
|
||||||
|
public mappedTextIndices: number[], public hostElementIndicesByViewIndex: number[]) {}
|
||||||
}
|
}
|
||||||
|
@ -19,17 +19,19 @@ import {
|
|||||||
} from 'angular2/change_detection';
|
} from 'angular2/change_detection';
|
||||||
|
|
||||||
import {DomProtoView, DomProtoViewRef, resolveInternalDomProtoView} from './proto_view';
|
import {DomProtoView, DomProtoViewRef, resolveInternalDomProtoView} from './proto_view';
|
||||||
import {ElementBinder, Event, HostAction} from './element_binder';
|
import {DomElementBinder, Event, HostAction} from './element_binder';
|
||||||
|
|
||||||
import * as api from '../../api';
|
import * as api from '../../api';
|
||||||
|
|
||||||
import {NG_BINDING_CLASS, EVENT_TARGET_SEPARATOR} from '../util';
|
import {NG_BINDING_CLASS, EVENT_TARGET_SEPARATOR, queryBoundTextNodeIndices} from '../util';
|
||||||
|
|
||||||
export class ProtoViewBuilder {
|
export class ProtoViewBuilder {
|
||||||
variableBindings: Map<string, string> = new Map();
|
variableBindings: Map<string, string> = new Map();
|
||||||
elements: List<ElementBinderBuilder> = [];
|
elements: List<ElementBinderBuilder> = [];
|
||||||
|
rootTextBindings: Map<Node, ASTWithSource> = new Map();
|
||||||
|
|
||||||
constructor(public rootElement, public type: api.ViewType) {}
|
constructor(public rootElement, public type: api.ViewType,
|
||||||
|
public useNativeShadowDom: boolean = false) {}
|
||||||
|
|
||||||
bindElement(element, description = null): ElementBinderBuilder {
|
bindElement(element, description = null): ElementBinderBuilder {
|
||||||
var builder = new ElementBinderBuilder(this.elements.length, element, description);
|
var builder = new ElementBinderBuilder(this.elements.length, element, description);
|
||||||
@ -49,12 +51,22 @@ export class ProtoViewBuilder {
|
|||||||
this.variableBindings.set(value, name);
|
this.variableBindings.set(value, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note: We don't store the node index until the compilation is complete,
|
||||||
|
// as the compiler might change the order of elements.
|
||||||
|
bindRootText(textNode, expression) { this.rootTextBindings.set(textNode, expression); }
|
||||||
|
|
||||||
build(): api.ProtoViewDto {
|
build(): api.ProtoViewDto {
|
||||||
var renderElementBinders = [];
|
var domElementBinders = [];
|
||||||
|
|
||||||
var apiElementBinders = [];
|
var apiElementBinders = [];
|
||||||
var transitiveContentTagCount = 0;
|
var textNodeExpressions = [];
|
||||||
var boundTextNodeCount = 0;
|
var rootTextNodeIndices = [];
|
||||||
|
queryBoundTextNodeIndices(DOM.content(this.rootElement), this.rootTextBindings,
|
||||||
|
(node, nodeIndex, expression) => {
|
||||||
|
textNodeExpressions.push(expression);
|
||||||
|
rootTextNodeIndices.push(nodeIndex);
|
||||||
|
});
|
||||||
|
|
||||||
ListWrapper.forEach(this.elements, (ebb: ElementBinderBuilder) => {
|
ListWrapper.forEach(this.elements, (ebb: ElementBinderBuilder) => {
|
||||||
var directiveTemplatePropertyNames = new Set();
|
var directiveTemplatePropertyNames = new Set();
|
||||||
var apiDirectiveBinders = ListWrapper.map(ebb.directives, (dbb: DirectiveBuilder) => {
|
var apiDirectiveBinders = ListWrapper.map(ebb.directives, (dbb: DirectiveBuilder) => {
|
||||||
@ -71,15 +83,12 @@ export class ProtoViewBuilder {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
var nestedProtoView = isPresent(ebb.nestedProtoView) ? ebb.nestedProtoView.build() : null;
|
var nestedProtoView = isPresent(ebb.nestedProtoView) ? ebb.nestedProtoView.build() : null;
|
||||||
var nestedRenderProtoView =
|
|
||||||
isPresent(nestedProtoView) ? resolveInternalDomProtoView(nestedProtoView.render) : null;
|
|
||||||
if (isPresent(nestedRenderProtoView)) {
|
|
||||||
transitiveContentTagCount += nestedRenderProtoView.transitiveContentTagCount;
|
|
||||||
}
|
|
||||||
if (isPresent(ebb.contentTagSelector)) {
|
|
||||||
transitiveContentTagCount++;
|
|
||||||
}
|
|
||||||
var parentIndex = isPresent(ebb.parent) ? ebb.parent.index : -1;
|
var parentIndex = isPresent(ebb.parent) ? ebb.parent.index : -1;
|
||||||
|
var textNodeIndices = [];
|
||||||
|
queryBoundTextNodeIndices(ebb.element, ebb.textBindings, (node, nodeIndex, expression) => {
|
||||||
|
textNodeExpressions.push(expression);
|
||||||
|
textNodeIndices.push(nodeIndex);
|
||||||
|
});
|
||||||
apiElementBinders.push(new api.ElementBinder({
|
apiElementBinders.push(new api.ElementBinder({
|
||||||
index: ebb.index,
|
index: ebb.index,
|
||||||
parentIndex: parentIndex,
|
parentIndex: parentIndex,
|
||||||
@ -91,64 +100,28 @@ export class ProtoViewBuilder {
|
|||||||
ebb.propertyBindings, directiveTemplatePropertyNames),
|
ebb.propertyBindings, directiveTemplatePropertyNames),
|
||||||
variableBindings: ebb.variableBindings,
|
variableBindings: ebb.variableBindings,
|
||||||
eventBindings: ebb.eventBindings,
|
eventBindings: ebb.eventBindings,
|
||||||
textBindings: ebb.textBindings,
|
|
||||||
readAttributes: ebb.readAttributes
|
readAttributes: ebb.readAttributes
|
||||||
}));
|
}));
|
||||||
var childNodeInfo = this._analyzeChildNodes(ebb.element, ebb.textBindingNodes);
|
domElementBinders.push(new DomElementBinder({
|
||||||
boundTextNodeCount += ebb.textBindingNodes.length;
|
textNodeIndices: textNodeIndices,
|
||||||
renderElementBinders.push(new ElementBinder({
|
hasNestedProtoView: isPresent(nestedProtoView) || isPresent(ebb.componentId),
|
||||||
textNodeIndices: childNodeInfo.boundTextNodeIndices,
|
hasNativeShadowRoot: isPresent(ebb.componentId) && this.useNativeShadowDom,
|
||||||
contentTagSelector: ebb.contentTagSelector,
|
|
||||||
parentIndex: parentIndex,
|
|
||||||
distanceToParent: ebb.distanceToParent,
|
|
||||||
nestedProtoView:
|
|
||||||
isPresent(nestedProtoView) ? resolveInternalDomProtoView(nestedProtoView.render) : null,
|
|
||||||
componentId: ebb.componentId,
|
|
||||||
eventLocals: new LiteralArray(ebb.eventBuilder.buildEventLocals()),
|
eventLocals: new LiteralArray(ebb.eventBuilder.buildEventLocals()),
|
||||||
localEvents: ebb.eventBuilder.buildLocalEvents(),
|
localEvents: ebb.eventBuilder.buildLocalEvents(),
|
||||||
globalEvents: ebb.eventBuilder.buildGlobalEvents(),
|
globalEvents: ebb.eventBuilder.buildGlobalEvents()
|
||||||
elementIsEmpty: childNodeInfo.elementIsEmpty
|
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
var rootNodeCount = DOM.childNodes(DOM.content(this.rootElement)).length;
|
||||||
return new api.ProtoViewDto({
|
return new api.ProtoViewDto({
|
||||||
render: new DomProtoViewRef(new DomProtoView({
|
render: new DomProtoViewRef(DomProtoView.create(this.type, this.rootElement, [rootNodeCount],
|
||||||
element: this.rootElement,
|
rootTextNodeIndices, domElementBinders, null,
|
||||||
elementBinders: renderElementBinders,
|
null, null)),
|
||||||
transitiveContentTagCount: transitiveContentTagCount,
|
|
||||||
boundTextNodeCount: boundTextNodeCount
|
|
||||||
})),
|
|
||||||
type: this.type,
|
type: this.type,
|
||||||
elementBinders: apiElementBinders,
|
elementBinders: apiElementBinders,
|
||||||
variableBindings: this.variableBindings
|
variableBindings: this.variableBindings,
|
||||||
|
textBindings: textNodeExpressions
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: We need to calculate the next node indices not until the compilation is complete,
|
|
||||||
// as the compiler might change the order of elements.
|
|
||||||
private _analyzeChildNodes(parentElement: /*element*/ any,
|
|
||||||
boundTextNodes: List</*node*/ any>): _ChildNodesInfo {
|
|
||||||
var childNodes = DOM.childNodes(DOM.templateAwareRoot(parentElement));
|
|
||||||
var boundTextNodeIndices = [];
|
|
||||||
var indexInBoundTextNodes = 0;
|
|
||||||
var elementIsEmpty = true;
|
|
||||||
for (var i = 0; i < childNodes.length; i++) {
|
|
||||||
var node = childNodes[i];
|
|
||||||
if (indexInBoundTextNodes < boundTextNodes.length &&
|
|
||||||
node === boundTextNodes[indexInBoundTextNodes]) {
|
|
||||||
boundTextNodeIndices.push(i);
|
|
||||||
indexInBoundTextNodes++;
|
|
||||||
elementIsEmpty = false;
|
|
||||||
} else if ((DOM.isTextNode(node) && DOM.getText(node).trim().length > 0) ||
|
|
||||||
(DOM.isElementNode(node))) {
|
|
||||||
elementIsEmpty = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new _ChildNodesInfo(boundTextNodeIndices, elementIsEmpty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ChildNodesInfo {
|
|
||||||
constructor(public boundTextNodeIndices: List<number>, public elementIsEmpty: boolean) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ElementBinderBuilder {
|
export class ElementBinderBuilder {
|
||||||
@ -161,9 +134,7 @@ export class ElementBinderBuilder {
|
|||||||
propertyBindingsToDirectives: Set<string> = new Set();
|
propertyBindingsToDirectives: Set<string> = new Set();
|
||||||
eventBindings: List<api.EventBinding> = [];
|
eventBindings: List<api.EventBinding> = [];
|
||||||
eventBuilder: EventBuilder = new EventBuilder();
|
eventBuilder: EventBuilder = new EventBuilder();
|
||||||
textBindingNodes: List</*node*/ any> = [];
|
textBindings: Map<Node, ASTWithSource> = new Map();
|
||||||
textBindings: List<ASTWithSource> = [];
|
|
||||||
contentTagSelector: string = null;
|
|
||||||
readAttributes: Map<string, string> = new Map();
|
readAttributes: Map<string, string> = new Map();
|
||||||
componentId: string = null;
|
componentId: string = null;
|
||||||
|
|
||||||
@ -229,12 +200,9 @@ export class ElementBinderBuilder {
|
|||||||
this.eventBindings.push(this.eventBuilder.add(name, expression, target));
|
this.eventBindings.push(this.eventBuilder.add(name, expression, target));
|
||||||
}
|
}
|
||||||
|
|
||||||
bindText(textNode, expression) {
|
// Note: We don't store the node index until the compilation is complete,
|
||||||
this.textBindingNodes.push(textNode);
|
// as the compiler might change the order of elements.
|
||||||
this.textBindings.push(expression);
|
bindText(textNode, expression) { this.textBindings.set(textNode, expression); }
|
||||||
}
|
|
||||||
|
|
||||||
setContentTagSelector(value: string) { this.contentTagSelector = value; }
|
|
||||||
|
|
||||||
setComponentId(componentId: string) { this.componentId = componentId; }
|
setComponentId(componentId: string) { this.componentId = componentId; }
|
||||||
}
|
}
|
||||||
|
451
modules/angular2/src/render/dom/view/proto_view_merger.ts
Normal file
451
modules/angular2/src/render/dom/view/proto_view_merger.ts
Normal file
@ -0,0 +1,451 @@
|
|||||||
|
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||||
|
import {isPresent, isBlank, BaseException, isArray} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
|
import {DomProtoView, DomProtoViewRef, resolveInternalDomProtoView} from './proto_view';
|
||||||
|
import {DomElementBinder} from './element_binder';
|
||||||
|
import {RenderProtoViewMergeMapping, RenderProtoViewRef, ViewType} from '../../api';
|
||||||
|
import {
|
||||||
|
NG_BINDING_CLASS,
|
||||||
|
NG_CONTENT_ELEMENT_NAME,
|
||||||
|
ClonedProtoView,
|
||||||
|
cloneAndQueryProtoView,
|
||||||
|
queryBoundElements,
|
||||||
|
queryBoundTextNodeIndices,
|
||||||
|
NG_SHADOW_ROOT_ELEMENT_NAME,
|
||||||
|
isElementWithTag
|
||||||
|
} from '../util';
|
||||||
|
import {CssSelector} from '../compiler/selector';
|
||||||
|
|
||||||
|
const NOT_MATCHABLE_SELECTOR = '_not-matchable_';
|
||||||
|
|
||||||
|
export function mergeProtoViewsRecursively(protoViewRefs: List<RenderProtoViewRef | List<any>>):
|
||||||
|
RenderProtoViewMergeMapping[] {
|
||||||
|
var target = [];
|
||||||
|
_mergeProtoViewsRecursively(protoViewRefs, target);
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _mergeProtoViewsRecursively(protoViewRefs: List<RenderProtoViewRef | List<any>>,
|
||||||
|
target: RenderProtoViewMergeMapping[]): RenderProtoViewRef {
|
||||||
|
var targetIndex = target.length;
|
||||||
|
target.push(null);
|
||||||
|
|
||||||
|
var resolvedProtoViewRefs = protoViewRefs.map((entry) => {
|
||||||
|
if (isArray(entry)) {
|
||||||
|
return _mergeProtoViewsRecursively(<List<any>>entry, target);
|
||||||
|
} else {
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var mapping = mergeProtoViews(resolvedProtoViewRefs);
|
||||||
|
target[targetIndex] = mapping;
|
||||||
|
return mapping.mergedProtoViewRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mergeProtoViews(protoViewRefs: RenderProtoViewRef[]): RenderProtoViewMergeMapping {
|
||||||
|
var hostProtoView = resolveInternalDomProtoView(protoViewRefs[0]);
|
||||||
|
|
||||||
|
var mergeableProtoViews: DomProtoView[] = [];
|
||||||
|
var hostElementIndices: number[] = [];
|
||||||
|
|
||||||
|
mergeableProtoViews.push(hostProtoView);
|
||||||
|
var protoViewIdx = 1;
|
||||||
|
for (var i = 0; i < hostProtoView.elementBinders.length; i++) {
|
||||||
|
var binder = hostProtoView.elementBinders[i];
|
||||||
|
if (binder.hasNestedProtoView) {
|
||||||
|
var nestedProtoViewRef = protoViewRefs[protoViewIdx++];
|
||||||
|
if (isPresent(nestedProtoViewRef)) {
|
||||||
|
mergeableProtoViews.push(resolveInternalDomProtoView(nestedProtoViewRef));
|
||||||
|
hostElementIndices.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _mergeProtoViews(mergeableProtoViews, hostElementIndices);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function _mergeProtoViews(mergeableProtoViews: DomProtoView[], hostElementIndices: number[]):
|
||||||
|
RenderProtoViewMergeMapping {
|
||||||
|
var clonedProtoViews: ClonedProtoView[] =
|
||||||
|
mergeableProtoViews.map(domProtoView => cloneAndQueryProtoView(domProtoView, false));
|
||||||
|
var hostProtoView: ClonedProtoView = clonedProtoViews[0];
|
||||||
|
|
||||||
|
// modify the DOM
|
||||||
|
mergeDom(clonedProtoViews, hostElementIndices);
|
||||||
|
|
||||||
|
// create a new root element with the changed fragments and elements
|
||||||
|
var rootElement = createRootElementFromFragments(hostProtoView.fragments);
|
||||||
|
var fragmentsRootNodeCount = hostProtoView.fragments.map(fragment => fragment.length);
|
||||||
|
var rootNode = DOM.content(rootElement);
|
||||||
|
|
||||||
|
// read out the new element / text node / ElementBinder order
|
||||||
|
var mergedBoundElements = queryBoundElements(rootNode, false);
|
||||||
|
var mergedBoundTextIndices: Map<Node, number> = new Map();
|
||||||
|
var boundTextNodeMap: Map<Node, any> = indexBoundTextNodes(clonedProtoViews);
|
||||||
|
var rootTextNodeIndices =
|
||||||
|
calcRootTextNodeIndices(rootNode, boundTextNodeMap, mergedBoundTextIndices);
|
||||||
|
var mergedElementBinders = calcElementBinders(clonedProtoViews, mergedBoundElements,
|
||||||
|
boundTextNodeMap, mergedBoundTextIndices);
|
||||||
|
|
||||||
|
// create element / text index mappings
|
||||||
|
var mappedElementIndices = calcMappedElementIndices(clonedProtoViews, mergedBoundElements);
|
||||||
|
var mappedTextIndices = calcMappedTextIndices(clonedProtoViews, mergedBoundTextIndices);
|
||||||
|
var hostElementIndicesByViewIndex =
|
||||||
|
calcHostElementIndicesByViewIndex(clonedProtoViews, hostElementIndices);
|
||||||
|
|
||||||
|
// create result
|
||||||
|
var mergedProtoView = DomProtoView.create(
|
||||||
|
hostProtoView.original.type, rootElement, fragmentsRootNodeCount, rootTextNodeIndices,
|
||||||
|
mergedElementBinders, mappedElementIndices, mappedTextIndices, hostElementIndicesByViewIndex);
|
||||||
|
return new RenderProtoViewMergeMapping(new DomProtoViewRef(mergedProtoView),
|
||||||
|
fragmentsRootNodeCount.length, mappedElementIndices,
|
||||||
|
mappedTextIndices, hostElementIndicesByViewIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
function indexBoundTextNodes(mergableProtoViews: ClonedProtoView[]): Map<Node, any> {
|
||||||
|
var boundTextNodeMap = new Map();
|
||||||
|
for (var pvIndex = 0; pvIndex < mergableProtoViews.length; pvIndex++) {
|
||||||
|
var mergableProtoView = mergableProtoViews[pvIndex];
|
||||||
|
mergableProtoView.boundTextNodes.forEach(
|
||||||
|
(textNode) => { boundTextNodeMap.set(textNode, null); });
|
||||||
|
}
|
||||||
|
return boundTextNodeMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeDom(clonedProtoViews: ClonedProtoView[], hostElementIndices: number[]) {
|
||||||
|
var nestedProtoViewByHostElement: Map<Element, ClonedProtoView> =
|
||||||
|
indexProtoViewsByHostElement(clonedProtoViews, hostElementIndices);
|
||||||
|
|
||||||
|
var hostProtoView = clonedProtoViews[0];
|
||||||
|
var mergableProtoViewIdx = 1;
|
||||||
|
hostElementIndices.forEach((boundElementIndex) => {
|
||||||
|
var binder = hostProtoView.original.elementBinders[boundElementIndex];
|
||||||
|
if (binder.hasNestedProtoView) {
|
||||||
|
var mergableNestedProtoView: ClonedProtoView = clonedProtoViews[mergableProtoViewIdx++];
|
||||||
|
if (mergableNestedProtoView.original.type === ViewType.COMPONENT) {
|
||||||
|
mergeComponentDom(hostProtoView, boundElementIndex, mergableNestedProtoView,
|
||||||
|
nestedProtoViewByHostElement);
|
||||||
|
} else {
|
||||||
|
mergeEmbeddedDom(hostProtoView, mergableNestedProtoView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function indexProtoViewsByHostElement(mergableProtoViews: ClonedProtoView[],
|
||||||
|
hostElementIndices: number[]): Map<Element, ClonedProtoView> {
|
||||||
|
var hostProtoView = mergableProtoViews[0];
|
||||||
|
var mergableProtoViewIdx = 1;
|
||||||
|
var nestedProtoViewByHostElement: Map<Element, ClonedProtoView> = new Map();
|
||||||
|
hostElementIndices.forEach((hostElementIndex) => {
|
||||||
|
nestedProtoViewByHostElement.set(hostProtoView.boundElements[hostElementIndex],
|
||||||
|
mergableProtoViews[mergableProtoViewIdx++]);
|
||||||
|
});
|
||||||
|
return nestedProtoViewByHostElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeComponentDom(hostProtoView: ClonedProtoView, boundElementIndex: number,
|
||||||
|
nestedProtoView: ClonedProtoView,
|
||||||
|
nestedProtoViewByHostElement: Map<Element, ClonedProtoView>) {
|
||||||
|
var hostElement = hostProtoView.boundElements[boundElementIndex];
|
||||||
|
|
||||||
|
// We wrap the fragments into elements so that we can expand <ng-content>
|
||||||
|
// even for root nodes in the fragment without special casing them.
|
||||||
|
var fragmentElements = mapFragmentsIntoElements(nestedProtoView.fragments);
|
||||||
|
var contentElements = findContentElements(fragmentElements);
|
||||||
|
|
||||||
|
var projectableNodes = DOM.childNodesAsList(hostElement);
|
||||||
|
for (var i = 0; i < contentElements.length; i++) {
|
||||||
|
var contentElement = contentElements[i];
|
||||||
|
var select = DOM.getAttribute(contentElement, 'select');
|
||||||
|
projectableNodes = projectMatchingNodes(select, contentElement, projectableNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// unwrap the fragment elements into arrays of nodes after projecting
|
||||||
|
var fragments = extractFragmentNodesFromElements(fragmentElements);
|
||||||
|
appendComponentNodesToHost(hostProtoView, boundElementIndex, fragments[0]);
|
||||||
|
|
||||||
|
for (var i = 1; i < fragments.length; i++) {
|
||||||
|
hostProtoView.fragments.push(fragments[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapFragmentsIntoElements(fragments: Node[][]): Element[] {
|
||||||
|
return fragments.map((fragment) => {
|
||||||
|
var fragmentElement = DOM.createTemplate('');
|
||||||
|
fragment.forEach(node => DOM.appendChild(DOM.content(fragmentElement), node));
|
||||||
|
return fragmentElement;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractFragmentNodesFromElements(fragmentElements: Element[]): Node[][] {
|
||||||
|
return fragmentElements.map(
|
||||||
|
(fragmentElement) => { return DOM.childNodesAsList(DOM.content(fragmentElement)); });
|
||||||
|
}
|
||||||
|
|
||||||
|
function findContentElements(fragmentElements: Element[]): Element[] {
|
||||||
|
var contentElements = [];
|
||||||
|
fragmentElements.forEach((fragmentElement: Element) => {
|
||||||
|
var fragmentContentElements =
|
||||||
|
DOM.querySelectorAll(DOM.content(fragmentElement), NG_CONTENT_ELEMENT_NAME);
|
||||||
|
for (var i = 0; i < fragmentContentElements.length; i++) {
|
||||||
|
contentElements.push(fragmentContentElements[i]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return sortContentElements(contentElements);
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendComponentNodesToHost(hostProtoView: ClonedProtoView, boundElementIndex: number,
|
||||||
|
componentRootNodes: Node[]) {
|
||||||
|
var hostElement = hostProtoView.boundElements[boundElementIndex];
|
||||||
|
var binder = hostProtoView.original.elementBinders[boundElementIndex];
|
||||||
|
if (binder.hasNativeShadowRoot) {
|
||||||
|
var shadowRootWrapper = DOM.createElement(NG_SHADOW_ROOT_ELEMENT_NAME);
|
||||||
|
for (var i = 0; i < componentRootNodes.length; i++) {
|
||||||
|
DOM.appendChild(shadowRootWrapper, componentRootNodes[i]);
|
||||||
|
}
|
||||||
|
var firstChild = DOM.firstChild(hostElement);
|
||||||
|
if (isPresent(firstChild)) {
|
||||||
|
DOM.insertBefore(firstChild, shadowRootWrapper);
|
||||||
|
} else {
|
||||||
|
DOM.appendChild(hostElement, shadowRootWrapper);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DOM.clearNodes(hostElement);
|
||||||
|
for (var i = 0; i < componentRootNodes.length; i++) {
|
||||||
|
DOM.appendChild(hostElement, componentRootNodes[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeEmbeddedDom(parentProtoView: ClonedProtoView, nestedProtoView: ClonedProtoView) {
|
||||||
|
nestedProtoView.fragments.forEach((fragment) => parentProtoView.fragments.push(fragment));
|
||||||
|
}
|
||||||
|
|
||||||
|
function projectMatchingNodes(selector: string, contentElement: Element, nodes: Node[]): Node[] {
|
||||||
|
var remaining = [];
|
||||||
|
var removeContentElement = true;
|
||||||
|
for (var i = 0; i < nodes.length; i++) {
|
||||||
|
var node = nodes[i];
|
||||||
|
if (isWildcard(selector)) {
|
||||||
|
DOM.insertBefore(contentElement, node);
|
||||||
|
} else if (DOM.isElementNode(node)) {
|
||||||
|
if (isElementWithTag(node, NG_CONTENT_ELEMENT_NAME)) {
|
||||||
|
// keep the projected content as other <ng-content> elements
|
||||||
|
// might want to use it as well.
|
||||||
|
remaining.push(node);
|
||||||
|
DOM.setAttribute(contentElement, 'select',
|
||||||
|
mergeSelectors(selector, DOM.getAttribute(node, 'select')));
|
||||||
|
removeContentElement = false;
|
||||||
|
} else {
|
||||||
|
if (DOM.elementMatches(node, selector)) {
|
||||||
|
DOM.insertBefore(contentElement, node);
|
||||||
|
} else {
|
||||||
|
remaining.push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// non projected text nodes
|
||||||
|
remaining.push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (removeContentElement) {
|
||||||
|
DOM.remove(contentElement);
|
||||||
|
}
|
||||||
|
return remaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isWildcard(selector): boolean {
|
||||||
|
return isBlank(selector) || selector.length === 0 || selector == '*';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mergeSelectors(selector1: string, selector2: string): string {
|
||||||
|
if (isWildcard(selector1)) {
|
||||||
|
return isBlank(selector2) ? '' : selector2;
|
||||||
|
} else if (isWildcard(selector2)) {
|
||||||
|
return isBlank(selector1) ? '' : selector1;
|
||||||
|
} else {
|
||||||
|
var sels1 = CssSelector.parse(selector1);
|
||||||
|
var sels2 = CssSelector.parse(selector2);
|
||||||
|
if (sels1.length > 1 || sels2.length > 1) {
|
||||||
|
throw new BaseException('multiple selectors are not supported in ng-content');
|
||||||
|
}
|
||||||
|
var sel1 = sels1[0];
|
||||||
|
var sel2 = sels2[0];
|
||||||
|
if (sel1.notSelectors.length > 0 || sel2.notSelectors.length > 0) {
|
||||||
|
throw new BaseException(':not selector is not supported in ng-content');
|
||||||
|
}
|
||||||
|
var merged = new CssSelector();
|
||||||
|
if (isBlank(sel1.element)) {
|
||||||
|
merged.setElement(sel2.element);
|
||||||
|
} else if (isBlank(sel2.element)) {
|
||||||
|
merged.setElement(sel1.element);
|
||||||
|
} else {
|
||||||
|
return NOT_MATCHABLE_SELECTOR;
|
||||||
|
}
|
||||||
|
merged.attrs = sel1.attrs.concat(sel2.attrs);
|
||||||
|
merged.classNames = sel1.classNames.concat(sel2.classNames);
|
||||||
|
return merged.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we need to sort content elements as they can originate from
|
||||||
|
// different sub views
|
||||||
|
function sortContentElements(contentElements: Element[]): Element[] {
|
||||||
|
// for now, only move the wildcard selector to the end.
|
||||||
|
// TODO(tbosch): think about sorting by selector specifity...
|
||||||
|
var firstWildcard = null;
|
||||||
|
var sorted = [];
|
||||||
|
contentElements.forEach((contentElement) => {
|
||||||
|
var select = DOM.getAttribute(contentElement, 'select');
|
||||||
|
if (isWildcard(select)) {
|
||||||
|
if (isBlank(firstWildcard)) {
|
||||||
|
firstWildcard = contentElement;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sorted.push(contentElement);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (isPresent(firstWildcard)) {
|
||||||
|
sorted.push(firstWildcard);
|
||||||
|
}
|
||||||
|
return sorted;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function createRootElementFromFragments(fragments: Node[][]): Element {
|
||||||
|
var rootElement = DOM.createTemplate('');
|
||||||
|
var rootNode = DOM.content(rootElement);
|
||||||
|
fragments.forEach(
|
||||||
|
(fragment) => { fragment.forEach((node) => { DOM.appendChild(rootNode, node); }); });
|
||||||
|
return rootElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calcRootTextNodeIndices(rootNode: Node, boundTextNodes: Map<Node, any>,
|
||||||
|
targetBoundTextIndices: Map<Node, number>): number[] {
|
||||||
|
var rootTextNodeIndices = [];
|
||||||
|
queryBoundTextNodeIndices(rootNode, boundTextNodes, (textNode, nodeIndex, _) => {
|
||||||
|
rootTextNodeIndices.push(nodeIndex);
|
||||||
|
targetBoundTextIndices.set(textNode, targetBoundTextIndices.size);
|
||||||
|
});
|
||||||
|
return rootTextNodeIndices;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calcElementBinders(clonedProtoViews: ClonedProtoView[], mergedBoundElements: Element[],
|
||||||
|
boundTextNodes: Map<Node, any>,
|
||||||
|
targetBoundTextIndices: Map<Node, number>): DomElementBinder[] {
|
||||||
|
var elementBinderByElement: Map<Element, DomElementBinder> =
|
||||||
|
indexElementBindersByElement(clonedProtoViews);
|
||||||
|
var mergedElementBinders = [];
|
||||||
|
for (var i = 0; i < mergedBoundElements.length; i++) {
|
||||||
|
var element = mergedBoundElements[i];
|
||||||
|
var textNodeIndices = [];
|
||||||
|
queryBoundTextNodeIndices(element, boundTextNodes, (textNode, nodeIndex, _) => {
|
||||||
|
textNodeIndices.push(nodeIndex);
|
||||||
|
targetBoundTextIndices.set(textNode, targetBoundTextIndices.size);
|
||||||
|
});
|
||||||
|
mergedElementBinders.push(
|
||||||
|
updateElementBinderTextNodeIndices(elementBinderByElement.get(element), textNodeIndices));
|
||||||
|
}
|
||||||
|
return mergedElementBinders;
|
||||||
|
}
|
||||||
|
|
||||||
|
function indexElementBindersByElement(mergableProtoViews: ClonedProtoView[]):
|
||||||
|
Map<Element, DomElementBinder> {
|
||||||
|
var elementBinderByElement = new Map();
|
||||||
|
mergableProtoViews.forEach((mergableProtoView) => {
|
||||||
|
for (var i = 0; i < mergableProtoView.boundElements.length; i++) {
|
||||||
|
var el = mergableProtoView.boundElements[i];
|
||||||
|
if (isPresent(el)) {
|
||||||
|
elementBinderByElement.set(el, mergableProtoView.original.elementBinders[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return elementBinderByElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateElementBinderTextNodeIndices(elementBinder: DomElementBinder,
|
||||||
|
textNodeIndices: number[]): DomElementBinder {
|
||||||
|
var result;
|
||||||
|
if (isBlank(elementBinder)) {
|
||||||
|
result = new DomElementBinder({
|
||||||
|
textNodeIndices: textNodeIndices,
|
||||||
|
hasNestedProtoView: false,
|
||||||
|
eventLocals: null,
|
||||||
|
localEvents: [],
|
||||||
|
globalEvents: [],
|
||||||
|
hasNativeShadowRoot: null
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
result = new DomElementBinder({
|
||||||
|
textNodeIndices: textNodeIndices,
|
||||||
|
hasNestedProtoView: false,
|
||||||
|
eventLocals: elementBinder.eventLocals,
|
||||||
|
localEvents: elementBinder.localEvents,
|
||||||
|
globalEvents: elementBinder.globalEvents,
|
||||||
|
hasNativeShadowRoot: elementBinder.hasNativeShadowRoot
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calcMappedElementIndices(clonedProtoViews: ClonedProtoView[],
|
||||||
|
mergedBoundElements: Element[]): number[] {
|
||||||
|
var mergedBoundElementIndices: Map<Element, number> = indexArray(mergedBoundElements);
|
||||||
|
var mappedElementIndices = [];
|
||||||
|
clonedProtoViews.forEach((clonedProtoView) => {
|
||||||
|
clonedProtoView.original.mappedElementIndices.forEach((boundElementIndex) => {
|
||||||
|
var mappedElementIndex = null;
|
||||||
|
if (isPresent(boundElementIndex)) {
|
||||||
|
var boundElement = clonedProtoView.boundElements[boundElementIndex];
|
||||||
|
mappedElementIndex = mergedBoundElementIndices.get(boundElement);
|
||||||
|
}
|
||||||
|
mappedElementIndices.push(mappedElementIndex);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return mappedElementIndices;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calcMappedTextIndices(clonedProtoViews: ClonedProtoView[],
|
||||||
|
mergedBoundTextIndices: Map<Node, number>): number[] {
|
||||||
|
var mappedTextIndices = [];
|
||||||
|
clonedProtoViews.forEach((clonedProtoView) => {
|
||||||
|
clonedProtoView.original.mappedTextIndices.forEach((textNodeIndex) => {
|
||||||
|
var mappedTextIndex = null;
|
||||||
|
if (isPresent(textNodeIndex)) {
|
||||||
|
var textNode = clonedProtoView.boundTextNodes[textNodeIndex];
|
||||||
|
mappedTextIndex = mergedBoundTextIndices.get(textNode);
|
||||||
|
}
|
||||||
|
mappedTextIndices.push(mappedTextIndex);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return mappedTextIndices;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calcHostElementIndicesByViewIndex(clonedProtoViews: ClonedProtoView[],
|
||||||
|
hostElementIndices: number[]): number[] {
|
||||||
|
var mergedElementCount = 0;
|
||||||
|
var hostElementIndicesByViewIndex = [];
|
||||||
|
for (var i = 0; i < clonedProtoViews.length; i++) {
|
||||||
|
var clonedProtoView = clonedProtoViews[i];
|
||||||
|
clonedProtoView.original.hostElementIndicesByViewIndex.forEach((hostElementIndex) => {
|
||||||
|
var mappedHostElementIndex;
|
||||||
|
if (isBlank(hostElementIndex)) {
|
||||||
|
mappedHostElementIndex = i > 0 ? hostElementIndices[i - 1] : null;
|
||||||
|
} else {
|
||||||
|
mappedHostElementIndex = hostElementIndex + mergedElementCount;
|
||||||
|
}
|
||||||
|
hostElementIndicesByViewIndex.push(mappedHostElementIndex);
|
||||||
|
});
|
||||||
|
mergedElementCount += clonedProtoView.original.mappedElementIndices.length;
|
||||||
|
}
|
||||||
|
return hostElementIndicesByViewIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
function indexArray(arr: any[]): Map<any, number> {
|
||||||
|
var map = new Map();
|
||||||
|
for (var i = 0; i < arr.length; i++) {
|
||||||
|
map.set(arr[i], i);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
@ -3,10 +3,8 @@ import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src
|
|||||||
import {isPresent, isBlank, BaseException, stringify} from 'angular2/src/facade/lang';
|
import {isPresent, isBlank, BaseException, stringify} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
import {DomProtoView} from './proto_view';
|
import {DomProtoView} from './proto_view';
|
||||||
import {LightDom} from '../shadow_dom/light_dom';
|
|
||||||
import {DomElement} from './element';
|
|
||||||
|
|
||||||
import {RenderViewRef, EventDispatcher} from '../../api';
|
import {RenderViewRef, RenderEventDispatcher} from '../../api';
|
||||||
import {camelCaseToDashCase} from '../util';
|
import {camelCaseToDashCase} from '../util';
|
||||||
|
|
||||||
export function resolveInternalDomView(viewRef: RenderViewRef): DomView {
|
export function resolveInternalDomView(viewRef: RenderViewRef): DomView {
|
||||||
@ -21,30 +19,19 @@ export class DomViewRef extends RenderViewRef {
|
|||||||
* Const of making objects: http://jsperf.com/instantiate-size-of-object
|
* Const of making objects: http://jsperf.com/instantiate-size-of-object
|
||||||
*/
|
*/
|
||||||
export class DomView {
|
export class DomView {
|
||||||
hostLightDom: LightDom = null;
|
|
||||||
shadowRoot = null;
|
|
||||||
hydrated: boolean = false;
|
hydrated: boolean = false;
|
||||||
eventDispatcher: EventDispatcher = null;
|
eventDispatcher: RenderEventDispatcher = null;
|
||||||
eventHandlerRemovers: List<Function> = [];
|
eventHandlerRemovers: List<Function> = [];
|
||||||
|
|
||||||
constructor(public proto: DomProtoView, public rootNodes: List</*node*/ any>,
|
constructor(public proto: DomProtoView, public boundTextNodes: List<Node>,
|
||||||
public boundTextNodes: List</*node*/ any>, public boundElements: List<DomElement>) {}
|
public boundElements: Element[]) {}
|
||||||
|
|
||||||
getDirectParentElement(boundElementIndex: number): DomElement {
|
|
||||||
var binder = this.proto.elementBinders[boundElementIndex];
|
|
||||||
var parent = null;
|
|
||||||
if (binder.parentIndex !== -1 && binder.distanceToParent === 1) {
|
|
||||||
parent = this.boundElements[binder.parentIndex];
|
|
||||||
}
|
|
||||||
return parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
setElementProperty(elementIndex: number, propertyName: string, value: any) {
|
setElementProperty(elementIndex: number, propertyName: string, value: any) {
|
||||||
DOM.setProperty(this.boundElements[elementIndex].element, propertyName, value);
|
DOM.setProperty(this.boundElements[elementIndex], propertyName, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
setElementAttribute(elementIndex: number, attributeName: string, value: string) {
|
setElementAttribute(elementIndex: number, attributeName: string, value: string) {
|
||||||
var element = this.boundElements[elementIndex].element;
|
var element = this.boundElements[elementIndex];
|
||||||
var dashCasedAttributeName = camelCaseToDashCase(attributeName);
|
var dashCasedAttributeName = camelCaseToDashCase(attributeName);
|
||||||
if (isPresent(value)) {
|
if (isPresent(value)) {
|
||||||
DOM.setAttribute(element, dashCasedAttributeName, stringify(value));
|
DOM.setAttribute(element, dashCasedAttributeName, stringify(value));
|
||||||
@ -54,7 +41,7 @@ export class DomView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setElementClass(elementIndex: number, className: string, isAdd: boolean) {
|
setElementClass(elementIndex: number, className: string, isAdd: boolean) {
|
||||||
var element = this.boundElements[elementIndex].element;
|
var element = this.boundElements[elementIndex];
|
||||||
var dashCasedClassName = camelCaseToDashCase(className);
|
var dashCasedClassName = camelCaseToDashCase(className);
|
||||||
if (isAdd) {
|
if (isAdd) {
|
||||||
DOM.addClass(element, dashCasedClassName);
|
DOM.addClass(element, dashCasedClassName);
|
||||||
@ -64,7 +51,7 @@ export class DomView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setElementStyle(elementIndex: number, styleName: string, value: string) {
|
setElementStyle(elementIndex: number, styleName: string, value: string) {
|
||||||
var element = this.boundElements[elementIndex].element;
|
var element = this.boundElements[elementIndex];
|
||||||
var dashCasedStyleName = camelCaseToDashCase(styleName);
|
var dashCasedStyleName = camelCaseToDashCase(styleName);
|
||||||
if (isPresent(value)) {
|
if (isPresent(value)) {
|
||||||
DOM.setStyle(element, dashCasedStyleName, stringify(value));
|
DOM.setStyle(element, dashCasedStyleName, stringify(value));
|
||||||
@ -74,7 +61,7 @@ export class DomView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
invokeElementMethod(elementIndex: number, methodName: string, args: List<any>) {
|
invokeElementMethod(elementIndex: number, methodName: string, args: List<any>) {
|
||||||
var element = this.boundElements[elementIndex].element;
|
var element = this.boundElements[elementIndex];
|
||||||
DOM.invoke(element, methodName, args);
|
DOM.invoke(element, methodName, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,7 +78,7 @@ export class DomView {
|
|||||||
// Locals(null, evalLocals));
|
// Locals(null, evalLocals));
|
||||||
// this.eventDispatcher.dispatchEvent(elementIndex, eventName, localValues);
|
// this.eventDispatcher.dispatchEvent(elementIndex, eventName, localValues);
|
||||||
allowDefaultBehavior =
|
allowDefaultBehavior =
|
||||||
this.eventDispatcher.dispatchEvent(elementIndex, eventName, evalLocals);
|
this.eventDispatcher.dispatchRenderEvent(elementIndex, eventName, evalLocals);
|
||||||
if (!allowDefaultBehavior) {
|
if (!allowDefaultBehavior) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
@ -5,14 +5,4 @@ import * as viewModule from './view';
|
|||||||
export class DomViewContainer {
|
export class DomViewContainer {
|
||||||
// The order in this list matches the DOM order.
|
// The order in this list matches the DOM order.
|
||||||
views: List<viewModule.DomView> = [];
|
views: List<viewModule.DomView> = [];
|
||||||
|
|
||||||
contentTagContainers(): List<viewModule.DomView> { return this.views; }
|
|
||||||
|
|
||||||
nodes(): List</*node*/ any> {
|
|
||||||
var r = [];
|
|
||||||
for (var i = 0; i < this.views.length; ++i) {
|
|
||||||
r = ListWrapper.concat(r, this.views[i].rootNodes);
|
|
||||||
}
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -271,6 +271,8 @@ export interface GuinessCompatibleSpy extends jasmine.Spy {
|
|||||||
/** By chaining the spy with and.callFake, all calls to the spy will delegate to the supplied
|
/** By chaining the spy with and.callFake, all calls to the spy will delegate to the supplied
|
||||||
* function. */
|
* function. */
|
||||||
andCallFake(fn: Function): GuinessCompatibleSpy;
|
andCallFake(fn: Function): GuinessCompatibleSpy;
|
||||||
|
/** removes all recorded calls */
|
||||||
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SpyObject {
|
export class SpyObject {
|
||||||
@ -320,6 +322,7 @@ export class SpyObject {
|
|||||||
var newSpy: GuinessCompatibleSpy = <any>jasmine.createSpy(name);
|
var newSpy: GuinessCompatibleSpy = <any>jasmine.createSpy(name);
|
||||||
newSpy.andCallFake = <any>newSpy.and.callFake;
|
newSpy.andCallFake = <any>newSpy.and.callFake;
|
||||||
newSpy.andReturn = <any>newSpy.and.returnValue;
|
newSpy.andReturn = <any>newSpy.and.returnValue;
|
||||||
|
newSpy.reset = <any>newSpy.calls.reset;
|
||||||
// return null by default to satisfy our rtts asserts
|
// return null by default to satisfy our rtts asserts
|
||||||
newSpy.and.returnValue(null);
|
newSpy.and.returnValue(null);
|
||||||
return newSpy;
|
return newSpy;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import {List, ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
|
import {List, ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
|
||||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||||
import {isPresent, isString, RegExpWrapper, StringWrapper, RegExp} from 'angular2/src/facade/lang';
|
import {isPresent, isString, RegExpWrapper, StringWrapper, RegExp} from 'angular2/src/facade/lang';
|
||||||
import {resolveInternalDomView} from 'angular2/src/render/dom/view/view';
|
|
||||||
|
|
||||||
export class Log {
|
export class Log {
|
||||||
_result: List<any>;
|
_result: List<any>;
|
||||||
@ -17,21 +16,6 @@ export class Log {
|
|||||||
result(): string { return ListWrapper.join(this._result, "; "); }
|
result(): string { return ListWrapper.join(this._result, "; "); }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function viewRootNodes(view): List</*node*/ any> {
|
|
||||||
return resolveInternalDomView(view.render).rootNodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function queryView(view, selector: string): any {
|
|
||||||
var rootNodes = viewRootNodes(view);
|
|
||||||
for (var i = 0; i < rootNodes.length; ++i) {
|
|
||||||
var res = DOM.querySelector(rootNodes[i], selector);
|
|
||||||
if (isPresent(res)) {
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function dispatchEvent(element, eventType) {
|
export function dispatchEvent(element, eventType) {
|
||||||
DOM.dispatchEvent(element, DOM.createEvent(eventType));
|
DOM.dispatchEvent(element, DOM.createEvent(eventType));
|
||||||
}
|
}
|
||||||
|
@ -185,7 +185,6 @@ class ElementBinderSerializer {
|
|||||||
'propertyBindings': Serializer.serialize(binder.propertyBindings, ElementPropertyBinding),
|
'propertyBindings': Serializer.serialize(binder.propertyBindings, ElementPropertyBinding),
|
||||||
'variableBindings': Serializer.mapToObject(binder.variableBindings),
|
'variableBindings': Serializer.mapToObject(binder.variableBindings),
|
||||||
'eventBindings': Serializer.serialize(binder.eventBindings, EventBinding),
|
'eventBindings': Serializer.serialize(binder.eventBindings, EventBinding),
|
||||||
'textBindings': Serializer.serialize(binder.textBindings, ASTWithSource),
|
|
||||||
'readAttributes': Serializer.mapToObject(binder.readAttributes)
|
'readAttributes': Serializer.mapToObject(binder.readAttributes)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -200,7 +199,6 @@ class ElementBinderSerializer {
|
|||||||
propertyBindings: Serializer.deserialize(obj.propertyBindings, ElementPropertyBinding),
|
propertyBindings: Serializer.deserialize(obj.propertyBindings, ElementPropertyBinding),
|
||||||
variableBindings: Serializer.objectToMap(obj.variableBindings),
|
variableBindings: Serializer.objectToMap(obj.variableBindings),
|
||||||
eventBindings: Serializer.deserialize(obj.eventBindings, EventBinding),
|
eventBindings: Serializer.deserialize(obj.eventBindings, EventBinding),
|
||||||
textBindings: Serializer.deserialize(obj.textBindings, ASTWithSource, "interpolation"),
|
|
||||||
readAttributes: Serializer.objectToMap(obj.readAttributes)
|
readAttributes: Serializer.objectToMap(obj.readAttributes)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -213,6 +211,7 @@ class ProtoViewDtoSerializer {
|
|||||||
'render': null,
|
'render': null,
|
||||||
'elementBinders': Serializer.serialize(view.elementBinders, ElementBinder),
|
'elementBinders': Serializer.serialize(view.elementBinders, ElementBinder),
|
||||||
'variableBindings': Serializer.mapToObject(view.variableBindings),
|
'variableBindings': Serializer.mapToObject(view.variableBindings),
|
||||||
|
'textBindings': Serializer.serialize(view.textBindings, ASTWithSource),
|
||||||
'type': view.type
|
'type': view.type
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -222,6 +221,7 @@ class ProtoViewDtoSerializer {
|
|||||||
render: null, // TODO: fix render refs and write a serializer for them
|
render: null, // TODO: fix render refs and write a serializer for them
|
||||||
elementBinders: Serializer.deserialize(obj.elementBinders, ElementBinder),
|
elementBinders: Serializer.deserialize(obj.elementBinders, ElementBinder),
|
||||||
variableBindings: Serializer.objectToMap(obj.variableBindings),
|
variableBindings: Serializer.objectToMap(obj.variableBindings),
|
||||||
|
textBindings: Serializer.deserialize(obj.textBindings, ASTWithSource, "interpolation"),
|
||||||
type: obj.type
|
type: obj.type
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ import "package:angular2/di.dart" show Injectable;
|
|||||||
@Template(
|
@Template(
|
||||||
inline: '<div class="greeting">{{greeting}} <span red>world</span>!</div>'
|
inline: '<div class="greeting">{{greeting}} <span red>world</span>!</div>'
|
||||||
'<button class="changeButton" (click)="changeGreeting()">'
|
'<button class="changeButton" (click)="changeGreeting()">'
|
||||||
'change greeting</button><content></content>',
|
'change greeting</button><ng-content></ng-content>',
|
||||||
directives: const [RedDec])
|
directives: const [RedDec])
|
||||||
class HelloCmp {
|
class HelloCmp {
|
||||||
String greeting;
|
String greeting;
|
||||||
|
@ -29,7 +29,7 @@ class HelloRootCmp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Component({selector: 'hello-app'})
|
@Component({selector: 'hello-app'})
|
||||||
@View({template: 'before: <content></content> after: done'})
|
@View({template: 'before: <ng-content></ng-content> after: done'})
|
||||||
class HelloRootCmpContent {
|
class HelloRootCmpContent {
|
||||||
constructor() {}
|
constructor() {}
|
||||||
}
|
}
|
||||||
@ -68,19 +68,19 @@ class HelloRootDirectiveIsNotCmp {
|
|||||||
export function main() {
|
export function main() {
|
||||||
var fakeDoc, el, el2, testBindings, lightDom;
|
var fakeDoc, el, el2, testBindings, lightDom;
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fakeDoc = DOM.createHtmlDocument();
|
|
||||||
el = DOM.createElement('hello-app', fakeDoc);
|
|
||||||
el2 = DOM.createElement('hello-app-2', fakeDoc);
|
|
||||||
lightDom = DOM.createElement('light-dom-el', fakeDoc);
|
|
||||||
DOM.appendChild(fakeDoc.body, el);
|
|
||||||
DOM.appendChild(fakeDoc.body, el2);
|
|
||||||
DOM.appendChild(el, lightDom);
|
|
||||||
DOM.setText(lightDom, 'loading');
|
|
||||||
testBindings = [bind(DOCUMENT_TOKEN).toValue(fakeDoc)];
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('bootstrap factory method', () => {
|
describe('bootstrap factory method', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
fakeDoc = DOM.createHtmlDocument();
|
||||||
|
el = DOM.createElement('hello-app', fakeDoc);
|
||||||
|
el2 = DOM.createElement('hello-app-2', fakeDoc);
|
||||||
|
lightDom = DOM.createElement('light-dom-el', fakeDoc);
|
||||||
|
DOM.appendChild(fakeDoc.body, el);
|
||||||
|
DOM.appendChild(fakeDoc.body, el2);
|
||||||
|
DOM.appendChild(el, lightDom);
|
||||||
|
DOM.setText(lightDom, 'loading');
|
||||||
|
testBindings = [bind(DOCUMENT_TOKEN).toValue(fakeDoc)];
|
||||||
|
});
|
||||||
|
|
||||||
it('should throw if bootstrapped Directive is not a Component',
|
it('should throw if bootstrapped Directive is not a Component',
|
||||||
inject([AsyncTestCompleter], (async) => {
|
inject([AsyncTestCompleter], (async) => {
|
||||||
var refPromise =
|
var refPromise =
|
||||||
@ -149,14 +149,6 @@ export function main() {
|
|||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it("should support shadow dom content tag", inject([AsyncTestCompleter], (async) => {
|
|
||||||
var refPromise = bootstrap(HelloRootCmpContent, testBindings);
|
|
||||||
refPromise.then((ref) => {
|
|
||||||
expect(el).toHaveText('before: loading after: done');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should register each application with the testability registry',
|
it('should register each application with the testability registry',
|
||||||
inject([AsyncTestCompleter], (async) => {
|
inject([AsyncTestCompleter], (async) => {
|
||||||
var refPromise1 = bootstrap(HelloRootCmp, testBindings);
|
var refPromise1 = bootstrap(HelloRootCmp, testBindings);
|
||||||
|
@ -15,7 +15,7 @@ import {
|
|||||||
} from 'angular2/test_lib';
|
} from 'angular2/test_lib';
|
||||||
|
|
||||||
import {List, ListWrapper, Map, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
import {List, ListWrapper, Map, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||||
import {IMPLEMENTS, Type, isBlank, stringify, isPresent} from 'angular2/src/facade/lang';
|
import {IMPLEMENTS, Type, isBlank, stringify, isPresent, isArray} from 'angular2/src/facade/lang';
|
||||||
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
|
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
|
||||||
|
|
||||||
import {Compiler, CompilerCache} from 'angular2/src/core/compiler/compiler';
|
import {Compiler, CompilerCache} from 'angular2/src/core/compiler/compiler';
|
||||||
@ -45,24 +45,41 @@ export function main() {
|
|||||||
rootProtoView;
|
rootProtoView;
|
||||||
var renderCompileRequests: any[];
|
var renderCompileRequests: any[];
|
||||||
|
|
||||||
beforeEach(() => {
|
function mergeProtoViewsRecursively(
|
||||||
directiveResolver = new DirectiveResolver();
|
protoViewRefs: List<renderApi.RenderProtoViewRef | List<any>>,
|
||||||
tplResolver = new FakeViewResolver();
|
target: renderApi.RenderProtoViewMergeMapping[]): renderApi.RenderProtoViewRef {
|
||||||
cmpUrlMapper = new RuntimeComponentUrlMapper();
|
var targetIndex = target.length;
|
||||||
renderCompiler = new SpyRenderCompiler();
|
target.push(null);
|
||||||
renderCompiler.spy('compileHost')
|
|
||||||
.andCallFake((componentId) => {
|
var flattended = protoViewRefs.map(protoViewRefOrArray => {
|
||||||
return PromiseWrapper.resolve(createRenderProtoView(
|
var resolvedProtoViewRef;
|
||||||
[createRenderComponentElementBinder(0)], renderApi.ViewType.HOST));
|
if (isArray(protoViewRefOrArray)) {
|
||||||
});
|
resolvedProtoViewRef = mergeProtoViewsRecursively(
|
||||||
rootProtoView = createRootProtoView(directiveResolver, MainComponent);
|
<List<renderApi.RenderProtoViewRef>>protoViewRefOrArray, target);
|
||||||
});
|
} else {
|
||||||
|
resolvedProtoViewRef = protoViewRefOrArray;
|
||||||
|
}
|
||||||
|
return resolvedProtoViewRef;
|
||||||
|
});
|
||||||
|
var merged = [];
|
||||||
|
flattended.forEach((entry) => {
|
||||||
|
if (entry instanceof MergedRenderProtoViewRef) {
|
||||||
|
entry.originals.forEach(ref => merged.push(ref));
|
||||||
|
} else {
|
||||||
|
merged.push(entry);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var result = new MergedRenderProtoViewRef(merged);
|
||||||
|
target[targetIndex] = new renderApi.RenderProtoViewMergeMapping(result, 1, [], [], []);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
function createCompiler(renderCompileResults:
|
function createCompiler(renderCompileResults:
|
||||||
List<renderApi.ProtoViewDto | Promise<renderApi.ProtoViewDto>>,
|
List<renderApi.ProtoViewDto | Promise<renderApi.ProtoViewDto>>,
|
||||||
protoViewFactoryResults: List<List<AppProtoView>>) {
|
protoViewFactoryResults: List<AppProtoView>) {
|
||||||
var urlResolver = new UrlResolver();
|
var urlResolver = new UrlResolver();
|
||||||
renderCompileRequests = [];
|
renderCompileRequests = [];
|
||||||
|
renderCompileResults = ListWrapper.clone(renderCompileResults);
|
||||||
renderCompiler.spy('compile').andCallFake((view) => {
|
renderCompiler.spy('compile').andCallFake((view) => {
|
||||||
renderCompileRequests.push(view);
|
renderCompileRequests.push(view);
|
||||||
return PromiseWrapper.resolve(ListWrapper.removeAt(renderCompileResults, 0));
|
return PromiseWrapper.resolve(ListWrapper.removeAt(renderCompileResults, 0));
|
||||||
@ -73,12 +90,31 @@ export function main() {
|
|||||||
urlResolver, renderCompiler, protoViewFactory, new FakeAppRootUrl());
|
urlResolver, renderCompiler, protoViewFactory, new FakeAppRootUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
directiveResolver = new DirectiveResolver();
|
||||||
|
tplResolver = new FakeViewResolver();
|
||||||
|
cmpUrlMapper = new RuntimeComponentUrlMapper();
|
||||||
|
renderCompiler = new SpyRenderCompiler();
|
||||||
|
renderCompiler.spy('compileHost')
|
||||||
|
.andCallFake((componentId) => {
|
||||||
|
return PromiseWrapper.resolve(createRenderProtoView(
|
||||||
|
[createRenderComponentElementBinder(0)], renderApi.ViewType.HOST));
|
||||||
|
});
|
||||||
|
renderCompiler.spy('mergeProtoViewsRecursively')
|
||||||
|
.andCallFake((protoViewRefs: List<renderApi.RenderProtoViewRef | List<any>>) => {
|
||||||
|
var result: renderApi.RenderProtoViewMergeMapping[] = [];
|
||||||
|
mergeProtoViewsRecursively(protoViewRefs, result);
|
||||||
|
return PromiseWrapper.resolve(result);
|
||||||
|
});
|
||||||
|
rootProtoView = createRootProtoView(directiveResolver, MainComponent);
|
||||||
|
});
|
||||||
|
|
||||||
describe('serialize template', () => {
|
describe('serialize template', () => {
|
||||||
|
|
||||||
function captureTemplate(template: viewAnn.View): Promise<renderApi.ViewDefinition> {
|
function captureTemplate(template: viewAnn.View): Promise<renderApi.ViewDefinition> {
|
||||||
tplResolver.setView(MainComponent, template);
|
tplResolver.setView(MainComponent, template);
|
||||||
var compiler =
|
var compiler =
|
||||||
createCompiler([createRenderProtoView()], [[rootProtoView], [createProtoView()]]);
|
createCompiler([createRenderProtoView()], [rootProtoView, createProtoView()]);
|
||||||
return compiler.compileInHost(MainComponent)
|
return compiler.compileInHost(MainComponent)
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
expect(renderCompileRequests.length).toBe(1);
|
expect(renderCompileRequests.length).toBe(1);
|
||||||
@ -260,7 +296,7 @@ export function main() {
|
|||||||
tplResolver.setView(MainComponent, new viewAnn.View({template: '<div></div>'}));
|
tplResolver.setView(MainComponent, new viewAnn.View({template: '<div></div>'}));
|
||||||
var renderProtoView = createRenderProtoView();
|
var renderProtoView = createRenderProtoView();
|
||||||
var expectedProtoView = createProtoView();
|
var expectedProtoView = createProtoView();
|
||||||
var compiler = createCompiler([renderProtoView], [[rootProtoView], [expectedProtoView]]);
|
var compiler = createCompiler([renderProtoView], [rootProtoView, expectedProtoView]);
|
||||||
compiler.compileInHost(MainComponent)
|
compiler.compileInHost(MainComponent)
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
var request = protoViewFactory.requests[1];
|
var request = protoViewFactory.requests[1];
|
||||||
@ -272,7 +308,7 @@ export function main() {
|
|||||||
it('should pass the component binding', inject([AsyncTestCompleter], (async) => {
|
it('should pass the component binding', inject([AsyncTestCompleter], (async) => {
|
||||||
tplResolver.setView(MainComponent, new viewAnn.View({template: '<div></div>'}));
|
tplResolver.setView(MainComponent, new viewAnn.View({template: '<div></div>'}));
|
||||||
var compiler =
|
var compiler =
|
||||||
createCompiler([createRenderProtoView()], [[rootProtoView], [createProtoView()]]);
|
createCompiler([createRenderProtoView()], [rootProtoView, createProtoView()]);
|
||||||
compiler.compileInHost(MainComponent)
|
compiler.compileInHost(MainComponent)
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
var request = protoViewFactory.requests[1];
|
var request = protoViewFactory.requests[1];
|
||||||
@ -286,7 +322,7 @@ export function main() {
|
|||||||
MainComponent,
|
MainComponent,
|
||||||
new viewAnn.View({template: '<div></div>', directives: [SomeDirective]}));
|
new viewAnn.View({template: '<div></div>', directives: [SomeDirective]}));
|
||||||
var compiler =
|
var compiler =
|
||||||
createCompiler([createRenderProtoView()], [[rootProtoView], [createProtoView()]]);
|
createCompiler([createRenderProtoView()], [rootProtoView, createProtoView()]);
|
||||||
compiler.compileInHost(MainComponent)
|
compiler.compileInHost(MainComponent)
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
var request = protoViewFactory.requests[1];
|
var request = protoViewFactory.requests[1];
|
||||||
@ -300,7 +336,7 @@ export function main() {
|
|||||||
inject([AsyncTestCompleter], (async) => {
|
inject([AsyncTestCompleter], (async) => {
|
||||||
tplResolver.setView(MainComponent, new viewAnn.View({template: '<div></div>'}));
|
tplResolver.setView(MainComponent, new viewAnn.View({template: '<div></div>'}));
|
||||||
var compiler =
|
var compiler =
|
||||||
createCompiler([createRenderProtoView()], [[rootProtoView], [createProtoView()]]);
|
createCompiler([createRenderProtoView()], [rootProtoView, createProtoView()]);
|
||||||
compiler.compileInHost(MainComponent)
|
compiler.compileInHost(MainComponent)
|
||||||
.then((protoViewRef) => {
|
.then((protoViewRef) => {
|
||||||
expect(internalProtoView(protoViewRef)).toBe(rootProtoView);
|
expect(internalProtoView(protoViewRef)).toBe(rootProtoView);
|
||||||
@ -316,17 +352,21 @@ export function main() {
|
|||||||
var mainProtoView =
|
var mainProtoView =
|
||||||
createProtoView([createComponentElementBinder(directiveResolver, NestedComponent)]);
|
createProtoView([createComponentElementBinder(directiveResolver, NestedComponent)]);
|
||||||
var nestedProtoView = createProtoView();
|
var nestedProtoView = createProtoView();
|
||||||
var compiler = createCompiler(
|
var renderPvDtos = [
|
||||||
[
|
createRenderProtoView([createRenderComponentElementBinder(0)]),
|
||||||
createRenderProtoView([createRenderComponentElementBinder(0)]),
|
createRenderProtoView()
|
||||||
createRenderProtoView()
|
];
|
||||||
],
|
var compiler =
|
||||||
[[rootProtoView], [mainProtoView], [nestedProtoView]]);
|
createCompiler(renderPvDtos, [rootProtoView, mainProtoView, nestedProtoView]);
|
||||||
compiler.compileInHost(MainComponent)
|
compiler.compileInHost(MainComponent)
|
||||||
.then((protoViewRef) => {
|
.then((protoViewRef) => {
|
||||||
expect(internalProtoView(protoViewRef).elementBinders[0].nestedProtoView)
|
expect(internalProtoView(protoViewRef).elementBinders[0].nestedProtoView)
|
||||||
.toBe(mainProtoView);
|
.toBe(mainProtoView);
|
||||||
|
expect(originalRenderProtoViewRefs(mainProtoView))
|
||||||
|
.toEqual([renderPvDtos[0].render, renderPvDtos[1].render]);
|
||||||
expect(mainProtoView.elementBinders[0].nestedProtoView).toBe(nestedProtoView);
|
expect(mainProtoView.elementBinders[0].nestedProtoView).toBe(nestedProtoView);
|
||||||
|
expect(originalRenderProtoViewRefs(nestedProtoView))
|
||||||
|
.toEqual([renderPvDtos[1].render]);
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
@ -334,25 +374,40 @@ export function main() {
|
|||||||
it('should load nested components in viewcontainers', inject([AsyncTestCompleter], (async) => {
|
it('should load nested components in viewcontainers', inject([AsyncTestCompleter], (async) => {
|
||||||
tplResolver.setView(MainComponent, new viewAnn.View({template: '<div></div>'}));
|
tplResolver.setView(MainComponent, new viewAnn.View({template: '<div></div>'}));
|
||||||
tplResolver.setView(NestedComponent, new viewAnn.View({template: '<div></div>'}));
|
tplResolver.setView(NestedComponent, new viewAnn.View({template: '<div></div>'}));
|
||||||
var mainProtoView = createProtoView([createViewportElementBinder(null)]);
|
|
||||||
var viewportProtoView =
|
var viewportProtoView =
|
||||||
createProtoView([createComponentElementBinder(directiveResolver, NestedComponent)]);
|
createProtoView([createComponentElementBinder(directiveResolver, NestedComponent)]);
|
||||||
|
var mainProtoView = createProtoView([createViewportElementBinder(viewportProtoView)]);
|
||||||
var nestedProtoView = createProtoView();
|
var nestedProtoView = createProtoView();
|
||||||
var compiler = createCompiler(
|
var renderPvDtos = [
|
||||||
[
|
createRenderProtoView([
|
||||||
createRenderProtoView([
|
createRenderViewportElementBinder(createRenderProtoView(
|
||||||
createRenderViewportElementBinder(createRenderProtoView(
|
[createRenderComponentElementBinder(0)], renderApi.ViewType.EMBEDDED))
|
||||||
[createRenderComponentElementBinder(0)], renderApi.ViewType.EMBEDDED))
|
]),
|
||||||
]),
|
createRenderProtoView()
|
||||||
createRenderProtoView()
|
];
|
||||||
],
|
var compiler =
|
||||||
[[rootProtoView], [mainProtoView, viewportProtoView], [nestedProtoView]]);
|
createCompiler(renderPvDtos, [rootProtoView, mainProtoView, nestedProtoView]);
|
||||||
compiler.compileInHost(MainComponent)
|
compiler.compileInHost(MainComponent)
|
||||||
.then((protoViewRef) => {
|
.then((protoViewRef) => {
|
||||||
expect(internalProtoView(protoViewRef).elementBinders[0].nestedProtoView)
|
expect(internalProtoView(protoViewRef).elementBinders[0].nestedProtoView)
|
||||||
.toBe(mainProtoView);
|
.toBe(mainProtoView);
|
||||||
|
expect(originalRenderProtoViewRefs(mainProtoView))
|
||||||
|
.toEqual([
|
||||||
|
renderPvDtos[0]
|
||||||
|
.render,
|
||||||
|
renderPvDtos[0].elementBinders[0].nestedProtoView.render,
|
||||||
|
renderPvDtos[1].render
|
||||||
|
]);
|
||||||
expect(viewportProtoView.elementBinders[0].nestedProtoView).toBe(nestedProtoView);
|
expect(viewportProtoView.elementBinders[0].nestedProtoView).toBe(nestedProtoView);
|
||||||
|
expect(originalRenderProtoViewRefs(viewportProtoView))
|
||||||
|
.toEqual([
|
||||||
|
renderPvDtos[0]
|
||||||
|
.elementBinders[0]
|
||||||
|
.nestedProtoView.render,
|
||||||
|
renderPvDtos[1].render
|
||||||
|
]);
|
||||||
|
expect(originalRenderProtoViewRefs(nestedProtoView))
|
||||||
|
.toEqual([renderPvDtos[1].render]);
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
@ -360,7 +415,7 @@ export function main() {
|
|||||||
it('should cache compiled host components', inject([AsyncTestCompleter], (async) => {
|
it('should cache compiled host components', inject([AsyncTestCompleter], (async) => {
|
||||||
tplResolver.setView(MainComponent, new viewAnn.View({template: '<div></div>'}));
|
tplResolver.setView(MainComponent, new viewAnn.View({template: '<div></div>'}));
|
||||||
var mainPv = createProtoView();
|
var mainPv = createProtoView();
|
||||||
var compiler = createCompiler([createRenderProtoView()], [[rootProtoView], [mainPv]]);
|
var compiler = createCompiler([createRenderProtoView()], [rootProtoView, mainPv]);
|
||||||
compiler.compileInHost(MainComponent)
|
compiler.compileInHost(MainComponent)
|
||||||
.then((protoViewRef) => {
|
.then((protoViewRef) => {
|
||||||
expect(internalProtoView(protoViewRef).elementBinders[0].nestedProtoView)
|
expect(internalProtoView(protoViewRef).elementBinders[0].nestedProtoView)
|
||||||
@ -404,7 +459,7 @@ export function main() {
|
|||||||
var nestedPv = createProtoView([]);
|
var nestedPv = createProtoView([]);
|
||||||
var compiler = createCompiler(
|
var compiler = createCompiler(
|
||||||
[createRenderProtoView(), createRenderProtoView(), createRenderProtoView()],
|
[createRenderProtoView(), createRenderProtoView(), createRenderProtoView()],
|
||||||
[[rootProtoView], [mainPv], [nestedPv], [rootProtoView2], [mainPv]]);
|
[rootProtoView, mainPv, nestedPv, rootProtoView2, mainPv]);
|
||||||
compiler.compileInHost(MainComponent)
|
compiler.compileInHost(MainComponent)
|
||||||
.then((protoViewRef) => {
|
.then((protoViewRef) => {
|
||||||
expect(internalProtoView(protoViewRef)
|
expect(internalProtoView(protoViewRef)
|
||||||
@ -429,7 +484,7 @@ export function main() {
|
|||||||
var renderProtoViewCompleter = PromiseWrapper.completer();
|
var renderProtoViewCompleter = PromiseWrapper.completer();
|
||||||
var expectedProtoView = createProtoView();
|
var expectedProtoView = createProtoView();
|
||||||
var compiler = createCompiler([renderProtoViewCompleter.promise],
|
var compiler = createCompiler([renderProtoViewCompleter.promise],
|
||||||
[[rootProtoView], [rootProtoView], [expectedProtoView]]);
|
[rootProtoView, rootProtoView, expectedProtoView]);
|
||||||
var result = PromiseWrapper.all([
|
var result = PromiseWrapper.all([
|
||||||
compiler.compileInHost(MainComponent),
|
compiler.compileInHost(MainComponent),
|
||||||
compiler.compileInHost(MainComponent),
|
compiler.compileInHost(MainComponent),
|
||||||
@ -445,18 +500,55 @@ export function main() {
|
|||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should allow recursive components', inject([AsyncTestCompleter], (async) => {
|
it('should throw on unconditional recursive components',
|
||||||
|
inject([AsyncTestCompleter], (async) => {
|
||||||
tplResolver.setView(MainComponent, new viewAnn.View({template: '<div></div>'}));
|
tplResolver.setView(MainComponent, new viewAnn.View({template: '<div></div>'}));
|
||||||
var mainProtoView =
|
var mainProtoView =
|
||||||
createProtoView([createComponentElementBinder(directiveResolver, MainComponent)]);
|
createProtoView([createComponentElementBinder(directiveResolver, MainComponent)]);
|
||||||
var compiler =
|
var compiler =
|
||||||
createCompiler([createRenderProtoView([createRenderComponentElementBinder(0)])],
|
createCompiler([createRenderProtoView([createRenderComponentElementBinder(0)])],
|
||||||
[[rootProtoView], [mainProtoView]]);
|
[rootProtoView, mainProtoView]);
|
||||||
|
PromiseWrapper.catchError(compiler.compileInHost(MainComponent), (e) => {
|
||||||
|
expect(() => { throw e; })
|
||||||
|
.toThrowError(`Unconditional component cycle in ${stringify(MainComponent)}`);
|
||||||
|
async.done();
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should allow recursive components that are connected via an embedded ProtoView',
|
||||||
|
inject([AsyncTestCompleter], (async) => {
|
||||||
|
tplResolver.setView(MainComponent, new viewAnn.View({template: '<div></div>'}));
|
||||||
|
var viewportProtoView =
|
||||||
|
createProtoView([createComponentElementBinder(directiveResolver, MainComponent)]);
|
||||||
|
var mainProtoView = createProtoView([createViewportElementBinder(viewportProtoView)]);
|
||||||
|
var renderPvDtos = [
|
||||||
|
createRenderProtoView([
|
||||||
|
createRenderViewportElementBinder(createRenderProtoView(
|
||||||
|
[createRenderComponentElementBinder(0)], renderApi.ViewType.EMBEDDED))
|
||||||
|
]),
|
||||||
|
createRenderProtoView()
|
||||||
|
];
|
||||||
|
var compiler = createCompiler(renderPvDtos, [rootProtoView, mainProtoView]);
|
||||||
compiler.compileInHost(MainComponent)
|
compiler.compileInHost(MainComponent)
|
||||||
.then((protoViewRef) => {
|
.then((protoViewRef) => {
|
||||||
expect(internalProtoView(protoViewRef).elementBinders[0].nestedProtoView)
|
expect(internalProtoView(protoViewRef).elementBinders[0].nestedProtoView)
|
||||||
.toBe(mainProtoView);
|
.toBe(mainProtoView);
|
||||||
expect(mainProtoView.elementBinders[0].nestedProtoView).toBe(mainProtoView);
|
expect(mainProtoView.elementBinders[0]
|
||||||
|
.nestedProtoView.elementBinders[0]
|
||||||
|
.nestedProtoView)
|
||||||
|
.toBe(mainProtoView);
|
||||||
|
// In case of a cycle, don't merge the embedded proto views into the component!
|
||||||
|
expect(originalRenderProtoViewRefs(mainProtoView))
|
||||||
|
.toEqual([renderPvDtos[0].render, null]);
|
||||||
|
expect(originalRenderProtoViewRefs(viewportProtoView))
|
||||||
|
.toEqual([
|
||||||
|
renderPvDtos[0]
|
||||||
|
.elementBinders[0]
|
||||||
|
.nestedProtoView.render,
|
||||||
|
renderPvDtos[1].render,
|
||||||
|
null
|
||||||
|
]);
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
@ -466,8 +558,7 @@ export function main() {
|
|||||||
var rootProtoView =
|
var rootProtoView =
|
||||||
createProtoView([createComponentElementBinder(directiveResolver, MainComponent)]);
|
createProtoView([createComponentElementBinder(directiveResolver, MainComponent)]);
|
||||||
var mainProtoView = createProtoView();
|
var mainProtoView = createProtoView();
|
||||||
var compiler =
|
var compiler = createCompiler([createRenderProtoView()], [rootProtoView, mainProtoView]);
|
||||||
createCompiler([createRenderProtoView()], [[rootProtoView], [mainProtoView]]);
|
|
||||||
compiler.compileInHost(MainComponent)
|
compiler.compileInHost(MainComponent)
|
||||||
.then((protoViewRef) => {
|
.then((protoViewRef) => {
|
||||||
expect(internalProtoView(protoViewRef)).toBe(rootProtoView);
|
expect(internalProtoView(protoViewRef)).toBe(rootProtoView);
|
||||||
@ -491,7 +582,7 @@ function createDirectiveBinding(directiveResolver, type): DirectiveBinding {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createProtoView(elementBinders = null): AppProtoView {
|
function createProtoView(elementBinders = null): AppProtoView {
|
||||||
var pv = new AppProtoView(null, null, new Map(), null);
|
var pv = new AppProtoView(null, null, null, new Map(), null);
|
||||||
if (isBlank(elementBinders)) {
|
if (isBlank(elementBinders)) {
|
||||||
elementBinders = [];
|
elementBinders = [];
|
||||||
}
|
}
|
||||||
@ -518,7 +609,8 @@ function createRenderProtoView(elementBinders = null, type: renderApi.ViewType =
|
|||||||
if (isBlank(elementBinders)) {
|
if (isBlank(elementBinders)) {
|
||||||
elementBinders = [];
|
elementBinders = [];
|
||||||
}
|
}
|
||||||
return new renderApi.ProtoViewDto({elementBinders: elementBinders, type: type});
|
return new renderApi.ProtoViewDto(
|
||||||
|
{elementBinders: elementBinders, type: type, render: new renderApi.RenderProtoViewRef()});
|
||||||
}
|
}
|
||||||
|
|
||||||
function createRenderComponentElementBinder(directiveIndex): renderApi.ElementBinder {
|
function createRenderComponentElementBinder(directiveIndex): renderApi.ElementBinder {
|
||||||
@ -611,14 +703,22 @@ class FakeViewResolver extends ViewResolver {
|
|||||||
class FakeProtoViewFactory extends ProtoViewFactory {
|
class FakeProtoViewFactory extends ProtoViewFactory {
|
||||||
requests: List<List<any>>;
|
requests: List<List<any>>;
|
||||||
|
|
||||||
constructor(public results: List<List<AppProtoView>>) {
|
constructor(public results: List<AppProtoView>) {
|
||||||
super(null);
|
super(null);
|
||||||
this.requests = [];
|
this.requests = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
createAppProtoViews(componentBinding: DirectiveBinding, renderProtoView: renderApi.ProtoViewDto,
|
createAppProtoViews(componentBinding: DirectiveBinding, renderProtoView: renderApi.ProtoViewDto,
|
||||||
directives: List<DirectiveBinding>): List<AppProtoView> {
|
directives: List<DirectiveBinding>): AppProtoView {
|
||||||
this.requests.push([componentBinding, renderProtoView, directives]);
|
this.requests.push([componentBinding, renderProtoView, directives]);
|
||||||
return ListWrapper.removeAt(this.results, 0);
|
return ListWrapper.removeAt(this.results, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MergedRenderProtoViewRef extends renderApi.RenderProtoViewRef {
|
||||||
|
constructor(public originals: renderApi.RenderProtoViewRef[]) { super(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
function originalRenderProtoViewRefs(appProtoView: AppProtoView) {
|
||||||
|
return (<MergedRenderProtoViewRef>appProtoView.mergeMapping.renderProtoViewRef).originals;
|
||||||
|
}
|
@ -12,7 +12,6 @@ import {
|
|||||||
beforeEachBindings,
|
beforeEachBindings,
|
||||||
it,
|
it,
|
||||||
xit,
|
xit,
|
||||||
viewRootNodes,
|
|
||||||
TestComponentBuilder,
|
TestComponentBuilder,
|
||||||
RootTestComponent,
|
RootTestComponent,
|
||||||
inspectElement,
|
inspectElement,
|
||||||
@ -240,8 +239,7 @@ export function main() {
|
|||||||
|
|
||||||
componentRef.dispose();
|
componentRef.dispose();
|
||||||
|
|
||||||
expect(rootEl).toHaveText('');
|
expect(rootEl.parentNode).toBeFalsy();
|
||||||
expect(rootEl.parentNode).toBe(doc.body);
|
|
||||||
|
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
|
@ -51,17 +51,10 @@ import {QueryList} from 'angular2/src/core/compiler/query_list';
|
|||||||
@proxy
|
@proxy
|
||||||
@IMPLEMENTS(AppView)
|
@IMPLEMENTS(AppView)
|
||||||
class DummyView extends SpyObject {
|
class DummyView extends SpyObject {
|
||||||
componentChildViews;
|
|
||||||
changeDetector;
|
changeDetector;
|
||||||
elementRefs;
|
constructor() {
|
||||||
constructor(elementCount = 0) {
|
|
||||||
super(AppView);
|
super(AppView);
|
||||||
this.componentChildViews = [];
|
|
||||||
this.changeDetector = null;
|
this.changeDetector = null;
|
||||||
this.elementRefs = ListWrapper.createFixedSize(elementCount);
|
|
||||||
for (var i=0; i<elementCount; i++) {
|
|
||||||
this.elementRefs[i] = new DummyElementRef();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
noSuchMethod(m) { return super.noSuchMethod(m); }
|
noSuchMethod(m) { return super.noSuchMethod(m); }
|
||||||
}
|
}
|
||||||
@ -69,6 +62,7 @@ class DummyView extends SpyObject {
|
|||||||
@proxy
|
@proxy
|
||||||
@IMPLEMENTS(ElementRef)
|
@IMPLEMENTS(ElementRef)
|
||||||
class DummyElementRef extends SpyObject {
|
class DummyElementRef extends SpyObject {
|
||||||
|
boundElementIndex: number = 0;
|
||||||
constructor() { super(ElementRef); }
|
constructor() { super(ElementRef); }
|
||||||
noSuchMethod(m) { return super.noSuchMethod(m); }
|
noSuchMethod(m) { return super.noSuchMethod(m); }
|
||||||
}
|
}
|
||||||
@ -246,14 +240,14 @@ class TestNode extends TreeNode<TestNode> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
var defaultPreBuiltObjects = new PreBuiltObjects(null, <any>new DummyView(1), null);
|
var defaultPreBuiltObjects = new PreBuiltObjects(null, <any>new DummyView(), <any>new DummyElementRef(), null);
|
||||||
|
|
||||||
// An injector with more than 10 bindings will switch to the dynamic strategy
|
// An injector with more than 10 bindings will switch to the dynamic strategy
|
||||||
var dynamicBindings = [];
|
var dynamicBindings = [];
|
||||||
|
|
||||||
for (var i = 0; i < 20; i++) {
|
for (var i = 0; i < 20; i++) {
|
||||||
dynamicBindings.push(bind(i).toValue(i));
|
dynamicBindings.push(bind(i).toValue(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
function createPei(parent, index, bindings, distance = 1, hasShadowRoot = false, dirVariableBindings = null) {
|
function createPei(parent, index, bindings, distance = 1, hasShadowRoot = false, dirVariableBindings = null) {
|
||||||
var directiveBinding = ListWrapper.map(bindings, b => {
|
var directiveBinding = ListWrapper.map(bindings, b => {
|
||||||
@ -754,9 +748,9 @@ export function main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should instantiate directives that depend on pre built objects", () => {
|
it("should instantiate directives that depend on pre built objects", () => {
|
||||||
var protoView = new AppProtoView(null, null, null, null);
|
var protoView = new AppProtoView(null, null, null, null, null);
|
||||||
var bindings = ListWrapper.concat([NeedsProtoViewRef], extraBindings);
|
var bindings = ListWrapper.concat([NeedsProtoViewRef], extraBindings);
|
||||||
var inj = injector(bindings, null, false, new PreBuiltObjects(null, null, protoView));
|
var inj = injector(bindings, null, false, new PreBuiltObjects(null, null, null, protoView));
|
||||||
|
|
||||||
expect(inj.get(NeedsProtoViewRef).protoViewRef).toEqual(new ProtoViewRef(protoView));
|
expect(inj.get(NeedsProtoViewRef).protoViewRef).toEqual(new ProtoViewRef(protoView));
|
||||||
});
|
});
|
||||||
@ -947,7 +941,7 @@ export function main() {
|
|||||||
describe("refs", () => {
|
describe("refs", () => {
|
||||||
it("should inject ElementRef", () => {
|
it("should inject ElementRef", () => {
|
||||||
var inj = injector(ListWrapper.concat([NeedsElementRef], extraBindings));
|
var inj = injector(ListWrapper.concat([NeedsElementRef], extraBindings));
|
||||||
expect(inj.get(NeedsElementRef).elementRef).toBe(defaultPreBuiltObjects.view.elementRefs[0]);
|
expect(inj.get(NeedsElementRef).elementRef).toBe(defaultPreBuiltObjects.elementRef);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should inject ChangeDetectorRef of the component's view into the component", () => {
|
it("should inject ChangeDetectorRef of the component's view into the component", () => {
|
||||||
@ -955,10 +949,10 @@ export function main() {
|
|||||||
var view = <any>new DummyView();
|
var view = <any>new DummyView();
|
||||||
var childView = new DummyView();
|
var childView = new DummyView();
|
||||||
childView.changeDetector = cd;
|
childView.changeDetector = cd;
|
||||||
view.componentChildViews = [childView];
|
view.spy('getNestedView').andReturn(childView);
|
||||||
var binding = DirectiveBinding.createFromType(ComponentNeedsChangeDetectorRef, new dirAnn.Component());
|
var binding = DirectiveBinding.createFromType(ComponentNeedsChangeDetectorRef, new dirAnn.Component());
|
||||||
var inj = injector(ListWrapper.concat([binding], extraBindings), null, true,
|
var inj = injector(ListWrapper.concat([binding], extraBindings), null, true,
|
||||||
new PreBuiltObjects(null, view, null));
|
new PreBuiltObjects(null, view, <any>new DummyElementRef(), null));
|
||||||
|
|
||||||
expect(inj.get(ComponentNeedsChangeDetectorRef).changeDetectorRef).toBe(cd.ref);
|
expect(inj.get(ComponentNeedsChangeDetectorRef).changeDetectorRef).toBe(cd.ref);
|
||||||
});
|
});
|
||||||
@ -969,7 +963,7 @@ export function main() {
|
|||||||
view.changeDetector =cd;
|
view.changeDetector =cd;
|
||||||
var binding = DirectiveBinding.createFromType(DirectiveNeedsChangeDetectorRef, new dirAnn.Directive());
|
var binding = DirectiveBinding.createFromType(DirectiveNeedsChangeDetectorRef, new dirAnn.Directive());
|
||||||
var inj = injector(ListWrapper.concat([binding], extraBindings), null, false,
|
var inj = injector(ListWrapper.concat([binding], extraBindings), null, false,
|
||||||
new PreBuiltObjects(null, view, null));
|
new PreBuiltObjects(null, view, <any>new DummyElementRef(), null));
|
||||||
|
|
||||||
expect(inj.get(DirectiveNeedsChangeDetectorRef).changeDetectorRef).toBe(cd.ref);
|
expect(inj.get(DirectiveNeedsChangeDetectorRef).changeDetectorRef).toBe(cd.ref);
|
||||||
});
|
});
|
||||||
@ -980,9 +974,9 @@ export function main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should inject ProtoViewRef", () => {
|
it("should inject ProtoViewRef", () => {
|
||||||
var protoView = new AppProtoView(null, null, null, null);
|
var protoView = new AppProtoView(null, null, null, null, null);
|
||||||
var inj = injector(ListWrapper.concat([NeedsProtoViewRef], extraBindings), null, false,
|
var inj = injector(ListWrapper.concat([NeedsProtoViewRef], extraBindings), null, false,
|
||||||
new PreBuiltObjects(null, null, protoView));
|
new PreBuiltObjects(null, null, null, protoView));
|
||||||
|
|
||||||
expect(inj.get(NeedsProtoViewRef).protoViewRef).toEqual(new ProtoViewRef(protoView));
|
expect(inj.get(NeedsProtoViewRef).protoViewRef).toEqual(new ProtoViewRef(protoView));
|
||||||
});
|
});
|
||||||
@ -1071,7 +1065,7 @@ export function main() {
|
|||||||
var inj = injector(ListWrapper.concat(dirs, extraBindings), null,
|
var inj = injector(ListWrapper.concat(dirs, extraBindings), null,
|
||||||
false, preBuildObjects, null, dirVariableBindings);
|
false, preBuildObjects, null, dirVariableBindings);
|
||||||
|
|
||||||
expect(inj.get(NeedsQueryByVarBindings).query.first).toBe(defaultPreBuiltObjects.view.elementRefs[0]);
|
expect(inj.get(NeedsQueryByVarBindings).query.first).toBe(defaultPreBuiltObjects.elementRef);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should contain directives on the same injector when querying by variable bindings' +
|
it('should contain directives on the same injector when querying by variable bindings' +
|
||||||
@ -1198,7 +1192,7 @@ export function main() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class ContextWithHandler {
|
class ContextWithHandler {
|
||||||
|
@ -15,7 +15,8 @@ import {
|
|||||||
xit,
|
xit,
|
||||||
containsRegexp,
|
containsRegexp,
|
||||||
stringifyElement,
|
stringifyElement,
|
||||||
TestComponentBuilder
|
TestComponentBuilder,
|
||||||
|
fakeAsync
|
||||||
} from 'angular2/test_lib';
|
} from 'angular2/test_lib';
|
||||||
|
|
||||||
|
|
||||||
@ -1369,8 +1370,8 @@ class SimpleImperativeViewComponent {
|
|||||||
done;
|
done;
|
||||||
|
|
||||||
constructor(self: ElementRef, viewManager: AppViewManager, renderer: DomRenderer) {
|
constructor(self: ElementRef, viewManager: AppViewManager, renderer: DomRenderer) {
|
||||||
var shadowViewRef = viewManager.getComponentView(self);
|
var hostElement = renderer.getNativeElementSync(self);
|
||||||
renderer.setComponentViewRootNodes(shadowViewRef.render, [el('hello imp view')]);
|
DOM.appendChild(hostElement, el('hello imp view'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1870,7 +1871,7 @@ class SomeImperativeViewport {
|
|||||||
}
|
}
|
||||||
if (value) {
|
if (value) {
|
||||||
this.view = this.vc.create(this.protoView);
|
this.view = this.vc.create(this.protoView);
|
||||||
var nodes = this.renderer.getRootNodes(this.view.render);
|
var nodes = this.renderer.getRootNodes(this.view.renderFragment);
|
||||||
for (var i = 0; i < nodes.length; i++) {
|
for (var i = 0; i < nodes.length; i++) {
|
||||||
DOM.appendChild(this.anchor, nodes[i]);
|
DOM.appendChild(this.anchor, nodes[i]);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,492 @@
|
|||||||
|
import {
|
||||||
|
AsyncTestCompleter,
|
||||||
|
beforeEach,
|
||||||
|
ddescribe,
|
||||||
|
xdescribe,
|
||||||
|
describe,
|
||||||
|
el,
|
||||||
|
dispatchEvent,
|
||||||
|
expect,
|
||||||
|
iit,
|
||||||
|
inject,
|
||||||
|
IS_DARTIUM,
|
||||||
|
beforeEachBindings,
|
||||||
|
it,
|
||||||
|
xit,
|
||||||
|
containsRegexp,
|
||||||
|
stringifyElement,
|
||||||
|
TestComponentBuilder,
|
||||||
|
RootTestComponent,
|
||||||
|
fakeAsync,
|
||||||
|
tick,
|
||||||
|
By
|
||||||
|
} from 'angular2/test_lib';
|
||||||
|
|
||||||
|
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||||
|
|
||||||
|
import * as viewAnn from 'angular2/src/core/annotations_impl/view';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Component,
|
||||||
|
Directive,
|
||||||
|
View,
|
||||||
|
forwardRef,
|
||||||
|
ViewContainerRef,
|
||||||
|
ProtoViewRef,
|
||||||
|
ElementRef,
|
||||||
|
bind
|
||||||
|
} from 'angular2/angular2';
|
||||||
|
import {ShadowDomStrategy, NativeShadowDomStrategy} from 'angular2/render';
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
describe('projection', () => {
|
||||||
|
it('should support simple components',
|
||||||
|
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||||
|
tcb.overrideView(MainComp, new viewAnn.View({
|
||||||
|
template: '<simple>' +
|
||||||
|
'<div>A</div>' +
|
||||||
|
'</simple>',
|
||||||
|
directives: [Simple]
|
||||||
|
}))
|
||||||
|
.createAsync(MainComp)
|
||||||
|
.then((main) => {
|
||||||
|
expect(main.nativeElement).toHaveText('SIMPLE(A)');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should support simple components with text interpolation as direct children',
|
||||||
|
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||||
|
tcb.overrideView(MainComp, new viewAnn.View({
|
||||||
|
template: '{{\'START(\'}}<simple>' +
|
||||||
|
'{{text}}' +
|
||||||
|
'</simple>{{\')END\'}}',
|
||||||
|
directives: [Simple]
|
||||||
|
}))
|
||||||
|
.createAsync(MainComp)
|
||||||
|
.then((main) => {
|
||||||
|
|
||||||
|
main.componentInstance.text = 'A';
|
||||||
|
main.detectChanges();
|
||||||
|
expect(main.nativeElement).toHaveText('START(SIMPLE(A))END');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not show the light dom even if there is no content tag',
|
||||||
|
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||||
|
tcb.overrideView(MainComp,
|
||||||
|
new viewAnn.View({template: '<empty>A</empty>', directives: [Empty]}))
|
||||||
|
.createAsync(MainComp)
|
||||||
|
.then((main) => {
|
||||||
|
|
||||||
|
expect(main.nativeElement).toHaveText('');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should support multiple content tags',
|
||||||
|
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||||
|
tcb.overrideView(MainComp, new viewAnn.View({
|
||||||
|
template: '<multiple-content-tags>' +
|
||||||
|
'<div>B</div>' +
|
||||||
|
'<div>C</div>' +
|
||||||
|
'<div class="left">A</div>' +
|
||||||
|
'</multiple-content-tags>',
|
||||||
|
directives: [MultipleContentTagsComponent]
|
||||||
|
}))
|
||||||
|
.createAsync(MainComp)
|
||||||
|
.then((main) => {
|
||||||
|
|
||||||
|
expect(main.nativeElement).toHaveText('(A, BC)');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should redistribute only direct children',
|
||||||
|
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||||
|
tcb.overrideView(MainComp, new viewAnn.View({
|
||||||
|
template: '<multiple-content-tags>' +
|
||||||
|
'<div>B<div class="left">A</div></div>' +
|
||||||
|
'<div>C</div>' +
|
||||||
|
'</multiple-content-tags>',
|
||||||
|
directives: [MultipleContentTagsComponent]
|
||||||
|
}))
|
||||||
|
.createAsync(MainComp)
|
||||||
|
.then((main) => {
|
||||||
|
|
||||||
|
expect(main.nativeElement).toHaveText('(, BAC)');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it("should redistribute direct child viewcontainers when the light dom changes",
|
||||||
|
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||||
|
tcb.overrideView(MainComp, new viewAnn.View({
|
||||||
|
template: '<multiple-content-tags>' +
|
||||||
|
'<template manual class="left"><div>A1</div></template>' +
|
||||||
|
'<div>B</div>' +
|
||||||
|
'</multiple-content-tags>',
|
||||||
|
directives: [MultipleContentTagsComponent, ManualViewportDirective]
|
||||||
|
}))
|
||||||
|
.createAsync(MainComp)
|
||||||
|
.then((main) => {
|
||||||
|
|
||||||
|
var viewportDirectives = main.queryAll(By.directive(ManualViewportDirective))
|
||||||
|
.map(de => de.inject(ManualViewportDirective));
|
||||||
|
|
||||||
|
expect(main.nativeElement).toHaveText('(, B)');
|
||||||
|
viewportDirectives.forEach(d => d.show());
|
||||||
|
expect(main.nativeElement).toHaveText('(A1, B)');
|
||||||
|
|
||||||
|
viewportDirectives.forEach(d => d.hide());
|
||||||
|
|
||||||
|
expect(main.nativeElement).toHaveText('(, B)');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it("should support nested components",
|
||||||
|
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||||
|
tcb.overrideView(MainComp, new viewAnn.View({
|
||||||
|
template: '<outer-with-indirect-nested>' +
|
||||||
|
'<div>A</div>' +
|
||||||
|
'<div>B</div>' +
|
||||||
|
'</outer-with-indirect-nested>',
|
||||||
|
directives: [OuterWithIndirectNestedComponent]
|
||||||
|
}))
|
||||||
|
.createAsync(MainComp)
|
||||||
|
.then((main) => {
|
||||||
|
|
||||||
|
expect(main.nativeElement).toHaveText('OUTER(SIMPLE(AB))');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it("should support nesting with content being direct child of a nested component",
|
||||||
|
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||||
|
tcb.overrideView(MainComp, new viewAnn.View({
|
||||||
|
template: '<outer>' +
|
||||||
|
'<template manual class="left"><div>A</div></template>' +
|
||||||
|
'<div>B</div>' +
|
||||||
|
'<div>C</div>' +
|
||||||
|
'</outer>',
|
||||||
|
directives: [OuterComponent, ManualViewportDirective],
|
||||||
|
}))
|
||||||
|
.createAsync(MainComp)
|
||||||
|
.then((main) => {
|
||||||
|
|
||||||
|
var viewportDirective = main.query(By.directive(ManualViewportDirective))
|
||||||
|
.inject(ManualViewportDirective);
|
||||||
|
|
||||||
|
expect(main.nativeElement).toHaveText('OUTER(INNER(INNERINNER(,BC)))');
|
||||||
|
viewportDirective.show();
|
||||||
|
|
||||||
|
expect(main.nativeElement).toHaveText('OUTER(INNER(INNERINNER(A,BC)))');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should redistribute when the shadow dom changes',
|
||||||
|
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||||
|
tcb.overrideView(MainComp, new viewAnn.View({
|
||||||
|
template: '<conditional-content>' +
|
||||||
|
'<div class="left">A</div>' +
|
||||||
|
'<div>B</div>' +
|
||||||
|
'<div>C</div>' +
|
||||||
|
'</conditional-content>',
|
||||||
|
directives: [ConditionalContentComponent]
|
||||||
|
}))
|
||||||
|
.createAsync(MainComp)
|
||||||
|
.then((main) => {
|
||||||
|
|
||||||
|
var viewportDirective = main.query(By.directive(ManualViewportDirective))
|
||||||
|
.inject(ManualViewportDirective);
|
||||||
|
|
||||||
|
expect(main.nativeElement).toHaveText('(, BC)');
|
||||||
|
|
||||||
|
viewportDirective.show();
|
||||||
|
expect(main.nativeElement).toHaveText('(A, BC)');
|
||||||
|
|
||||||
|
viewportDirective.hide();
|
||||||
|
|
||||||
|
expect(main.nativeElement).toHaveText('(, BC)');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
// GH-2095 - https://github.com/angular/angular/issues/2095
|
||||||
|
// important as we are removing the ng-content element during compilation,
|
||||||
|
// which could skrew up text node indices.
|
||||||
|
it('should support text nodes after content tags',
|
||||||
|
inject([TestComponentBuilder, AsyncTestCompleter], (tcb, async) => {
|
||||||
|
|
||||||
|
tcb.overrideView(
|
||||||
|
MainComp,
|
||||||
|
new viewAnn.View(
|
||||||
|
{template: '<simple string-prop="text"></simple>', directives: [Simple]}))
|
||||||
|
.overrideTemplate(Simple, '<ng-content></ng-content><p>P,</p>{{stringProp}}')
|
||||||
|
.createAsync(MainComp)
|
||||||
|
.then((main) => {
|
||||||
|
|
||||||
|
main.detectChanges();
|
||||||
|
|
||||||
|
expect(main.nativeElement).toHaveText('P,text');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
|
||||||
|
}));
|
||||||
|
|
||||||
|
// important as we are moving style tags around during compilation,
|
||||||
|
// which could skrew up text node indices.
|
||||||
|
it('should support text nodes after style tags',
|
||||||
|
inject([TestComponentBuilder, AsyncTestCompleter], (tcb, async) => {
|
||||||
|
|
||||||
|
tcb.overrideView(
|
||||||
|
MainComp,
|
||||||
|
new viewAnn.View(
|
||||||
|
{template: '<simple string-prop="text"></simple>', directives: [Simple]}))
|
||||||
|
.overrideTemplate(Simple, '<style></style><p>P,</p>{{stringProp}}')
|
||||||
|
.createAsync(MainComp)
|
||||||
|
.then((main) => {
|
||||||
|
|
||||||
|
main.detectChanges();
|
||||||
|
expect(main.nativeElement).toHaveText('P,text');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should support moving non projected light dom around',
|
||||||
|
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||||
|
tcb.overrideView(MainComp, new viewAnn.View({
|
||||||
|
template: '<empty>' +
|
||||||
|
' <template manual><div>A</div></template>' +
|
||||||
|
'</empty>' +
|
||||||
|
'START(<div project></div>)END',
|
||||||
|
directives: [Empty, ProjectDirective, ManualViewportDirective],
|
||||||
|
}))
|
||||||
|
.createAsync(MainComp)
|
||||||
|
.then((main) => {
|
||||||
|
|
||||||
|
var sourceDirective: ManualViewportDirective =
|
||||||
|
main.query(By.directive(ManualViewportDirective))
|
||||||
|
.inject(ManualViewportDirective);
|
||||||
|
var projectDirective: ProjectDirective =
|
||||||
|
main.query(By.directive(ProjectDirective)).inject(ProjectDirective);
|
||||||
|
expect(main.nativeElement).toHaveText('START()END');
|
||||||
|
|
||||||
|
projectDirective.show(sourceDirective.protoViewRef, sourceDirective.elementRef);
|
||||||
|
expect(main.nativeElement).toHaveText('START(A)END');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should support moving projected light dom around',
|
||||||
|
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||||
|
tcb.overrideView(MainComp, new viewAnn.View({
|
||||||
|
template: '<simple><template manual><div>A</div></template></simple>' +
|
||||||
|
'START(<div project></div>)END',
|
||||||
|
directives: [Simple, ProjectDirective, ManualViewportDirective],
|
||||||
|
}))
|
||||||
|
.createAsync(MainComp)
|
||||||
|
.then((main) => {
|
||||||
|
|
||||||
|
var sourceDirective: ManualViewportDirective =
|
||||||
|
main.query(By.directive(ManualViewportDirective))
|
||||||
|
.inject(ManualViewportDirective);
|
||||||
|
var projectDirective: ProjectDirective =
|
||||||
|
main.query(By.directive(ProjectDirective)).inject(ProjectDirective);
|
||||||
|
expect(main.nativeElement).toHaveText('SIMPLE()START()END');
|
||||||
|
|
||||||
|
projectDirective.show(sourceDirective.protoViewRef, sourceDirective.elementRef);
|
||||||
|
expect(main.nativeElement).toHaveText('SIMPLE()START(A)END');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should support moving ng-content around',
|
||||||
|
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||||
|
tcb.overrideView(MainComp, new viewAnn.View({
|
||||||
|
template: '<conditional-content>' +
|
||||||
|
'<div class="left">A</div>' +
|
||||||
|
'<div>B</div>' +
|
||||||
|
'</conditional-content>' +
|
||||||
|
'START(<div project></div>)END',
|
||||||
|
directives:
|
||||||
|
[ConditionalContentComponent, ProjectDirective, ManualViewportDirective]
|
||||||
|
}))
|
||||||
|
.createAsync(MainComp)
|
||||||
|
.then((main) => {
|
||||||
|
|
||||||
|
var sourceDirective: ManualViewportDirective =
|
||||||
|
main.query(By.directive(ManualViewportDirective))
|
||||||
|
.inject(ManualViewportDirective);
|
||||||
|
var projectDirective: ProjectDirective =
|
||||||
|
main.query(By.directive(ProjectDirective)).inject(ProjectDirective);
|
||||||
|
expect(main.nativeElement).toHaveText('(, B)START()END');
|
||||||
|
|
||||||
|
projectDirective.show(sourceDirective.protoViewRef, sourceDirective.elementRef);
|
||||||
|
expect(main.nativeElement).toHaveText('(, B)START(A)END');
|
||||||
|
|
||||||
|
// Stamping ng-content multiple times should not produce the content multiple
|
||||||
|
// times...
|
||||||
|
projectDirective.show(sourceDirective.protoViewRef, sourceDirective.elementRef);
|
||||||
|
expect(main.nativeElement).toHaveText('(, B)START(A)END');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
// Note: This does not use a ng-content element, but
|
||||||
|
// is still important as we are merging proto views independent of
|
||||||
|
// the presence of ng-content elements!
|
||||||
|
it('should still allow to implement a recursive trees',
|
||||||
|
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||||
|
tcb.overrideView(MainComp,
|
||||||
|
new viewAnn.View({template: '<tree></tree>', directives: [Tree]}))
|
||||||
|
.createAsync(MainComp)
|
||||||
|
.then((main) => {
|
||||||
|
|
||||||
|
main.detectChanges();
|
||||||
|
var manualDirective: ManualViewportDirective =
|
||||||
|
main.query(By.directive(ManualViewportDirective))
|
||||||
|
.inject(ManualViewportDirective);
|
||||||
|
expect(main.nativeElement).toHaveText('TREE(0:)');
|
||||||
|
manualDirective.show();
|
||||||
|
main.detectChanges();
|
||||||
|
expect(main.nativeElement).toHaveText('TREE(0:TREE(1:))');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (DOM.supportsNativeShadowDOM()) {
|
||||||
|
describe('native shadow dom support', () => {
|
||||||
|
beforeEachBindings(
|
||||||
|
() => { return [bind(ShadowDomStrategy).toValue(new NativeShadowDomStrategy())]; });
|
||||||
|
|
||||||
|
it('should support native content projection',
|
||||||
|
inject([TestComponentBuilder, AsyncTestCompleter],
|
||||||
|
(tcb: TestComponentBuilder, async) => {
|
||||||
|
tcb.overrideView(MainComp, new viewAnn.View({
|
||||||
|
template: '<simple-native>' +
|
||||||
|
'<div>A</div>' +
|
||||||
|
'</simple-native>',
|
||||||
|
directives: [SimpleNative]
|
||||||
|
}))
|
||||||
|
.createAsync(MainComp)
|
||||||
|
.then((main) => {
|
||||||
|
|
||||||
|
expect(main.nativeElement).toHaveText('SIMPLE(A)');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'main'})
|
||||||
|
@View({template: '', directives: []})
|
||||||
|
class MainComp {
|
||||||
|
text: string = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'simple', properties: ['stringProp']})
|
||||||
|
@View({template: 'SIMPLE(<ng-content></ng-content>)', directives: []})
|
||||||
|
class Simple {
|
||||||
|
stringProp: string = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'simple-native'})
|
||||||
|
@View({template: 'SIMPLE(<content></content>)', directives: []})
|
||||||
|
class SimpleNative {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'empty'})
|
||||||
|
@View({template: '', directives: []})
|
||||||
|
class Empty {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'multiple-content-tags'})
|
||||||
|
@View({
|
||||||
|
template: '(<ng-content select=".left"></ng-content>, <ng-content></ng-content>)',
|
||||||
|
directives: []
|
||||||
|
})
|
||||||
|
class MultipleContentTagsComponent {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Directive({selector: '[manual]'})
|
||||||
|
class ManualViewportDirective {
|
||||||
|
constructor(public vc: ViewContainerRef, public protoViewRef: ProtoViewRef,
|
||||||
|
public elementRef: ElementRef) {}
|
||||||
|
show() { this.vc.create(this.protoViewRef, 0); }
|
||||||
|
hide() { this.vc.clear(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Directive({selector: '[project]'})
|
||||||
|
class ProjectDirective {
|
||||||
|
constructor(public vc: ViewContainerRef) {}
|
||||||
|
show(protoViewRef: ProtoViewRef, context: ElementRef) {
|
||||||
|
this.vc.create(protoViewRef, 0, context);
|
||||||
|
}
|
||||||
|
hide() { this.vc.clear(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'outer-with-indirect-nested'})
|
||||||
|
@View({
|
||||||
|
template: 'OUTER(<simple><div><ng-content></ng-content></div></simple>)',
|
||||||
|
directives: [Simple]
|
||||||
|
})
|
||||||
|
class OuterWithIndirectNestedComponent {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'outer'})
|
||||||
|
@View({
|
||||||
|
template: 'OUTER(<inner><ng-content></ng-content></inner>)',
|
||||||
|
directives: [forwardRef(() => InnerComponent)]
|
||||||
|
})
|
||||||
|
class OuterComponent {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'inner'})
|
||||||
|
@View({
|
||||||
|
template: 'INNER(<innerinner><ng-content></ng-content></innerinner>)',
|
||||||
|
directives: [forwardRef(() => InnerInnerComponent)]
|
||||||
|
})
|
||||||
|
class InnerComponent {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'innerinner'})
|
||||||
|
@View({
|
||||||
|
template: 'INNERINNER(<ng-content select=".left"></ng-content>,<ng-content></ng-content>)',
|
||||||
|
directives: []
|
||||||
|
})
|
||||||
|
class InnerInnerComponent {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'conditional-content'})
|
||||||
|
@View({
|
||||||
|
template:
|
||||||
|
'<div>(<div *manual><ng-content select=".left"></ng-content></div>, <ng-content></ng-content>)</div>',
|
||||||
|
directives: [ManualViewportDirective]
|
||||||
|
})
|
||||||
|
class ConditionalContentComponent {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'tab'})
|
||||||
|
@View({
|
||||||
|
template: '<div><div *manual>TAB(<ng-content></ng-content>)</div></div>',
|
||||||
|
directives: [ManualViewportDirective]
|
||||||
|
})
|
||||||
|
class Tab {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'tree', properties: ['depth']})
|
||||||
|
@View({
|
||||||
|
template: 'TREE({{depth}}:<tree *manual [depth]="depth+1"></tree>)',
|
||||||
|
directives: [ManualViewportDirective, Tree]
|
||||||
|
})
|
||||||
|
class Tree {
|
||||||
|
depth = 0;
|
||||||
|
}
|
@ -35,7 +35,7 @@ export function main() {
|
|||||||
|
|
||||||
describe('ProtoViewFactory', () => {
|
describe('ProtoViewFactory', () => {
|
||||||
var changeDetection;
|
var changeDetection;
|
||||||
var protoViewFactory;
|
var protoViewFactory: ProtoViewFactory;
|
||||||
var directiveResolver;
|
var directiveResolver;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -63,10 +63,13 @@ export function main() {
|
|||||||
describe('createAppProtoViews', () => {
|
describe('createAppProtoViews', () => {
|
||||||
|
|
||||||
it('should create an AppProtoView for the root render proto view', () => {
|
it('should create an AppProtoView for the root render proto view', () => {
|
||||||
var renderPv = createRenderProtoView();
|
var varBindings = new Map();
|
||||||
var pvs = protoViewFactory.createAppProtoViews(bindDirective(MainComponent), renderPv, []);
|
varBindings.set('a', 'b');
|
||||||
expect(pvs.length).toBe(1);
|
var renderPv = createRenderProtoView([], null, varBindings);
|
||||||
expect(pvs[0].render).toBe(renderPv.render);
|
var appPv =
|
||||||
|
protoViewFactory.createAppProtoViews(bindDirective(MainComponent), renderPv, []);
|
||||||
|
expect(appPv.variableBindings.get('a')).toEqual('b');
|
||||||
|
expect(appPv).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -159,15 +162,23 @@ function directiveBinding({metadata}: {metadata?: any} = {}) {
|
|||||||
return new DirectiveBinding(Key.get("dummy"), null, [], [], [], metadata);
|
return new DirectiveBinding(Key.get("dummy"), null, [], [], [], metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createRenderProtoView(elementBinders = null, type: renderApi.ViewType = null) {
|
function createRenderProtoView(elementBinders = null, type: renderApi.ViewType = null,
|
||||||
|
variableBindings = null) {
|
||||||
if (isBlank(type)) {
|
if (isBlank(type)) {
|
||||||
type = renderApi.ViewType.COMPONENT;
|
type = renderApi.ViewType.COMPONENT;
|
||||||
}
|
}
|
||||||
if (isBlank(elementBinders)) {
|
if (isBlank(elementBinders)) {
|
||||||
elementBinders = [];
|
elementBinders = [];
|
||||||
}
|
}
|
||||||
return new renderApi.ProtoViewDto(
|
if (isBlank(variableBindings)) {
|
||||||
{elementBinders: elementBinders, type: type, variableBindings: new Map()});
|
variableBindings = new Map();
|
||||||
|
}
|
||||||
|
return new renderApi.ProtoViewDto({
|
||||||
|
elementBinders: elementBinders,
|
||||||
|
type: type,
|
||||||
|
variableBindings: variableBindings,
|
||||||
|
textBindings: []
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function createRenderComponentElementBinder(directiveIndex) {
|
function createRenderComponentElementBinder(directiveIndex) {
|
||||||
|
@ -398,7 +398,7 @@ class NeedsQueryDesc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Component({selector: 'needs-query-by-var-binding'})
|
@Component({selector: 'needs-query-by-var-binding'})
|
||||||
@View({directives: [], template: '<content>'})
|
@View({directives: [], template: '<ng-content>'})
|
||||||
@Injectable()
|
@Injectable()
|
||||||
class NeedsQueryByLabel {
|
class NeedsQueryByLabel {
|
||||||
query: QueryList<any>;
|
query: QueryList<any>;
|
||||||
@ -408,7 +408,7 @@ class NeedsQueryByLabel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Component({selector: 'needs-query-by-var-bindings'})
|
@Component({selector: 'needs-query-by-var-bindings'})
|
||||||
@View({directives: [], template: '<content>'})
|
@View({directives: [], template: '<ng-content>'})
|
||||||
@Injectable()
|
@Injectable()
|
||||||
class NeedsQueryByTwoLabels {
|
class NeedsQueryByTwoLabels {
|
||||||
query: QueryList<any>;
|
query: QueryList<any>;
|
||||||
|
@ -18,10 +18,11 @@ import {
|
|||||||
|
|
||||||
import {IMPLEMENTS} from 'angular2/src/facade/lang';
|
import {IMPLEMENTS} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
import {AppView, AppProtoView, AppViewContainer} from 'angular2/src/core/compiler/view';
|
import {AppView, AppViewContainer} from 'angular2/src/core/compiler/view';
|
||||||
import {ViewContainerRef} from 'angular2/src/core/compiler/view_container_ref';
|
import {ViewContainerRef} from 'angular2/src/core/compiler/view_container_ref';
|
||||||
import {AppViewManager} from 'angular2/src/core/compiler/view_manager';
|
import {AppViewManager} from 'angular2/src/core/compiler/view_manager';
|
||||||
import {ElementBinder} from 'angular2/src/core/compiler/element_binder';
|
import {ElementRef} from 'angular2/src/core/compiler/element_ref';
|
||||||
|
import {ViewRef} from 'angular2/src/core/compiler/view_ref';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
// TODO(tbosch): add missing tests
|
// TODO(tbosch): add missing tests
|
||||||
@ -31,34 +32,26 @@ export function main() {
|
|||||||
var view;
|
var view;
|
||||||
var viewManager;
|
var viewManager;
|
||||||
|
|
||||||
function createProtoView() {
|
|
||||||
var pv = new AppProtoView(null, null, null, null);
|
|
||||||
pv.elementBinders = [new ElementBinder(0, null, 0, null, null)];
|
|
||||||
return pv;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createView() { return new AppView(null, createProtoView(), new Map()); }
|
|
||||||
|
|
||||||
function createViewContainer() { return new ViewContainerRef(viewManager, location); }
|
function createViewContainer() { return new ViewContainerRef(viewManager, location); }
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
viewManager = new AppViewManagerSpy();
|
viewManager = new AppViewManagerSpy();
|
||||||
view = createView();
|
view = new AppViewSpy();
|
||||||
view.viewContainers = [null];
|
location = new ElementRef(new ViewRef(view), 0, 0, null);
|
||||||
location = view.elementRefs[0];
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('length', () => {
|
describe('length', () => {
|
||||||
|
|
||||||
it('should return a 0 length if there is no underlying ViewContainerRef', () => {
|
it('should return a 0 length if there is no underlying AppViewContainer', () => {
|
||||||
var vc = createViewContainer();
|
var vc = createViewContainer();
|
||||||
expect(vc.length).toBe(0);
|
expect(vc.length).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return the size of the underlying ViewContainerRef', () => {
|
it('should return the size of the underlying AppViewContainer', () => {
|
||||||
var vc = createViewContainer();
|
var vc = createViewContainer();
|
||||||
view.viewContainers = [new AppViewContainer()];
|
var appVc = new AppViewContainer();
|
||||||
view.viewContainers[0].views = [createView()];
|
view.viewContainers = [appVc];
|
||||||
|
appVc.views = [<any>new AppViewSpy()];
|
||||||
expect(vc.length).toBe(1);
|
expect(vc.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -69,6 +62,14 @@ export function main() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@proxy
|
||||||
|
@IMPLEMENTS(AppView)
|
||||||
|
class AppViewSpy extends SpyObject {
|
||||||
|
viewContainers: AppViewContainer[] = [null];
|
||||||
|
constructor() { super(AppView); }
|
||||||
|
noSuchMethod(m) { return super.noSuchMethod(m) }
|
||||||
|
}
|
||||||
|
|
||||||
@proxy
|
@proxy
|
||||||
@IMPLEMENTS(AppViewManager)
|
@IMPLEMENTS(AppViewManager)
|
||||||
class AppViewManagerSpy extends SpyObject {
|
class AppViewManagerSpy extends SpyObject {
|
||||||
|
@ -16,240 +16,155 @@ import {
|
|||||||
proxy
|
proxy
|
||||||
} from 'angular2/test_lib';
|
} from 'angular2/test_lib';
|
||||||
import {Injector, bind} from 'angular2/di';
|
import {Injector, bind} from 'angular2/di';
|
||||||
import {IMPLEMENTS, isBlank, isPresent} from 'angular2/src/facade/lang';
|
import {IMPLEMENTS} from 'angular2/src/facade/lang';
|
||||||
import {MapWrapper, ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
|
||||||
|
|
||||||
import {AppProtoView, AppView, AppViewContainer} from 'angular2/src/core/compiler/view';
|
import {
|
||||||
|
AppProtoView,
|
||||||
|
AppView,
|
||||||
|
AppViewContainer,
|
||||||
|
AppProtoViewMergeMapping
|
||||||
|
} from 'angular2/src/core/compiler/view';
|
||||||
import {ProtoViewRef, ViewRef, internalView} from 'angular2/src/core/compiler/view_ref';
|
import {ProtoViewRef, ViewRef, internalView} from 'angular2/src/core/compiler/view_ref';
|
||||||
import {Renderer, RenderViewRef, RenderProtoViewRef} from 'angular2/src/render/api';
|
import {ElementRef} from 'angular2/src/core/compiler/element_ref';
|
||||||
import {ElementBinder} from 'angular2/src/core/compiler/element_binder';
|
import {
|
||||||
import {DirectiveBinding, ElementInjector} from 'angular2/src/core/compiler/element_injector';
|
Renderer,
|
||||||
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
|
RenderViewRef,
|
||||||
import {Component} from 'angular2/annotations';
|
RenderProtoViewRef,
|
||||||
|
RenderFragmentRef,
|
||||||
|
ViewType,
|
||||||
|
RenderProtoViewMergeMapping,
|
||||||
|
RenderViewWithFragments
|
||||||
|
} from 'angular2/src/render/api';
|
||||||
import {AppViewManager} from 'angular2/src/core/compiler/view_manager';
|
import {AppViewManager} from 'angular2/src/core/compiler/view_manager';
|
||||||
import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils';
|
import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils';
|
||||||
import {AppViewListener} from 'angular2/src/core/compiler/view_listener';
|
import {AppViewListener} from 'angular2/src/core/compiler/view_listener';
|
||||||
import {AppViewPool} from 'angular2/src/core/compiler/view_pool';
|
import {AppViewPool} from 'angular2/src/core/compiler/view_pool';
|
||||||
|
|
||||||
|
import {
|
||||||
|
createHostPv,
|
||||||
|
createComponentPv,
|
||||||
|
createEmbeddedPv,
|
||||||
|
createEmptyElBinder,
|
||||||
|
createNestedElBinder,
|
||||||
|
createProtoElInjector
|
||||||
|
} from './view_manager_utils_spec';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
// TODO(tbosch): add missing tests
|
// TODO(tbosch): add missing tests
|
||||||
|
|
||||||
describe('AppViewManager', () => {
|
describe('AppViewManager', () => {
|
||||||
var renderer;
|
var renderer;
|
||||||
var utils;
|
var utils: AppViewManagerUtils;
|
||||||
var viewListener;
|
var viewListener;
|
||||||
var viewPool;
|
var viewPool;
|
||||||
var manager;
|
var manager: AppViewManager;
|
||||||
var directiveResolver;
|
var createdRenderViews: RenderViewWithFragments[];
|
||||||
var createdViews: any[];
|
|
||||||
var createdRenderViews: any[];
|
|
||||||
|
|
||||||
function wrapPv(protoView: AppProtoView): ProtoViewRef { return new ProtoViewRef(protoView); }
|
function wrapPv(protoView: AppProtoView): ProtoViewRef { return new ProtoViewRef(protoView); }
|
||||||
|
|
||||||
function wrapView(view: AppView): ViewRef { return new ViewRef(view); }
|
function wrapView(view: AppView): ViewRef { return new ViewRef(view); }
|
||||||
|
|
||||||
function elementRef(parentView, boundElementIndex) {
|
function resetSpies() {
|
||||||
return parentView.elementRefs[boundElementIndex];
|
viewListener.spy('viewCreated').reset();
|
||||||
}
|
viewListener.spy('viewDestroyed').reset();
|
||||||
|
renderer.spy('createView').reset();
|
||||||
function createDirectiveBinding(type) {
|
renderer.spy('destroyView').reset();
|
||||||
var annotation = directiveResolver.resolve(type);
|
renderer.spy('createRootHostView').reset();
|
||||||
return DirectiveBinding.createFromType(type, annotation);
|
renderer.spy('setEventDispatcher').reset();
|
||||||
}
|
renderer.spy('hydrateView').reset();
|
||||||
|
renderer.spy('dehydrateView').reset();
|
||||||
function createEmptyElBinder() { return new ElementBinder(0, null, 0, null, null); }
|
viewPool.spy('returnView').reset();
|
||||||
|
|
||||||
function createComponentElBinder(nestedProtoView = null) {
|
|
||||||
var binding = createDirectiveBinding(SomeComponent);
|
|
||||||
var binder = new ElementBinder(0, null, 0, null, binding);
|
|
||||||
binder.nestedProtoView = nestedProtoView;
|
|
||||||
return binder;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createProtoView(binders = null) {
|
|
||||||
if (isBlank(binders)) {
|
|
||||||
binders = [];
|
|
||||||
}
|
|
||||||
var staticChildComponentCount = 0;
|
|
||||||
for (var i = 0; i < binders.length; i++) {
|
|
||||||
if (binders[i].hasStaticComponent()) {
|
|
||||||
staticChildComponentCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var res = new AppProtoView(new MockProtoViewRef(staticChildComponentCount), null, null, null);
|
|
||||||
res.elementBinders = binders;
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createElementInjector() {
|
|
||||||
return SpyObject.stub(new SpyElementInjector(),
|
|
||||||
{
|
|
||||||
'isExportingComponent': false,
|
|
||||||
'isExportingElement': false,
|
|
||||||
'getEventEmitterAccessors': [],
|
|
||||||
'getComponent': null
|
|
||||||
},
|
|
||||||
{});
|
|
||||||
}
|
|
||||||
|
|
||||||
function createView(pv = null, renderViewRef = null) {
|
|
||||||
if (isBlank(pv)) {
|
|
||||||
pv = createProtoView();
|
|
||||||
}
|
|
||||||
if (isBlank(renderViewRef)) {
|
|
||||||
renderViewRef = new RenderViewRef();
|
|
||||||
}
|
|
||||||
var view = new AppView(renderer, pv, new Map());
|
|
||||||
view.render = renderViewRef;
|
|
||||||
var elementInjectors = ListWrapper.createFixedSize(pv.elementBinders.length);
|
|
||||||
for (var i = 0; i < pv.elementBinders.length; i++) {
|
|
||||||
elementInjectors[i] = createElementInjector();
|
|
||||||
}
|
|
||||||
view.init(null, elementInjectors, [], ListWrapper.createFixedSize(pv.elementBinders.length),
|
|
||||||
ListWrapper.createFixedSize(pv.elementBinders.length));
|
|
||||||
return view;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
directiveResolver = new DirectiveResolver();
|
|
||||||
renderer = new SpyRenderer();
|
renderer = new SpyRenderer();
|
||||||
utils = new SpyAppViewManagerUtils();
|
utils = new AppViewManagerUtils();
|
||||||
viewListener = new SpyAppViewListener();
|
viewListener = new SpyAppViewListener();
|
||||||
viewPool = new SpyAppViewPool();
|
viewPool = new SpyAppViewPool();
|
||||||
manager = new AppViewManager(viewPool, viewListener, utils, renderer);
|
manager = new AppViewManager(viewPool, viewListener, utils, renderer);
|
||||||
createdViews = [];
|
|
||||||
createdRenderViews = [];
|
createdRenderViews = [];
|
||||||
|
|
||||||
utils.spy('createView')
|
|
||||||
.andCallFake((proto, renderViewRef, _a, _b) => {
|
|
||||||
var view = createView(proto, renderViewRef);
|
|
||||||
createdViews.push(view);
|
|
||||||
return view;
|
|
||||||
});
|
|
||||||
utils.spy('attachComponentView')
|
|
||||||
.andCallFake((hostView, elementIndex, childView) => {
|
|
||||||
hostView.componentChildViews[elementIndex] = childView;
|
|
||||||
});
|
|
||||||
utils.spy('attachViewInContainer')
|
|
||||||
.andCallFake((parentView, elementIndex, _a, _b, atIndex, childView) => {
|
|
||||||
var viewContainer = parentView.viewContainers[elementIndex];
|
|
||||||
if (isBlank(viewContainer)) {
|
|
||||||
viewContainer = new AppViewContainer();
|
|
||||||
parentView.viewContainers[elementIndex] = viewContainer;
|
|
||||||
}
|
|
||||||
ListWrapper.insert(viewContainer.views, atIndex, childView);
|
|
||||||
});
|
|
||||||
renderer.spy('createRootHostView')
|
renderer.spy('createRootHostView')
|
||||||
.andCallFake((_b, _c) => {
|
.andCallFake((_a, renderFragmentCount, _b) => {
|
||||||
var rv = new RenderViewRef();
|
var fragments = [];
|
||||||
|
for (var i = 0; i < renderFragmentCount; i++) {
|
||||||
|
fragments.push(new RenderFragmentRef());
|
||||||
|
}
|
||||||
|
var rv = new RenderViewWithFragments(new RenderViewRef(), fragments);
|
||||||
createdRenderViews.push(rv);
|
createdRenderViews.push(rv);
|
||||||
return rv;
|
return rv;
|
||||||
});
|
});
|
||||||
renderer.spy('createView')
|
renderer.spy('createView')
|
||||||
.andCallFake((_a) => {
|
.andCallFake((_a, renderFragmentCount) => {
|
||||||
var rv = new RenderViewRef();
|
var fragments = [];
|
||||||
|
for (var i = 0; i < renderFragmentCount; i++) {
|
||||||
|
fragments.push(new RenderFragmentRef());
|
||||||
|
}
|
||||||
|
var rv = new RenderViewWithFragments(new RenderViewRef(), fragments);
|
||||||
createdRenderViews.push(rv);
|
createdRenderViews.push(rv);
|
||||||
return rv;
|
return rv;
|
||||||
});
|
});
|
||||||
viewPool.spy('returnView').andReturn(true);
|
viewPool.spy('returnView').andReturn(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('static child components', () => {
|
|
||||||
|
|
||||||
describe('recursively create when not cached', () => {
|
|
||||||
var rootProtoView, hostProtoView, componentProtoView, hostView, componentView;
|
|
||||||
beforeEach(() => {
|
|
||||||
componentProtoView = createProtoView();
|
|
||||||
hostProtoView = createProtoView([createComponentElBinder(componentProtoView)]);
|
|
||||||
rootProtoView = createProtoView([createComponentElBinder(hostProtoView)]);
|
|
||||||
manager.createRootHostView(wrapPv(rootProtoView), null, null);
|
|
||||||
hostView = createdViews[1];
|
|
||||||
componentView = createdViews[2];
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create the view', () => {
|
|
||||||
expect(hostView.proto).toBe(hostProtoView);
|
|
||||||
expect(componentView.proto).toBe(componentProtoView);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should hydrate the view', () => {
|
|
||||||
expect(utils.spy('hydrateComponentView')).toHaveBeenCalledWith(hostView, 0);
|
|
||||||
expect(renderer.spy('hydrateView')).toHaveBeenCalledWith(componentView.render);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set the render view',
|
|
||||||
() => {expect(componentView.render).toBe(createdRenderViews[2])});
|
|
||||||
|
|
||||||
it('should set the event dispatcher', () => {
|
|
||||||
expect(renderer.spy('setEventDispatcher'))
|
|
||||||
.toHaveBeenCalledWith(componentView.render, componentView);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('recursively hydrate when getting from from the cache',
|
|
||||||
() => {
|
|
||||||
// TODO(tbosch): implement this
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('recursively dehydrate', () => {
|
|
||||||
// TODO(tbosch): implement this
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('createRootHostView', () => {
|
describe('createRootHostView', () => {
|
||||||
|
|
||||||
var hostProtoView;
|
var hostProtoView: AppProtoView;
|
||||||
beforeEach(() => { hostProtoView = createProtoView([createComponentElBinder(null)]); });
|
beforeEach(
|
||||||
|
() => { hostProtoView = createHostPv([createNestedElBinder(createComponentPv())]); });
|
||||||
|
|
||||||
it('should create the view', () => {
|
it('should create the view', () => {
|
||||||
expect(internalView(manager.createRootHostView(wrapPv(hostProtoView), null, null)))
|
var rootView = internalView(manager.createRootHostView(wrapPv(hostProtoView), null, null));
|
||||||
.toBe(createdViews[0]);
|
expect(rootView.proto).toBe(hostProtoView);
|
||||||
expect(createdViews[0].proto).toBe(hostProtoView);
|
expect(viewListener.spy('viewCreated')).toHaveBeenCalledWith(rootView);
|
||||||
expect(viewListener.spy('viewCreated')).toHaveBeenCalledWith(createdViews[0]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should hydrate the view', () => {
|
it('should hydrate the view', () => {
|
||||||
var injector = Injector.resolveAndCreate([]);
|
var injector = Injector.resolveAndCreate([]);
|
||||||
manager.createRootHostView(wrapPv(hostProtoView), null, injector);
|
var rootView =
|
||||||
expect(utils.spy('hydrateRootHostView')).toHaveBeenCalledWith(createdViews[0], injector);
|
internalView(manager.createRootHostView(wrapPv(hostProtoView), null, injector));
|
||||||
expect(renderer.spy('hydrateView')).toHaveBeenCalledWith(createdViews[0].render);
|
expect(rootView.hydrated()).toBe(true);
|
||||||
|
expect(renderer.spy('hydrateView')).toHaveBeenCalledWith(rootView.render);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create and set the render view using the component selector', () => {
|
it('should create and set the render view using the component selector', () => {
|
||||||
manager.createRootHostView(wrapPv(hostProtoView), null, null)
|
var rootView = internalView(manager.createRootHostView(wrapPv(hostProtoView), null, null));
|
||||||
expect(renderer.spy('createRootHostView'))
|
expect(renderer.spy('createRootHostView'))
|
||||||
.toHaveBeenCalledWith(hostProtoView.render, 'someComponent');
|
.toHaveBeenCalledWith(hostProtoView.mergeMapping.renderProtoViewRef,
|
||||||
expect(createdViews[0].render).toBe(createdRenderViews[0]);
|
hostProtoView.mergeMapping.renderFragmentCount, 'someComponent');
|
||||||
|
expect(rootView.render).toBe(createdRenderViews[0].viewRef);
|
||||||
|
expect(rootView.renderFragment).toBe(createdRenderViews[0].fragmentRefs[0]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow to override the selector', () => {
|
it('should allow to override the selector', () => {
|
||||||
var selector = 'someOtherSelector';
|
var selector = 'someOtherSelector';
|
||||||
manager.createRootHostView(wrapPv(hostProtoView), selector, null)
|
internalView(manager.createRootHostView(wrapPv(hostProtoView), selector, null));
|
||||||
expect(renderer.spy('createRootHostView'))
|
expect(renderer.spy('createRootHostView'))
|
||||||
.toHaveBeenCalledWith(hostProtoView.render, selector);
|
.toHaveBeenCalledWith(hostProtoView.mergeMapping.renderProtoViewRef,
|
||||||
|
hostProtoView.mergeMapping.renderFragmentCount, selector);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set the event dispatcher', () => {
|
it('should set the event dispatcher', () => {
|
||||||
manager.createRootHostView(wrapPv(hostProtoView), null, null);
|
var rootView = internalView(manager.createRootHostView(wrapPv(hostProtoView), null, null));
|
||||||
var cmpView = createdViews[0];
|
expect(renderer.spy('setEventDispatcher')).toHaveBeenCalledWith(rootView.render, rootView);
|
||||||
expect(renderer.spy('setEventDispatcher')).toHaveBeenCalledWith(cmpView.render, cmpView);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('destroyRootHostView', () => {
|
describe('destroyRootHostView', () => {
|
||||||
var hostProtoView, hostView, hostRenderViewRef;
|
var hostProtoView: AppProtoView;
|
||||||
|
var hostView: AppView;
|
||||||
|
var hostRenderViewRef: RenderViewRef;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
hostProtoView = createProtoView([createComponentElBinder(null)]);
|
hostProtoView = createHostPv([createNestedElBinder(createComponentPv())]);
|
||||||
hostView = internalView(manager.createRootHostView(wrapPv(hostProtoView), null, null));
|
hostView = internalView(manager.createRootHostView(wrapPv(hostProtoView), null, null));
|
||||||
hostRenderViewRef = hostView.render;
|
hostRenderViewRef = hostView.render;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should dehydrate', () => {
|
it('should dehydrate', () => {
|
||||||
manager.destroyRootHostView(wrapView(hostView));
|
manager.destroyRootHostView(wrapView(hostView));
|
||||||
expect(utils.spy('dehydrateView')).toHaveBeenCalledWith(hostView);
|
expect(hostView.hydrated()).toBe(false);
|
||||||
expect(renderer.spy('dehydrateView')).toHaveBeenCalledWith(hostView.render);
|
expect(renderer.spy('dehydrateView')).toHaveBeenCalledWith(hostView.render);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -269,60 +184,120 @@ export function main() {
|
|||||||
describe('createViewInContainer', () => {
|
describe('createViewInContainer', () => {
|
||||||
|
|
||||||
describe('basic functionality', () => {
|
describe('basic functionality', () => {
|
||||||
var parentView, childProtoView;
|
var hostView: AppView;
|
||||||
|
var childProtoView: AppProtoView;
|
||||||
|
var vcRef: ElementRef;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
parentView = createView(createProtoView([createEmptyElBinder()]));
|
childProtoView = createEmbeddedPv();
|
||||||
childProtoView = createProtoView();
|
var hostProtoView = createHostPv(
|
||||||
|
[createNestedElBinder(createComponentPv([createNestedElBinder(childProtoView)]))]);
|
||||||
|
hostView = internalView(manager.createRootHostView(wrapPv(hostProtoView), null, null));
|
||||||
|
vcRef = hostView.elementRefs[1];
|
||||||
|
resetSpies();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create a ViewContainerRef if not yet existing', () => {
|
describe('create the first view', () => {
|
||||||
manager.createViewInContainer(elementRef(parentView, 0), 0, wrapPv(childProtoView), null);
|
|
||||||
expect(parentView.viewContainers[0]).toBeTruthy();
|
it('should create an AppViewContainer if not yet existing', () => {
|
||||||
|
manager.createViewInContainer(vcRef, 0, wrapPv(childProtoView), null);
|
||||||
|
expect(hostView.viewContainers[1]).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use an existing nested view', () => {
|
||||||
|
var childView =
|
||||||
|
internalView(manager.createViewInContainer(vcRef, 0, wrapPv(childProtoView), null));
|
||||||
|
expect(childView.proto).toBe(childProtoView);
|
||||||
|
expect(childView).toBe(hostView.views[2]);
|
||||||
|
expect(viewListener.spy('viewCreated')).not.toHaveBeenCalled();
|
||||||
|
expect(renderer.spy('createView')).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should attach the fragment', () => {
|
||||||
|
var childView =
|
||||||
|
internalView(manager.createViewInContainer(vcRef, 0, wrapPv(childProtoView), null));
|
||||||
|
expect(childView.proto).toBe(childProtoView);
|
||||||
|
expect(hostView.viewContainers[1].views.length).toBe(1);
|
||||||
|
expect(hostView.viewContainers[1].views[0]).toBe(childView);
|
||||||
|
expect(renderer.spy('attachFragmentAfterElement'))
|
||||||
|
.toHaveBeenCalledWith(vcRef, childView.renderFragment);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should hydrate the view but not the render view', () => {
|
||||||
|
var childView =
|
||||||
|
internalView(manager.createViewInContainer(vcRef, 0, wrapPv(childProtoView), null));
|
||||||
|
expect(childView.hydrated()).toBe(true);
|
||||||
|
expect(renderer.spy('hydrateView')).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not set the EventDispatcher', () => {
|
||||||
|
internalView(manager.createViewInContainer(vcRef, 0, wrapPv(childProtoView), null));
|
||||||
|
expect(renderer.spy('setEventDispatcher')).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create the view', () => {
|
describe('create the second view', () => {
|
||||||
expect(internalView(manager.createViewInContainer(elementRef(parentView, 0), 0,
|
var firstChildView;
|
||||||
wrapPv(childProtoView), null)))
|
beforeEach(() => {
|
||||||
.toBe(createdViews[0]);
|
firstChildView =
|
||||||
expect(createdViews[0].proto).toBe(childProtoView);
|
internalView(manager.createViewInContainer(vcRef, 0, wrapPv(childProtoView), null));
|
||||||
expect(viewListener.spy('viewCreated')).toHaveBeenCalledWith(createdViews[0]);
|
resetSpies();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a new view', () => {
|
||||||
|
var childView =
|
||||||
|
internalView(manager.createViewInContainer(vcRef, 1, wrapPv(childProtoView), null));
|
||||||
|
expect(childView.proto).toBe(childProtoView);
|
||||||
|
expect(childView).not.toBe(firstChildView);
|
||||||
|
expect(viewListener.spy('viewCreated')).toHaveBeenCalledWith(childView);
|
||||||
|
expect(renderer.spy('createView'))
|
||||||
|
.toHaveBeenCalledWith(childProtoView.mergeMapping.renderProtoViewRef,
|
||||||
|
childProtoView.mergeMapping.renderFragmentCount);
|
||||||
|
expect(childView.render).toBe(createdRenderViews[1].viewRef);
|
||||||
|
expect(childView.renderFragment).toBe(createdRenderViews[1].fragmentRefs[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should attach the fragment', () => {
|
||||||
|
var childView =
|
||||||
|
internalView(manager.createViewInContainer(vcRef, 1, wrapPv(childProtoView), null));
|
||||||
|
expect(childView.proto).toBe(childProtoView);
|
||||||
|
expect(hostView.viewContainers[1].views[1]).toBe(childView);
|
||||||
|
expect(renderer.spy('attachFragmentAfterFragment'))
|
||||||
|
.toHaveBeenCalledWith(firstChildView.renderFragment, childView.renderFragment);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should hydrate the view', () => {
|
||||||
|
var childView =
|
||||||
|
internalView(manager.createViewInContainer(vcRef, 1, wrapPv(childProtoView), null));
|
||||||
|
expect(childView.hydrated()).toBe(true);
|
||||||
|
expect(renderer.spy('hydrateView')).toHaveBeenCalledWith(childView.render);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the EventDispatcher', () => {
|
||||||
|
var childView =
|
||||||
|
internalView(manager.createViewInContainer(vcRef, 1, wrapPv(childProtoView), null));
|
||||||
|
expect(renderer.spy('setEventDispatcher'))
|
||||||
|
.toHaveBeenCalledWith(childView.render, childView);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should attach the view', () => {
|
describe('create another view when the first view has been returned', () => {
|
||||||
var contextView =
|
beforeEach(() => {
|
||||||
createView(createProtoView([createEmptyElBinder(), createEmptyElBinder()]));
|
internalView(manager.createViewInContainer(vcRef, 0, wrapPv(childProtoView), null));
|
||||||
var elRef = elementRef(parentView, 0);
|
manager.destroyViewInContainer(vcRef, 0);
|
||||||
manager.createViewInContainer(elRef, 0, wrapPv(childProtoView),
|
resetSpies();
|
||||||
elementRef(contextView, 1), null);
|
});
|
||||||
expect(utils.spy('attachViewInContainer'))
|
|
||||||
.toHaveBeenCalledWith(parentView, 0, contextView, 1, 0, createdViews[0]);
|
|
||||||
expect(renderer.spy('attachViewInContainer'))
|
|
||||||
.toHaveBeenCalledWith(elRef, 0, createdViews[0].render);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should hydrate the view', () => {
|
it('should use an existing nested view', () => {
|
||||||
var contextView =
|
var childView =
|
||||||
createView(createProtoView([createEmptyElBinder(), createEmptyElBinder()]));
|
internalView(manager.createViewInContainer(vcRef, 0, wrapPv(childProtoView), null));
|
||||||
manager.createViewInContainer(elementRef(parentView, 0), 0, wrapPv(childProtoView),
|
expect(childView.proto).toBe(childProtoView);
|
||||||
elementRef(contextView, 1), []);
|
expect(childView).toBe(hostView.views[2]);
|
||||||
expect(utils.spy('hydrateViewInContainer'))
|
expect(viewListener.spy('viewCreated')).not.toHaveBeenCalled();
|
||||||
.toHaveBeenCalledWith(parentView, 0, contextView, 1, 0, []);
|
expect(renderer.spy('createView')).not.toHaveBeenCalled();
|
||||||
expect(renderer.spy('hydrateView')).toHaveBeenCalledWith(createdViews[0].render);
|
});
|
||||||
});
|
|
||||||
|
|
||||||
it('should create and set the render view', () => {
|
|
||||||
manager.createViewInContainer(elementRef(parentView, 0), 0, wrapPv(childProtoView), null,
|
|
||||||
null);
|
|
||||||
expect(renderer.spy('createView')).toHaveBeenCalledWith(childProtoView.render);
|
|
||||||
expect(createdViews[0].render).toBe(createdRenderViews[0]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set the event dispatcher', () => {
|
|
||||||
manager.createViewInContainer(elementRef(parentView, 0), 0, wrapPv(childProtoView), null,
|
|
||||||
null);
|
|
||||||
var childView = createdViews[0];
|
|
||||||
expect(renderer.spy('setEventDispatcher'))
|
|
||||||
.toHaveBeenCalledWith(childView.render, childView);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -331,61 +306,159 @@ export function main() {
|
|||||||
describe('destroyViewInContainer', () => {
|
describe('destroyViewInContainer', () => {
|
||||||
|
|
||||||
describe('basic functionality', () => {
|
describe('basic functionality', () => {
|
||||||
var parentView, childProtoView, childView;
|
var hostView: AppView;
|
||||||
|
var childProtoView: AppProtoView;
|
||||||
|
var vcRef: ElementRef;
|
||||||
|
var firstChildView: AppView;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
parentView = createView(createProtoView([createEmptyElBinder()]));
|
childProtoView = createEmbeddedPv();
|
||||||
childProtoView = createProtoView();
|
var hostProtoView = createHostPv(
|
||||||
childView = internalView(manager.createViewInContainer(elementRef(parentView, 0), 0,
|
[createNestedElBinder(createComponentPv([createNestedElBinder(childProtoView)]))]);
|
||||||
wrapPv(childProtoView), null));
|
hostView = internalView(manager.createRootHostView(wrapPv(hostProtoView), null, null));
|
||||||
|
vcRef = hostView.elementRefs[1];
|
||||||
|
firstChildView =
|
||||||
|
internalView(manager.createViewInContainer(vcRef, 0, wrapPv(childProtoView), null));
|
||||||
|
resetSpies();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should dehydrate', () => {
|
describe('destroy the first view', () => {
|
||||||
manager.destroyViewInContainer(elementRef(parentView, 0), 0);
|
it('should dehydrate the app view but not the render view', () => {
|
||||||
expect(utils.spy('dehydrateView'))
|
manager.destroyViewInContainer(vcRef, 0);
|
||||||
.toHaveBeenCalledWith(parentView.viewContainers[0].views[0]);
|
expect(firstChildView.hydrated()).toBe(false);
|
||||||
expect(renderer.spy('dehydrateView')).toHaveBeenCalledWith(childView.render);
|
expect(renderer.spy('dehydrateView')).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detach', () => {
|
||||||
|
manager.destroyViewInContainer(vcRef, 0);
|
||||||
|
expect(hostView.viewContainers[1].views).toEqual([]);
|
||||||
|
expect(renderer.spy('detachFragment'))
|
||||||
|
.toHaveBeenCalledWith(firstChildView.renderFragment);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not return the view to the pool', () => {
|
||||||
|
manager.destroyViewInContainer(vcRef, 0);
|
||||||
|
expect(viewPool.spy('returnView')).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should detach', () => {
|
describe('destroy another view', () => {
|
||||||
var elRef = elementRef(parentView, 0);
|
var secondChildView;
|
||||||
manager.destroyViewInContainer(elRef, 0);
|
beforeEach(() => {
|
||||||
expect(utils.spy('detachViewInContainer')).toHaveBeenCalledWith(parentView, 0, 0);
|
secondChildView =
|
||||||
expect(renderer.spy('detachViewInContainer'))
|
internalView(manager.createViewInContainer(vcRef, 1, wrapPv(childProtoView), null));
|
||||||
.toHaveBeenCalledWith(elRef, 0, childView.render);
|
resetSpies();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should dehydrate', () => {
|
||||||
|
manager.destroyViewInContainer(vcRef, 1);
|
||||||
|
expect(secondChildView.hydrated()).toBe(false);
|
||||||
|
expect(renderer.spy('dehydrateView')).toHaveBeenCalledWith(secondChildView.render);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detach', () => {
|
||||||
|
manager.destroyViewInContainer(vcRef, 1);
|
||||||
|
expect(hostView.viewContainers[1].views[0]).toBe(firstChildView);
|
||||||
|
expect(renderer.spy('detachFragment'))
|
||||||
|
.toHaveBeenCalledWith(secondChildView.renderFragment);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the view to the pool', () => {
|
||||||
|
manager.destroyViewInContainer(vcRef, 1);
|
||||||
|
expect(viewPool.spy('returnView')).toHaveBeenCalledWith(secondChildView);
|
||||||
|
});
|
||||||
|
|
||||||
it('should return the view to the pool', () => {
|
|
||||||
manager.destroyViewInContainer(elementRef(parentView, 0), 0);
|
|
||||||
expect(viewPool.spy('returnView')).toHaveBeenCalledWith(childView);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('recursively destroy views in ViewContainers', () => {
|
describe('recursively destroy views in ViewContainers', () => {
|
||||||
var parentView, childProtoView, childView;
|
|
||||||
beforeEach(() => {
|
describe('destroy child views when a component is destroyed', () => {
|
||||||
parentView = createView(createProtoView([createEmptyElBinder()]));
|
var hostView: AppView;
|
||||||
childProtoView = createProtoView();
|
var childProtoView: AppProtoView;
|
||||||
childView = internalView(manager.createViewInContainer(elementRef(parentView, 0), 0,
|
var vcRef: ElementRef;
|
||||||
wrapPv(childProtoView), null));
|
var firstChildView: AppView;
|
||||||
|
var secondChildView: AppView;
|
||||||
|
beforeEach(() => {
|
||||||
|
childProtoView = createEmbeddedPv();
|
||||||
|
var hostProtoView = createHostPv(
|
||||||
|
[createNestedElBinder(createComponentPv([createNestedElBinder(childProtoView)]))]);
|
||||||
|
hostView = internalView(manager.createRootHostView(wrapPv(hostProtoView), null, null));
|
||||||
|
vcRef = hostView.elementRefs[1];
|
||||||
|
firstChildView =
|
||||||
|
internalView(manager.createViewInContainer(vcRef, 0, wrapPv(childProtoView), null));
|
||||||
|
secondChildView =
|
||||||
|
internalView(manager.createViewInContainer(vcRef, 1, wrapPv(childProtoView), null));
|
||||||
|
resetSpies();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should dehydrate', () => {
|
||||||
|
manager.destroyRootHostView(wrapView(hostView));
|
||||||
|
expect(firstChildView.hydrated()).toBe(false);
|
||||||
|
expect(secondChildView.hydrated()).toBe(false);
|
||||||
|
expect(renderer.spy('dehydrateView')).toHaveBeenCalledWith(hostView.render);
|
||||||
|
expect(renderer.spy('dehydrateView')).toHaveBeenCalledWith(secondChildView.render);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detach', () => {
|
||||||
|
manager.destroyRootHostView(wrapView(hostView));
|
||||||
|
expect(hostView.viewContainers[1].views).toEqual([]);
|
||||||
|
expect(renderer.spy('detachFragment'))
|
||||||
|
.toHaveBeenCalledWith(firstChildView.renderFragment);
|
||||||
|
expect(renderer.spy('detachFragment'))
|
||||||
|
.toHaveBeenCalledWith(secondChildView.renderFragment);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the view to the pool', () => {
|
||||||
|
manager.destroyRootHostView(wrapView(hostView));
|
||||||
|
expect(viewPool.spy('returnView')).not.toHaveBeenCalledWith(firstChildView);
|
||||||
|
expect(viewPool.spy('returnView')).toHaveBeenCalledWith(secondChildView);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should dehydrate', () => {
|
describe('destroy child views over multiple levels', () => {
|
||||||
manager.destroyRootHostView(wrapView(parentView));
|
var hostView: AppView;
|
||||||
expect(utils.spy('dehydrateView'))
|
var childProtoView: AppProtoView;
|
||||||
.toHaveBeenCalledWith(parentView.viewContainers[0].views[0]);
|
var nestedChildProtoView: AppProtoView;
|
||||||
expect(renderer.spy('dehydrateView')).toHaveBeenCalledWith(childView.render);
|
var vcRef: ElementRef;
|
||||||
});
|
var nestedVcRefs: ElementRef[];
|
||||||
|
var childViews: AppView[];
|
||||||
|
var nestedChildViews: AppView[];
|
||||||
|
beforeEach(() => {
|
||||||
|
nestedChildProtoView = createEmbeddedPv();
|
||||||
|
childProtoView = createEmbeddedPv([
|
||||||
|
createNestedElBinder(
|
||||||
|
createComponentPv([createNestedElBinder(nestedChildProtoView)]))
|
||||||
|
]);
|
||||||
|
var hostProtoView = createHostPv(
|
||||||
|
[createNestedElBinder(createComponentPv([createNestedElBinder(childProtoView)]))]);
|
||||||
|
hostView = internalView(manager.createRootHostView(wrapPv(hostProtoView), null, null));
|
||||||
|
vcRef = hostView.elementRefs[1];
|
||||||
|
nestedChildViews = [];
|
||||||
|
childViews = [];
|
||||||
|
nestedVcRefs = [];
|
||||||
|
for (var i = 0; i < 2; i++) {
|
||||||
|
var view = internalView(
|
||||||
|
manager.createViewInContainer(vcRef, i, wrapPv(childProtoView), null));
|
||||||
|
childViews.push(view);
|
||||||
|
var nestedVcRef = view.elementRefs[view.elementOffset];
|
||||||
|
nestedVcRefs.push(nestedVcRef);
|
||||||
|
for (var j = 0; j < 2; j++) {
|
||||||
|
var nestedView = internalView(
|
||||||
|
manager.createViewInContainer(nestedVcRef, j, wrapPv(childProtoView), null));
|
||||||
|
nestedChildViews.push(nestedView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resetSpies();
|
||||||
|
});
|
||||||
|
|
||||||
it('should detach', () => {
|
it('should dehydrate all child views', () => {
|
||||||
manager.destroyRootHostView(wrapView(parentView));
|
manager.destroyRootHostView(wrapView(hostView));
|
||||||
expect(utils.spy('detachViewInContainer')).toHaveBeenCalledWith(parentView, 0, 0);
|
childViews.forEach((childView) => expect(childView.hydrated()).toBe(false));
|
||||||
expect(renderer.spy('detachViewInContainer'))
|
nestedChildViews.forEach((childView) => expect(childView.hydrated()).toBe(false));
|
||||||
.toHaveBeenCalledWith(parentView.elementRefs[0], 0, childView.render);
|
});
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the view to the pool', () => {
|
|
||||||
manager.destroyRootHostView(wrapView(parentView));
|
|
||||||
expect(viewPool.spy('returnView')).toHaveBeenCalledWith(childView);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -402,18 +475,6 @@ export function main() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class MockProtoViewRef extends RenderProtoViewRef {
|
|
||||||
nestedComponentCount: number;
|
|
||||||
constructor(nestedComponentCount: number) {
|
|
||||||
super();
|
|
||||||
this.nestedComponentCount = nestedComponentCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({selector: 'someComponent'})
|
|
||||||
class SomeComponent {
|
|
||||||
}
|
|
||||||
|
|
||||||
@proxy
|
@proxy
|
||||||
@IMPLEMENTS(Renderer)
|
@IMPLEMENTS(Renderer)
|
||||||
class SpyRenderer extends SpyObject {
|
class SpyRenderer extends SpyObject {
|
||||||
@ -428,23 +489,9 @@ class SpyAppViewPool extends SpyObject {
|
|||||||
noSuchMethod(m) { return super.noSuchMethod(m) }
|
noSuchMethod(m) { return super.noSuchMethod(m) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@proxy
|
|
||||||
@IMPLEMENTS(AppViewManagerUtils)
|
|
||||||
class SpyAppViewManagerUtils extends SpyObject {
|
|
||||||
constructor() { super(AppViewManagerUtils); }
|
|
||||||
noSuchMethod(m) { return super.noSuchMethod(m) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@proxy
|
@proxy
|
||||||
@IMPLEMENTS(AppViewListener)
|
@IMPLEMENTS(AppViewListener)
|
||||||
class SpyAppViewListener extends SpyObject {
|
class SpyAppViewListener extends SpyObject {
|
||||||
constructor() { super(AppViewListener); }
|
constructor() { super(AppViewListener); }
|
||||||
noSuchMethod(m) { return super.noSuchMethod(m) }
|
noSuchMethod(m) { return super.noSuchMethod(m) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@proxy
|
|
||||||
@IMPLEMENTS(ElementInjector)
|
|
||||||
class SpyElementInjector extends SpyObject {
|
|
||||||
constructor() { super(ElementInjector); }
|
|
||||||
noSuchMethod(m) { return super.noSuchMethod(m) }
|
|
||||||
}
|
|
||||||
|
@ -14,6 +14,7 @@ import {
|
|||||||
xit,
|
xit,
|
||||||
SpyObject,
|
SpyObject,
|
||||||
SpyChangeDetector,
|
SpyChangeDetector,
|
||||||
|
SpyProtoChangeDetector,
|
||||||
proxy,
|
proxy,
|
||||||
Log
|
Log
|
||||||
} from 'angular2/test_lib';
|
} from 'angular2/test_lib';
|
||||||
@ -22,7 +23,7 @@ import {Injector, bind} from 'angular2/di';
|
|||||||
import {IMPLEMENTS, isBlank, isPresent} from 'angular2/src/facade/lang';
|
import {IMPLEMENTS, isBlank, isPresent} from 'angular2/src/facade/lang';
|
||||||
import {MapWrapper, ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
import {MapWrapper, ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||||
|
|
||||||
import {AppProtoView, AppView} from 'angular2/src/core/compiler/view';
|
import {AppProtoView, AppView, AppProtoViewMergeMapping} from 'angular2/src/core/compiler/view';
|
||||||
import {ElementBinder} from 'angular2/src/core/compiler/element_binder';
|
import {ElementBinder} from 'angular2/src/core/compiler/element_binder';
|
||||||
import {
|
import {
|
||||||
DirectiveBinding,
|
DirectiveBinding,
|
||||||
@ -33,89 +34,31 @@ import {
|
|||||||
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
|
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
|
||||||
import {Component} from 'angular2/annotations';
|
import {Component} from 'angular2/annotations';
|
||||||
import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils';
|
import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils';
|
||||||
|
import {RenderProtoViewMergeMapping, ViewType, RenderViewWithFragments} from 'angular2/render';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
// TODO(tbosch): add more tests here!
|
// TODO(tbosch): add more tests here!
|
||||||
|
|
||||||
describe('AppViewManagerUtils', () => {
|
describe('AppViewManagerUtils', () => {
|
||||||
|
|
||||||
var directiveResolver;
|
var utils: AppViewManagerUtils;
|
||||||
var utils;
|
|
||||||
|
|
||||||
function createInjector() { return Injector.resolveAndCreate([]); }
|
beforeEach(() => { utils = new AppViewManagerUtils(); });
|
||||||
|
|
||||||
function createDirectiveBinding(type) {
|
function createViewWithChildren(pv: AppProtoView): AppView {
|
||||||
var annotation = directiveResolver.resolve(type);
|
var renderViewWithFragments = new RenderViewWithFragments(null, [null, null]);
|
||||||
return DirectiveBinding.createFromType(type, annotation);
|
return utils.createView(pv, renderViewWithFragments, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createEmptyElBinder() { return new ElementBinder(0, null, 0, null, null); }
|
describe('shared hydrate functionality', () => {
|
||||||
|
|
||||||
function createComponentElBinder(nestedProtoView = null) {
|
|
||||||
var binding = createDirectiveBinding(SomeComponent);
|
|
||||||
var binder = new ElementBinder(0, null, 0, null, binding);
|
|
||||||
binder.nestedProtoView = nestedProtoView;
|
|
||||||
return binder;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createProtoView(binders = null) {
|
|
||||||
if (isBlank(binders)) {
|
|
||||||
binders = [];
|
|
||||||
}
|
|
||||||
var res = new AppProtoView(null, null, null, null);
|
|
||||||
res.elementBinders = binders;
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createElementInjector(parent = null) {
|
|
||||||
var host = new SpyElementInjector();
|
|
||||||
var elementInjector =
|
|
||||||
isPresent(parent) ? new SpyElementInjectorWithParent(parent) : new SpyElementInjector();
|
|
||||||
return SpyObject.stub(elementInjector,
|
|
||||||
{
|
|
||||||
'isExportingComponent': false,
|
|
||||||
'isExportingElement': false,
|
|
||||||
'getEventEmitterAccessors': [],
|
|
||||||
'getHostActionAccessors': [],
|
|
||||||
'getComponent': null,
|
|
||||||
'getHost': host
|
|
||||||
},
|
|
||||||
{});
|
|
||||||
}
|
|
||||||
|
|
||||||
function createView(pv = null, nestedInjectors = false) {
|
|
||||||
if (isBlank(pv)) {
|
|
||||||
pv = createProtoView();
|
|
||||||
}
|
|
||||||
var view = new AppView(null, pv, new Map());
|
|
||||||
var elementInjectors = ListWrapper.createGrowableSize(pv.elementBinders.length);
|
|
||||||
var preBuiltObjects = ListWrapper.createFixedSize(pv.elementBinders.length);
|
|
||||||
for (var i = 0; i < pv.elementBinders.length; i++) {
|
|
||||||
if (nestedInjectors && i > 0) {
|
|
||||||
elementInjectors[i] = createElementInjector(elementInjectors[i - 1]);
|
|
||||||
} else {
|
|
||||||
elementInjectors[i] = createElementInjector();
|
|
||||||
}
|
|
||||||
preBuiltObjects[i] = new SpyPreBuiltObjects();
|
|
||||||
}
|
|
||||||
view.init(<any>new SpyChangeDetector(), elementInjectors, elementInjectors, preBuiltObjects,
|
|
||||||
ListWrapper.createFixedSize(pv.elementBinders.length));
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
directiveResolver = new DirectiveResolver();
|
|
||||||
utils = new AppViewManagerUtils();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("hydrateComponentView", () => {
|
|
||||||
|
|
||||||
it("should hydrate the change detector after hydrating element injectors", () => {
|
it("should hydrate the change detector after hydrating element injectors", () => {
|
||||||
var log = new Log();
|
var log = new Log();
|
||||||
|
|
||||||
var componentView = createView(createProtoView([createEmptyElBinder()]));
|
var componentProtoView = createComponentPv([createEmptyElBinder()]);
|
||||||
var hostView = createView(createProtoView([createComponentElBinder(createProtoView())]));
|
var hostView =
|
||||||
hostView.componentChildViews = [componentView];
|
createViewWithChildren(createHostPv([createNestedElBinder(componentProtoView)]));
|
||||||
|
var componentView = hostView.views[1];
|
||||||
|
|
||||||
var spyEi = <any>componentView.elementInjectors[0];
|
var spyEi = <any>componentView.elementInjectors[0];
|
||||||
spyEi.spy('hydrate').andCallFake(log.fn('hydrate'));
|
spyEi.spy('hydrate').andCallFake(log.fn('hydrate'));
|
||||||
@ -123,20 +66,17 @@ export function main() {
|
|||||||
var spyCd = <any>componentView.changeDetector;
|
var spyCd = <any>componentView.changeDetector;
|
||||||
spyCd.spy('hydrate').andCallFake(log.fn('hydrateCD'));
|
spyCd.spy('hydrate').andCallFake(log.fn('hydrateCD'));
|
||||||
|
|
||||||
utils.hydrateComponentView(hostView, 0);
|
utils.hydrateRootHostView(hostView, createInjector());
|
||||||
|
|
||||||
expect(log.result()).toEqual('hydrate; hydrateCD');
|
expect(log.result()).toEqual('hydrate; hydrateCD');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('shared hydrate functionality', () => {
|
|
||||||
|
|
||||||
it("should set up event listeners", () => {
|
it("should set up event listeners", () => {
|
||||||
var dir = new Object();
|
var dir = new Object();
|
||||||
|
|
||||||
var hostPv = createProtoView([createComponentElBinder(null), createEmptyElBinder()]);
|
var hostPv =
|
||||||
var hostView = createView(hostPv);
|
createHostPv([createNestedElBinder(createComponentPv()), createEmptyElBinder()]);
|
||||||
|
var hostView = createViewWithChildren(hostPv);
|
||||||
var spyEventAccessor1 = SpyObject.stub({"subscribe": null});
|
var spyEventAccessor1 = SpyObject.stub({"subscribe": null});
|
||||||
SpyObject.stub(hostView.elementInjectors[0], {
|
SpyObject.stub(hostView.elementInjectors[0], {
|
||||||
'getHostActionAccessors': [],
|
'getHostActionAccessors': [],
|
||||||
@ -150,9 +90,6 @@ export function main() {
|
|||||||
'getDirectiveAtIndex': dir
|
'getDirectiveAtIndex': dir
|
||||||
});
|
});
|
||||||
|
|
||||||
var shadowView = createView();
|
|
||||||
utils.attachComponentView(hostView, 0, shadowView);
|
|
||||||
|
|
||||||
utils.hydrateRootHostView(hostView, createInjector());
|
utils.hydrateRootHostView(hostView, createInjector());
|
||||||
|
|
||||||
expect(spyEventAccessor1.spy('subscribe')).toHaveBeenCalledWith(hostView, 0, dir);
|
expect(spyEventAccessor1.spy('subscribe')).toHaveBeenCalledWith(hostView, 0, dir);
|
||||||
@ -162,8 +99,9 @@ export function main() {
|
|||||||
it("should set up host action listeners", () => {
|
it("should set up host action listeners", () => {
|
||||||
var dir = new Object();
|
var dir = new Object();
|
||||||
|
|
||||||
var hostPv = createProtoView([createComponentElBinder(null), createEmptyElBinder()]);
|
var hostPv =
|
||||||
var hostView = createView(hostPv);
|
createHostPv([createNestedElBinder(createComponentPv()), createEmptyElBinder()]);
|
||||||
|
var hostView = createViewWithChildren(hostPv);
|
||||||
var spyActionAccessor1 = SpyObject.stub({"subscribe": null});
|
var spyActionAccessor1 = SpyObject.stub({"subscribe": null});
|
||||||
SpyObject.stub(hostView.elementInjectors[0], {
|
SpyObject.stub(hostView.elementInjectors[0], {
|
||||||
'getHostActionAccessors': [[spyActionAccessor1]],
|
'getHostActionAccessors': [[spyActionAccessor1]],
|
||||||
@ -177,37 +115,51 @@ export function main() {
|
|||||||
'getDirectiveAtIndex': dir
|
'getDirectiveAtIndex': dir
|
||||||
});
|
});
|
||||||
|
|
||||||
var shadowView = createView();
|
|
||||||
utils.attachComponentView(hostView, 0, shadowView);
|
|
||||||
|
|
||||||
utils.hydrateRootHostView(hostView, createInjector());
|
utils.hydrateRootHostView(hostView, createInjector());
|
||||||
|
|
||||||
expect(spyActionAccessor1.spy('subscribe')).toHaveBeenCalledWith(hostView, 0, dir);
|
expect(spyActionAccessor1.spy('subscribe')).toHaveBeenCalledWith(hostView, 0, dir);
|
||||||
expect(spyActionAccessor2.spy('subscribe')).toHaveBeenCalledWith(hostView, 1, dir);
|
expect(spyActionAccessor2.spy('subscribe')).toHaveBeenCalledWith(hostView, 1, dir);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should not hydrate element injectors of component views inside of embedded fragments",
|
||||||
|
() => {
|
||||||
|
var hostView = createViewWithChildren(createHostPv([
|
||||||
|
createNestedElBinder(createComponentPv([
|
||||||
|
createNestedElBinder(createEmbeddedPv(
|
||||||
|
[createNestedElBinder(createComponentPv([createEmptyElBinder()]))]))
|
||||||
|
]))
|
||||||
|
]));
|
||||||
|
|
||||||
|
utils.hydrateRootHostView(hostView, createInjector());
|
||||||
|
expect(hostView.elementInjectors.length).toBe(4);
|
||||||
|
expect((<any>hostView.elementInjectors[3]).spy('hydrate')).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('attachViewInContainer', () => {
|
describe('attachViewInContainer', () => {
|
||||||
var parentView, contextView, childView;
|
var parentView, contextView, childView;
|
||||||
|
|
||||||
function createViews(numInj = 1) {
|
function createViews(numInj = 1) {
|
||||||
var parentPv = createProtoView([createEmptyElBinder()]);
|
var childPv = createEmbeddedPv([createEmptyElBinder()]);
|
||||||
parentView = createView(parentPv);
|
childView = createViewWithChildren(childPv);
|
||||||
|
|
||||||
|
var parentPv = createHostPv([createEmptyElBinder()]);
|
||||||
|
parentView = createViewWithChildren(parentPv);
|
||||||
|
|
||||||
var binders = [];
|
var binders = [];
|
||||||
for (var i = 0; i < numInj; i++) binders.push(createEmptyElBinder());
|
for (var i = 0; i < numInj; i++) {
|
||||||
var contextPv = createProtoView(binders);
|
binders.push(createEmptyElBinder(i > 0 ? binders[i - 1] : null))
|
||||||
contextView = createView(contextPv, true);
|
};
|
||||||
|
var contextPv = createHostPv(binders);
|
||||||
var childPv = createProtoView([createEmptyElBinder()]);
|
contextView = createViewWithChildren(contextPv);
|
||||||
childView = createView(childPv);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should link the views rootElementInjectors at the given context', () => {
|
it('should link the views rootElementInjectors at the given context', () => {
|
||||||
createViews();
|
createViews();
|
||||||
utils.attachViewInContainer(parentView, 0, contextView, 0, 0, childView);
|
utils.attachViewInContainer(parentView, 0, contextView, 0, 0, childView);
|
||||||
expect(contextView.elementInjectors.length).toEqual(2);
|
expect(contextView.rootElementInjectors.length).toEqual(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should link the views rootElementInjectors after the elementInjector at the given context',
|
it('should link the views rootElementInjectors after the elementInjector at the given context',
|
||||||
@ -223,14 +175,14 @@ export function main() {
|
|||||||
var parentView, contextView, childView;
|
var parentView, contextView, childView;
|
||||||
|
|
||||||
function createViews() {
|
function createViews() {
|
||||||
var parentPv = createProtoView([createEmptyElBinder()]);
|
var parentPv = createHostPv([createEmptyElBinder()]);
|
||||||
parentView = createView(parentPv);
|
parentView = createViewWithChildren(parentPv);
|
||||||
|
|
||||||
var contextPv = createProtoView([createEmptyElBinder()]);
|
var contextPv = createHostPv([createEmptyElBinder()]);
|
||||||
contextView = createView(contextPv);
|
contextView = createViewWithChildren(contextPv);
|
||||||
|
|
||||||
var childPv = createProtoView([createEmptyElBinder()]);
|
var childPv = createEmbeddedPv([createEmptyElBinder()]);
|
||||||
childView = createView(childPv);
|
childView = createViewWithChildren(childPv);
|
||||||
utils.attachViewInContainer(parentView, 0, contextView, 0, 0, childView);
|
utils.attachViewInContainer(parentView, 0, contextView, 0, 0, childView);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,8 +201,8 @@ export function main() {
|
|||||||
var hostView;
|
var hostView;
|
||||||
|
|
||||||
function createViews() {
|
function createViews() {
|
||||||
var hostPv = createProtoView([createComponentElBinder()]);
|
var hostPv = createHostPv([createNestedElBinder(createComponentPv())]);
|
||||||
hostView = createView(hostPv);
|
hostView = createViewWithChildren(hostPv);
|
||||||
}
|
}
|
||||||
|
|
||||||
it("should instantiate the elementInjectors with the given injector and an empty host element injector",
|
it("should instantiate the elementInjectors with the given injector and an empty host element injector",
|
||||||
@ -268,25 +220,125 @@ export function main() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function createInjector() {
|
||||||
|
return Injector.resolveAndCreate([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createElementInjector(parent = null) {
|
||||||
|
var host = new SpyElementInjector(null);
|
||||||
|
var elementInjector = new SpyElementInjector(parent);
|
||||||
|
return SpyObject.stub(elementInjector,
|
||||||
|
{
|
||||||
|
'isExportingComponent': false,
|
||||||
|
'isExportingElement': false,
|
||||||
|
'getEventEmitterAccessors': [],
|
||||||
|
'getHostActionAccessors': [],
|
||||||
|
'getComponent': new Object(),
|
||||||
|
'getHost': host
|
||||||
|
},
|
||||||
|
{});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createProtoElInjector(parent: ProtoElementInjector = null): ProtoElementInjector {
|
||||||
|
var pei = new SpyProtoElementInjector(parent);
|
||||||
|
pei.spy('instantiate').andCallFake((parentEli) => createElementInjector(parentEli));
|
||||||
|
return <any>pei;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createEmptyElBinder(parent: ElementBinder = null) {
|
||||||
|
var parentPeli = isPresent(parent) ? parent.protoElementInjector : null;
|
||||||
|
return new ElementBinder(0, null, 0, createProtoElInjector(parentPeli), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createNestedElBinder(nestedProtoView: AppProtoView) {
|
||||||
|
var componentBinding = null;
|
||||||
|
if (nestedProtoView.type === ViewType.COMPONENT) {
|
||||||
|
var annotation = new DirectiveResolver().resolve(SomeComponent);
|
||||||
|
componentBinding = DirectiveBinding.createFromType(SomeComponent, annotation);
|
||||||
|
}
|
||||||
|
var binder = new ElementBinder(0, null, 0, createProtoElInjector(), componentBinding);
|
||||||
|
binder.nestedProtoView = nestedProtoView;
|
||||||
|
return binder;
|
||||||
|
}
|
||||||
|
|
||||||
|
function countNestedElementBinders(pv: AppProtoView): number {
|
||||||
|
var result = pv.elementBinders.length;
|
||||||
|
pv.elementBinders.forEach(binder => {
|
||||||
|
if (isPresent(binder.nestedProtoView)) {
|
||||||
|
result += countNestedElementBinders(binder.nestedProtoView);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calcHostElementIndicesByViewIndex(pv: AppProtoView, elementOffset = 0,
|
||||||
|
target: number[] = null): number[] {
|
||||||
|
if (isBlank(target)) {
|
||||||
|
target = [null];
|
||||||
|
}
|
||||||
|
for (var binderIdx = 0; binderIdx < pv.elementBinders.length; binderIdx++) {
|
||||||
|
var binder = pv.elementBinders[binderIdx];
|
||||||
|
if (isPresent(binder.nestedProtoView)) {
|
||||||
|
target.push(elementOffset + binderIdx);
|
||||||
|
calcHostElementIndicesByViewIndex(binder.nestedProtoView,
|
||||||
|
elementOffset + pv.elementBinders.length, target);
|
||||||
|
elementOffset += countNestedElementBinders(binder.nestedProtoView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _createProtoView(type: ViewType, binders: ElementBinder[] = null) {
|
||||||
|
if (isBlank(binders)) {
|
||||||
|
binders = [];
|
||||||
|
}
|
||||||
|
var protoChangeDetector = <any>new SpyProtoChangeDetector();
|
||||||
|
protoChangeDetector.spy('instantiate').andReturn(new SpyChangeDetector());
|
||||||
|
var res = new AppProtoView(type, protoChangeDetector, null, null, 0);
|
||||||
|
res.elementBinders = binders;
|
||||||
|
var mappedElementIndices = ListWrapper.createFixedSize(countNestedElementBinders(res));
|
||||||
|
for (var i = 0; i < binders.length; i++) {
|
||||||
|
var binder = binders[i];
|
||||||
|
mappedElementIndices[i] = i;
|
||||||
|
binder.protoElementInjector.index = i;
|
||||||
|
}
|
||||||
|
var hostElementIndicesByViewIndex = calcHostElementIndicesByViewIndex(res);
|
||||||
|
res.mergeMapping = new AppProtoViewMergeMapping(
|
||||||
|
new RenderProtoViewMergeMapping(null, hostElementIndicesByViewIndex.length,
|
||||||
|
mappedElementIndices, [], hostElementIndicesByViewIndex));
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createHostPv(binders: ElementBinder[] = null) {
|
||||||
|
return _createProtoView(ViewType.HOST, binders);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createComponentPv(binders: ElementBinder[] = null) {
|
||||||
|
return _createProtoView(ViewType.COMPONENT, binders);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createEmbeddedPv(binders: ElementBinder[] = null) {
|
||||||
|
return _createProtoView(ViewType.EMBEDDED, binders);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Component({selector: 'someComponent'})
|
@Component({selector: 'someComponent'})
|
||||||
class SomeComponent {
|
class SomeComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
@proxy
|
@proxy
|
||||||
@IMPLEMENTS(ElementInjector)
|
@IMPLEMENTS(ProtoElementInjector)
|
||||||
class SpyElementInjector extends SpyObject {
|
class SpyProtoElementInjector extends SpyObject {
|
||||||
constructor() { super(ElementInjector); }
|
index: number;
|
||||||
|
constructor(public parent: ProtoElementInjector) { super(ProtoElementInjector); }
|
||||||
noSuchMethod(m) { return super.noSuchMethod(m) }
|
noSuchMethod(m) { return super.noSuchMethod(m) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@proxy
|
@proxy
|
||||||
@IMPLEMENTS(ElementInjector)
|
@IMPLEMENTS(ElementInjector)
|
||||||
class SpyElementInjectorWithParent extends SpyObject {
|
class SpyElementInjector extends SpyObject {
|
||||||
parent: ElementInjector;
|
constructor(public parent: ElementInjector) { super(ElementInjector); }
|
||||||
constructor(parent) {
|
|
||||||
super(ElementInjector);
|
|
||||||
this.parent = parent;
|
|
||||||
}
|
|
||||||
noSuchMethod(m) { return super.noSuchMethod(m) }
|
noSuchMethod(m) { return super.noSuchMethod(m) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,9 +24,11 @@ export function main() {
|
|||||||
|
|
||||||
function createViewPool({capacity}): AppViewPool { return new AppViewPool(capacity); }
|
function createViewPool({capacity}): AppViewPool { return new AppViewPool(capacity); }
|
||||||
|
|
||||||
function createProtoView() { return new AppProtoView(null, null, null, null); }
|
function createProtoView() { return new AppProtoView(null, null, null, null, null); }
|
||||||
|
|
||||||
function createView(pv) { return new AppView(null, pv, new Map()); }
|
function createView(pv) {
|
||||||
|
return new AppView(null, pv, null, null, null, null, new Map(), null, null);
|
||||||
|
}
|
||||||
|
|
||||||
it('should support multiple AppProtoViews', () => {
|
it('should support multiple AppProtoViews', () => {
|
||||||
var vf = createViewPool({capacity: 2});
|
var vf = createViewPool({capacity: 2});
|
||||||
|
53
modules/angular2/test/dom/dom_adapter_spec.ts
Normal file
53
modules/angular2/test/dom/dom_adapter_spec.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import {
|
||||||
|
AsyncTestCompleter,
|
||||||
|
beforeEach,
|
||||||
|
ddescribe,
|
||||||
|
describe,
|
||||||
|
el,
|
||||||
|
expect,
|
||||||
|
iit,
|
||||||
|
inject,
|
||||||
|
it,
|
||||||
|
xit,
|
||||||
|
beforeEachBindings,
|
||||||
|
SpyObject,
|
||||||
|
stringifyElement
|
||||||
|
} from 'angular2/test_lib';
|
||||||
|
|
||||||
|
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
describe('dom adapter', () => {
|
||||||
|
it('should not coalesque text nodes', () => {
|
||||||
|
var el1 = el('<div>a</div>');
|
||||||
|
var el2 = el('<div>b</div>');
|
||||||
|
DOM.appendChild(el2, DOM.firstChild(el1));
|
||||||
|
expect(DOM.childNodes(el2).length).toBe(2);
|
||||||
|
|
||||||
|
var el2Clone = DOM.clone(el2);
|
||||||
|
expect(DOM.childNodes(el2Clone).length).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should clone correctly', () => {
|
||||||
|
var el1 = el('<div x="y">a<span>b</span></div>');
|
||||||
|
var clone = DOM.clone(el1);
|
||||||
|
|
||||||
|
expect(clone).not.toBe(el1);
|
||||||
|
DOM.setAttribute(clone, 'test', '1');
|
||||||
|
expect(DOM.getOuterHTML(clone)).toEqual('<div x="y" test="1">a<span>b</span></div>');
|
||||||
|
expect(DOM.getAttribute(el1, 'test')).toBeFalsy();
|
||||||
|
|
||||||
|
var cNodes = DOM.childNodes(clone);
|
||||||
|
var firstChild = cNodes[0];
|
||||||
|
var secondChild = cNodes[1];
|
||||||
|
expect(DOM.parentElement(firstChild)).toBe(clone);
|
||||||
|
expect(DOM.nextSibling(firstChild)).toBe(secondChild);
|
||||||
|
expect(DOM.isTextNode(firstChild)).toBe(true);
|
||||||
|
|
||||||
|
expect(DOM.parentElement(secondChild)).toBe(clone);
|
||||||
|
expect(DOM.nextSibling(secondChild)).toBeFalsy();
|
||||||
|
expect(DOM.isElementNode(secondChild)).toBe(true);
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -36,7 +36,7 @@ export function runCompilerCommonTests() {
|
|||||||
}
|
}
|
||||||
var tplLoader = new FakeViewLoader(urlData);
|
var tplLoader = new FakeViewLoader(urlData);
|
||||||
mockStepFactory = new MockStepFactory([new MockStep(processClosure)]);
|
mockStepFactory = new MockStepFactory([new MockStep(processClosure)]);
|
||||||
return new DomCompiler(mockStepFactory, tplLoader);
|
return new DomCompiler(mockStepFactory, tplLoader, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('compile', () => {
|
describe('compile', () => {
|
||||||
@ -64,7 +64,8 @@ export function runCompilerCommonTests() {
|
|||||||
{id: 'id', selector: 'CUSTOM', type: DirectiveMetadata.COMPONENT_TYPE});
|
{id: 'id', selector: 'CUSTOM', type: DirectiveMetadata.COMPONENT_TYPE});
|
||||||
compiler.compileHost(dirMetadata)
|
compiler.compileHost(dirMetadata)
|
||||||
.then((protoView) => {
|
.then((protoView) => {
|
||||||
expect(DOM.tagName(resolveInternalDomProtoView(protoView.render).element))
|
expect(DOM.tagName(DOM.firstChild(DOM.content(
|
||||||
|
resolveInternalDomProtoView(protoView.render).rootElement))))
|
||||||
.toEqual('CUSTOM');
|
.toEqual('CUSTOM');
|
||||||
expect(mockStepFactory.viewDef.directives).toEqual([dirMetadata]);
|
expect(mockStepFactory.viewDef.directives).toEqual([dirMetadata]);
|
||||||
expect(protoView.variableBindings)
|
expect(protoView.variableBindings)
|
||||||
@ -79,7 +80,7 @@ export function runCompilerCommonTests() {
|
|||||||
compiler.compile(
|
compiler.compile(
|
||||||
new ViewDefinition({componentId: 'someId', template: 'inline component'}))
|
new ViewDefinition({componentId: 'someId', template: 'inline component'}))
|
||||||
.then((protoView) => {
|
.then((protoView) => {
|
||||||
expect(DOM.getInnerHTML(resolveInternalDomProtoView(protoView.render).element))
|
expect(DOM.getInnerHTML(resolveInternalDomProtoView(protoView.render).rootElement))
|
||||||
.toEqual('inline component');
|
.toEqual('inline component');
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
@ -90,7 +91,7 @@ export function runCompilerCommonTests() {
|
|||||||
var compiler = createCompiler(EMPTY_STEP, urlData);
|
var compiler = createCompiler(EMPTY_STEP, urlData);
|
||||||
compiler.compile(new ViewDefinition({componentId: 'someId', templateAbsUrl: 'someUrl'}))
|
compiler.compile(new ViewDefinition({componentId: 'someId', templateAbsUrl: 'someUrl'}))
|
||||||
.then((protoView) => {
|
.then((protoView) => {
|
||||||
expect(DOM.getInnerHTML(resolveInternalDomProtoView(protoView.render).element))
|
expect(DOM.getInnerHTML(resolveInternalDomProtoView(protoView.render).rootElement))
|
||||||
.toEqual('url component');
|
.toEqual('url component');
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
|
@ -2,9 +2,12 @@ import {describe, beforeEach, expect, it, iit, ddescribe, el} from 'angular2/tes
|
|||||||
import {TextInterpolationParser} from 'angular2/src/render/dom/compiler/text_interpolation_parser';
|
import {TextInterpolationParser} from 'angular2/src/render/dom/compiler/text_interpolation_parser';
|
||||||
import {CompilePipeline} from 'angular2/src/render/dom/compiler/compile_pipeline';
|
import {CompilePipeline} from 'angular2/src/render/dom/compiler/compile_pipeline';
|
||||||
import {MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
|
import {MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
|
||||||
import {Lexer, Parser} from 'angular2/change_detection';
|
import {Lexer, Parser, ASTWithSource} from 'angular2/change_detection';
|
||||||
import {IgnoreChildrenStep} from './pipeline_spec';
|
import {IgnoreChildrenStep} from './pipeline_spec';
|
||||||
import {ElementBinderBuilder} from 'angular2/src/render/dom/view/proto_view_builder';
|
import {
|
||||||
|
ProtoViewBuilder,
|
||||||
|
ElementBinderBuilder
|
||||||
|
} from 'angular2/src/render/dom/view/proto_view_builder';
|
||||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
@ -14,48 +17,55 @@ export function main() {
|
|||||||
[new IgnoreChildrenStep(), new TextInterpolationParser(new Parser(new Lexer()))]);
|
[new IgnoreChildrenStep(), new TextInterpolationParser(new Parser(new Lexer()))]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function process(element): List<ElementBinderBuilder> {
|
function process(templateString: string): ProtoViewBuilder {
|
||||||
return ListWrapper.map(createPipeline().process(element),
|
var compileElements = createPipeline().process(DOM.createTemplate(templateString));
|
||||||
(compileElement) => compileElement.inheritedElementBinder);
|
return compileElements[0].inheritedProtoView;
|
||||||
}
|
}
|
||||||
|
|
||||||
function assertTextBinding(elementBinder, bindingIndex, nodeIndex, expression) {
|
function assertRootTextBinding(protoViewBuilder: ProtoViewBuilder, nodeIndex: number,
|
||||||
expect(elementBinder.textBindings[bindingIndex].source).toEqual(expression);
|
expression: string) {
|
||||||
expect(elementBinder.textBindingNodes[bindingIndex])
|
var node = DOM.childNodes(DOM.templateAwareRoot(protoViewBuilder.rootElement))[nodeIndex];
|
||||||
.toEqual(DOM.childNodes(DOM.templateAwareRoot(elementBinder.element))[nodeIndex]);
|
expect(protoViewBuilder.rootTextBindings.get(node).source).toEqual(expression);
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should find text interpolation in normal elements', () => {
|
function assertElementTextBinding(elementBinderBuilder: ElementBinderBuilder, nodeIndex: number,
|
||||||
var result = process(el('<div>{{expr1}}<span></span>{{expr2}}</div>'))[0];
|
expression: string) {
|
||||||
assertTextBinding(result, 0, 0, "{{expr1}}");
|
var node = DOM.childNodes(DOM.templateAwareRoot(elementBinderBuilder.element))[nodeIndex];
|
||||||
assertTextBinding(result, 1, 2, "{{expr2}}");
|
expect(elementBinderBuilder.textBindings.get(node).source).toEqual(expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should find root text interpolations', () => {
|
||||||
|
var result = process('{{expr1}}{{expr2}}<div></div>{{expr3}}');
|
||||||
|
assertRootTextBinding(result, 0, "{{expr1}}{{expr2}}");
|
||||||
|
assertRootTextBinding(result, 2, "{{expr3}}");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should find text interpolation in template elements', () => {
|
it('should find text interpolation in normal elements', () => {
|
||||||
var result = process(el('<template>{{expr1}}<span></span>{{expr2}}</template>'))[0];
|
var result = process('<div>{{expr1}}<span></span>{{expr2}}</div>');
|
||||||
assertTextBinding(result, 0, 0, "{{expr1}}");
|
assertElementTextBinding(result.elements[0], 0, "{{expr1}}");
|
||||||
assertTextBinding(result, 1, 2, "{{expr2}}");
|
assertElementTextBinding(result.elements[0], 2, "{{expr2}}");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow multiple expressions', () => {
|
it('should allow multiple expressions', () => {
|
||||||
var result = process(el('<div>{{expr1}}{{expr2}}</div>'))[0];
|
var result = process('<div>{{expr1}}{{expr2}}</div>');
|
||||||
assertTextBinding(result, 0, 0, "{{expr1}}{{expr2}}");
|
assertElementTextBinding(result.elements[0], 0, "{{expr1}}{{expr2}}");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not interpolate when compileChildren is false', () => {
|
it('should not interpolate when compileChildren is false', () => {
|
||||||
var results = process(el('<div>{{included}}<span ignore-children>{{excluded}}</span></div>'));
|
var results = process('<div>{{included}}<span ignore-children>{{excluded}}</span></div>');
|
||||||
assertTextBinding(results[0], 0, 0, "{{included}}");
|
assertElementTextBinding(results.elements[0], 0, "{{included}}");
|
||||||
expect(results[1]).toBe(results[0]);
|
expect(results.elements.length).toBe(1);
|
||||||
|
expect(results.elements[0].textBindings.size).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow fixed text before, in between and after expressions', () => {
|
it('should allow fixed text before, in between and after expressions', () => {
|
||||||
var result = process(el('<div>a{{expr1}}b{{expr2}}c</div>'))[0];
|
var result = process('<div>a{{expr1}}b{{expr2}}c</div>');
|
||||||
assertTextBinding(result, 0, 0, "a{{expr1}}b{{expr2}}c");
|
assertElementTextBinding(result.elements[0], 0, "a{{expr1}}b{{expr2}}c");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should escape quotes in fixed parts', () => {
|
it('should escape quotes in fixed parts', () => {
|
||||||
var result = process(el("<div>'\"a{{expr1}}</div>"))[0];
|
var result = process("<div>'\"a{{expr1}}</div>");
|
||||||
assertTextBinding(result, 0, 0, "'\"a{{expr1}}");
|
assertElementTextBinding(result.elements[0], 0, "'\"a{{expr1}}");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ export function main() {
|
|||||||
describe('<template> elements', () => {
|
describe('<template> elements', () => {
|
||||||
|
|
||||||
it('should move the content into a new <template> element and mark that as viewRoot', () => {
|
it('should move the content into a new <template> element and mark that as viewRoot', () => {
|
||||||
var rootElement = el('<div><template if="true">a</template></div>');
|
var rootElement = DOM.createTemplate('<template if="true">a</template>');
|
||||||
var results = createPipeline().process(rootElement);
|
var results = createPipeline().process(rootElement);
|
||||||
|
|
||||||
expect(stringifyElement(results[1].element))
|
expect(stringifyElement(results[1].element))
|
||||||
@ -38,32 +38,32 @@ export function main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should mark the new <template> element as viewRoot', () => {
|
it('should mark the new <template> element as viewRoot', () => {
|
||||||
var rootElement = el('<div><template if="true">a</template></div>');
|
var rootElement = DOM.createTemplate('<template if="true">a</template>');
|
||||||
var results = createPipeline().process(rootElement);
|
var results = createPipeline().process(rootElement);
|
||||||
expect(results[2].isViewRoot).toBe(true);
|
expect(results[2].isViewRoot).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not wrap the root element', () => {
|
it('should not wrap the root element', () => {
|
||||||
var rootElement = el('<div></div>');
|
var rootElement = DOM.createTemplate('');
|
||||||
var results = createPipeline().process(rootElement);
|
var results = createPipeline().process(rootElement);
|
||||||
expect(results.length).toBe(1);
|
expect(results.length).toBe(1);
|
||||||
expect(stringifyElement(rootElement)).toEqual('<div></div>');
|
expect(stringifyElement(rootElement)).toEqual('<template></template>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should copy over the elementDescription', () => {
|
it('should copy over the elementDescription', () => {
|
||||||
var rootElement = el('<div><template if="true">a</template></div>');
|
var rootElement = DOM.createTemplate('<template if="true">a</template>');
|
||||||
var results = createPipeline().process(rootElement);
|
var results = createPipeline().process(rootElement);
|
||||||
expect(results[2].elementDescription).toBe(results[1].elementDescription);
|
expect(results[2].elementDescription).toBe(results[1].elementDescription);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should clean out the inheritedElementBinder', () => {
|
it('should clean out the inheritedElementBinder', () => {
|
||||||
var rootElement = el('<div><template if="true">a</template></div>');
|
var rootElement = DOM.createTemplate('<template if="true">a</template>');
|
||||||
var results = createPipeline().process(rootElement);
|
var results = createPipeline().process(rootElement);
|
||||||
expect(results[2].inheritedElementBinder).toBe(null);
|
expect(results[2].inheritedElementBinder).toBe(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create a nestedProtoView', () => {
|
it('should create a nestedProtoView', () => {
|
||||||
var rootElement = el('<div><template if="true">a</template></div>');
|
var rootElement = DOM.createTemplate('<template if="true">a</template>');
|
||||||
var results = createPipeline().process(rootElement);
|
var results = createPipeline().process(rootElement);
|
||||||
expect(results[2].inheritedProtoView).not.toBe(null);
|
expect(results[2].inheritedProtoView).not.toBe(null);
|
||||||
expect(results[2].inheritedProtoView)
|
expect(results[2].inheritedProtoView)
|
||||||
@ -78,18 +78,19 @@ export function main() {
|
|||||||
describe('elements with template attribute', () => {
|
describe('elements with template attribute', () => {
|
||||||
|
|
||||||
it('should replace the element with an empty <template> element', () => {
|
it('should replace the element with an empty <template> element', () => {
|
||||||
var rootElement = el('<div><span template=""></span></div>');
|
var rootElement = DOM.createTemplate('<span template=""></span>');
|
||||||
var originalChild = rootElement.childNodes[0];
|
var originalChild = DOM.firstChild(DOM.content(rootElement));
|
||||||
var results = createPipeline().process(rootElement);
|
var results = createPipeline().process(rootElement);
|
||||||
expect(results[0].element).toBe(rootElement);
|
expect(results[0].element).toBe(rootElement);
|
||||||
expect(stringifyElement(results[0].element))
|
expect(stringifyElement(results[0].element))
|
||||||
.toEqual('<div><template class="ng-binding"></template></div>');
|
.toEqual('<template><template class="ng-binding"></template></template>');
|
||||||
expect(stringifyElement(results[2].element)).toEqual('<span template=""></span>');
|
expect(stringifyElement(results[2].element))
|
||||||
expect(results[2].element).toBe(originalChild);
|
.toEqual('<template><span template=""></span></template>');
|
||||||
|
expect(DOM.firstChild(DOM.content(results[2].element))).toBe(originalChild);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with top-level template node', () => {
|
it('should work with top-level template node', () => {
|
||||||
var rootElement = el('<template><div template>x</div></template>');
|
var rootElement = DOM.createTemplate('<div template>x</div>');
|
||||||
var originalChild = DOM.content(rootElement).childNodes[0];
|
var originalChild = DOM.content(rootElement).childNodes[0];
|
||||||
var results = createPipeline().process(rootElement);
|
var results = createPipeline().process(rootElement);
|
||||||
|
|
||||||
@ -98,17 +99,17 @@ export function main() {
|
|||||||
expect(results[2].isViewRoot).toBe(true);
|
expect(results[2].isViewRoot).toBe(true);
|
||||||
expect(stringifyElement(results[0].element))
|
expect(stringifyElement(results[0].element))
|
||||||
.toEqual('<template><template class="ng-binding"></template></template>');
|
.toEqual('<template><template class="ng-binding"></template></template>');
|
||||||
expect(results[2].element).toBe(originalChild);
|
expect(DOM.firstChild(DOM.content(results[2].element))).toBe(originalChild);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should mark the element as viewRoot', () => {
|
it('should mark the element as viewRoot', () => {
|
||||||
var rootElement = el('<div><div template></div></div>');
|
var rootElement = DOM.createTemplate('<div template></div>');
|
||||||
var results = createPipeline().process(rootElement);
|
var results = createPipeline().process(rootElement);
|
||||||
expect(results[2].isViewRoot).toBe(true);
|
expect(results[2].isViewRoot).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add property bindings from the template attribute', () => {
|
it('should add property bindings from the template attribute', () => {
|
||||||
var rootElement = el('<div><div template="some-prop:expr"></div></div>');
|
var rootElement = DOM.createTemplate('<div template="some-prop:expr"></div>');
|
||||||
var results = createPipeline().process(rootElement);
|
var results = createPipeline().process(rootElement);
|
||||||
expect(results[1].inheritedElementBinder.propertyBindings.get('someProp').source)
|
expect(results[1].inheritedElementBinder.propertyBindings.get('someProp').source)
|
||||||
.toEqual('expr');
|
.toEqual('expr');
|
||||||
@ -116,14 +117,14 @@ export function main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should add variable mappings from the template attribute to the nestedProtoView', () => {
|
it('should add variable mappings from the template attribute to the nestedProtoView', () => {
|
||||||
var rootElement = el('<div><div template="var var-name=mapName"></div></div>');
|
var rootElement = DOM.createTemplate('<div template="var var-name=mapName"></div>');
|
||||||
var results = createPipeline().process(rootElement);
|
var results = createPipeline().process(rootElement);
|
||||||
expect(results[2].inheritedProtoView.variableBindings)
|
expect(results[2].inheritedProtoView.variableBindings)
|
||||||
.toEqual(MapWrapper.createFromStringMap({'mapName': 'varName'}));
|
.toEqual(MapWrapper.createFromStringMap({'mapName': 'varName'}));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add entries without value as attributes to the element', () => {
|
it('should add entries without value as attributes to the element', () => {
|
||||||
var rootElement = el('<div><div template="varname"></div></div>');
|
var rootElement = DOM.createTemplate('<div template="varname"></div>');
|
||||||
var results = createPipeline().process(rootElement);
|
var results = createPipeline().process(rootElement);
|
||||||
expect(results[1].attrs().get('varname')).toEqual('');
|
expect(results[1].attrs().get('varname')).toEqual('');
|
||||||
expect(results[1].inheritedElementBinder.propertyBindings).toEqual(new Map());
|
expect(results[1].inheritedElementBinder.propertyBindings).toEqual(new Map());
|
||||||
@ -131,32 +132,32 @@ export function main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should iterate properly after a template dom modification', () => {
|
it('should iterate properly after a template dom modification', () => {
|
||||||
var rootElement = el('<div><div template></div><after></after></div>');
|
var rootElement = DOM.createTemplate('<div template></div><after></after>');
|
||||||
var results = createPipeline().process(rootElement);
|
var results = createPipeline().process(rootElement);
|
||||||
// 1 root + 2 initial + 1 generated template elements
|
// 1 root + 2 initial + 2 generated template elements
|
||||||
expect(results.length).toEqual(4);
|
expect(results.length).toEqual(5);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should copy over the elementDescription', () => {
|
it('should copy over the elementDescription', () => {
|
||||||
var rootElement = el('<div><span template=""></span></div>');
|
var rootElement = DOM.createTemplate('<span template=""></span>');
|
||||||
var results = createPipeline().process(rootElement);
|
var results = createPipeline().process(rootElement);
|
||||||
expect(results[2].elementDescription).toBe(results[1].elementDescription);
|
expect(results[2].elementDescription).toBe(results[1].elementDescription);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should clean out the inheritedElementBinder', () => {
|
it('should clean out the inheritedElementBinder', () => {
|
||||||
var rootElement = el('<div><span template=""></span></div>');
|
var rootElement = DOM.createTemplate('<span template=""></span>');
|
||||||
var results = createPipeline().process(rootElement);
|
var results = createPipeline().process(rootElement);
|
||||||
expect(results[2].inheritedElementBinder).toBe(null);
|
expect(results[2].inheritedElementBinder).toBe(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create a nestedProtoView', () => {
|
it('should create a nestedProtoView', () => {
|
||||||
var rootElement = el('<div><span template=""></span></div>');
|
var rootElement = DOM.createTemplate('<span template=""></span>');
|
||||||
var results = createPipeline().process(rootElement);
|
var results = createPipeline().process(rootElement);
|
||||||
expect(results[2].inheritedProtoView).not.toBe(null);
|
expect(results[2].inheritedProtoView).not.toBe(null);
|
||||||
expect(results[2].inheritedProtoView)
|
expect(results[2].inheritedProtoView)
|
||||||
.toBe(results[1].inheritedElementBinder.nestedProtoView);
|
.toBe(results[1].inheritedElementBinder.nestedProtoView);
|
||||||
expect(stringifyElement(results[2].inheritedProtoView.rootElement))
|
expect(stringifyElement(results[2].inheritedProtoView.rootElement))
|
||||||
.toEqual('<span template=""></span>');
|
.toEqual('<template><span template=""></span></template>');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -164,24 +165,25 @@ export function main() {
|
|||||||
describe('elements with *directive_name attribute', () => {
|
describe('elements with *directive_name attribute', () => {
|
||||||
|
|
||||||
it('should replace the element with an empty <template> element', () => {
|
it('should replace the element with an empty <template> element', () => {
|
||||||
var rootElement = el('<div><span *ng-if></span></div>');
|
var rootElement = DOM.createTemplate('<span *ng-if></span>');
|
||||||
var originalChild = rootElement.childNodes[0];
|
var originalChild = DOM.firstChild(DOM.content(rootElement));
|
||||||
var results = createPipeline().process(rootElement);
|
var results = createPipeline().process(rootElement);
|
||||||
expect(results[0].element).toBe(rootElement);
|
expect(results[0].element).toBe(rootElement);
|
||||||
expect(stringifyElement(results[0].element))
|
expect(stringifyElement(results[0].element))
|
||||||
.toEqual('<div><template class="ng-binding" ng-if=""></template></div>');
|
.toEqual('<template><template class="ng-binding" ng-if=""></template></template>');
|
||||||
expect(stringifyElement(results[2].element)).toEqual('<span *ng-if=""></span>');
|
expect(stringifyElement(results[2].element))
|
||||||
expect(results[2].element).toBe(originalChild);
|
.toEqual('<template><span *ng-if=""></span></template>');
|
||||||
|
expect(DOM.firstChild(DOM.content(results[2].element))).toBe(originalChild);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should mark the element as viewRoot', () => {
|
it('should mark the element as viewRoot', () => {
|
||||||
var rootElement = el('<div><div *foo="bar"></div></div>');
|
var rootElement = DOM.createTemplate('<div *foo="bar"></div>');
|
||||||
var results = createPipeline().process(rootElement);
|
var results = createPipeline().process(rootElement);
|
||||||
expect(results[2].isViewRoot).toBe(true);
|
expect(results[2].isViewRoot).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with top-level template node', () => {
|
it('should work with top-level template node', () => {
|
||||||
var rootElement = el('<template><div *foo>x</div></template>');
|
var rootElement = DOM.createTemplate('<div *foo>x</div>');
|
||||||
var originalChild = DOM.content(rootElement).childNodes[0];
|
var originalChild = DOM.content(rootElement).childNodes[0];
|
||||||
var results = createPipeline().process(rootElement);
|
var results = createPipeline().process(rootElement);
|
||||||
|
|
||||||
@ -190,11 +192,11 @@ export function main() {
|
|||||||
expect(results[2].isViewRoot).toBe(true);
|
expect(results[2].isViewRoot).toBe(true);
|
||||||
expect(stringifyElement(results[0].element))
|
expect(stringifyElement(results[0].element))
|
||||||
.toEqual('<template><template class="ng-binding" foo=""></template></template>');
|
.toEqual('<template><template class="ng-binding" foo=""></template></template>');
|
||||||
expect(results[2].element).toBe(originalChild);
|
expect(DOM.firstChild(DOM.content(results[2].element))).toBe(originalChild);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add property bindings from the template attribute', () => {
|
it('should add property bindings from the template attribute', () => {
|
||||||
var rootElement = el('<div><div *prop="expr"></div></div>');
|
var rootElement = DOM.createTemplate('<div *prop="expr"></div>');
|
||||||
var results = createPipeline().process(rootElement);
|
var results = createPipeline().process(rootElement);
|
||||||
expect(results[1].inheritedElementBinder.propertyBindings.get('prop').source)
|
expect(results[1].inheritedElementBinder.propertyBindings.get('prop').source)
|
||||||
.toEqual('expr');
|
.toEqual('expr');
|
||||||
@ -202,14 +204,14 @@ export function main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should add variable mappings from the template attribute to the nestedProtoView', () => {
|
it('should add variable mappings from the template attribute to the nestedProtoView', () => {
|
||||||
var rootElement = el('<div><div *foreach="var varName=mapName"></div></div>');
|
var rootElement = DOM.createTemplate('<div *foreach="var varName=mapName"></div>');
|
||||||
var results = createPipeline().process(rootElement);
|
var results = createPipeline().process(rootElement);
|
||||||
expect(results[2].inheritedProtoView.variableBindings)
|
expect(results[2].inheritedProtoView.variableBindings)
|
||||||
.toEqual(MapWrapper.createFromStringMap({'mapName': 'varName'}));
|
.toEqual(MapWrapper.createFromStringMap({'mapName': 'varName'}));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add entries without value as attribute to the element', () => {
|
it('should add entries without value as attribute to the element', () => {
|
||||||
var rootElement = el('<div><div *varname></div></div>');
|
var rootElement = DOM.createTemplate('<div *varname></div>');
|
||||||
var results = createPipeline().process(rootElement);
|
var results = createPipeline().process(rootElement);
|
||||||
expect(results[1].attrs().get('varname')).toEqual('');
|
expect(results[1].attrs().get('varname')).toEqual('');
|
||||||
expect(results[1].inheritedElementBinder.propertyBindings).toEqual(new Map());
|
expect(results[1].inheritedElementBinder.propertyBindings).toEqual(new Map());
|
||||||
@ -217,32 +219,32 @@ export function main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should iterate properly after a template dom modification', () => {
|
it('should iterate properly after a template dom modification', () => {
|
||||||
var rootElement = el('<div><div *foo></div><after></after></div>');
|
var rootElement = DOM.createTemplate('<div *foo></div><after></after>');
|
||||||
var results = createPipeline().process(rootElement);
|
var results = createPipeline().process(rootElement);
|
||||||
// 1 root + 2 initial + 1 generated template elements
|
// 1 root + 2 initial + 2 generated template elements
|
||||||
expect(results.length).toEqual(4);
|
expect(results.length).toEqual(5);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should copy over the elementDescription', () => {
|
it('should copy over the elementDescription', () => {
|
||||||
var rootElement = el('<div><span *foo></span></div>');
|
var rootElement = DOM.createTemplate('<span *foo></span>');
|
||||||
var results = createPipeline().process(rootElement);
|
var results = createPipeline().process(rootElement);
|
||||||
expect(results[2].elementDescription).toBe(results[1].elementDescription);
|
expect(results[2].elementDescription).toBe(results[1].elementDescription);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should clean out the inheritedElementBinder', () => {
|
it('should clean out the inheritedElementBinder', () => {
|
||||||
var rootElement = el('<div><span *foo></span></div>');
|
var rootElement = DOM.createTemplate('<span *foo></span>');
|
||||||
var results = createPipeline().process(rootElement);
|
var results = createPipeline().process(rootElement);
|
||||||
expect(results[2].inheritedElementBinder).toBe(null);
|
expect(results[2].inheritedElementBinder).toBe(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create a nestedProtoView', () => {
|
it('should create a nestedProtoView', () => {
|
||||||
var rootElement = el('<div><span *foo></span></div>');
|
var rootElement = DOM.createTemplate('<span *foo></span>');
|
||||||
var results = createPipeline().process(rootElement);
|
var results = createPipeline().process(rootElement);
|
||||||
expect(results[2].inheritedProtoView).not.toBe(null);
|
expect(results[2].inheritedProtoView).not.toBe(null);
|
||||||
expect(results[2].inheritedProtoView)
|
expect(results[2].inheritedProtoView)
|
||||||
.toBe(results[1].inheritedElementBinder.nestedProtoView);
|
.toBe(results[1].inheritedElementBinder.nestedProtoView);
|
||||||
expect(stringifyElement(results[2].inheritedProtoView.rootElement))
|
expect(stringifyElement(results[2].inheritedProtoView.rootElement))
|
||||||
.toEqual('<span *foo=""></span>');
|
.toEqual('<template><span *foo=""></span></template>');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -16,10 +16,11 @@ import {
|
|||||||
import {MapWrapper} from 'angular2/src/facade/collection';
|
import {MapWrapper} from 'angular2/src/facade/collection';
|
||||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||||
|
|
||||||
import {DomTestbed, TestView, elRef} from './dom_testbed';
|
import {DomTestbed, TestRootView, elRef} from './dom_testbed';
|
||||||
|
|
||||||
import {ViewDefinition, DirectiveMetadata, RenderViewRef} from 'angular2/src/render/api';
|
import {ViewDefinition, DirectiveMetadata, RenderViewRef} from 'angular2/src/render/api';
|
||||||
import {DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES} from 'angular2/src/render/dom/dom_renderer';
|
import {DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES} from 'angular2/src/render/dom/dom_renderer';
|
||||||
|
import {ShadowDomStrategy, NativeShadowDomStrategy} from 'angular2/render';
|
||||||
import {bind} from 'angular2/di';
|
import {bind} from 'angular2/di';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
@ -27,101 +28,69 @@ export function main() {
|
|||||||
beforeEachBindings(() => [DomTestbed]);
|
beforeEachBindings(() => [DomTestbed]);
|
||||||
|
|
||||||
it('should create and destroy root host views while using the given elements in place',
|
it('should create and destroy root host views while using the given elements in place',
|
||||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
inject([AsyncTestCompleter, DomTestbed], (async, tb: DomTestbed) => {
|
||||||
tb.compiler.compileHost(someComponent)
|
tb.compiler.compileHost(someComponent)
|
||||||
.then((hostProtoViewDto) => {
|
.then((hostProtoViewDto) => {
|
||||||
var view =
|
var view = new TestRootView(
|
||||||
new TestView(tb.renderer.createRootHostView(hostProtoViewDto.render, '#root'));
|
tb.renderer.createRootHostView(hostProtoViewDto.render, 0, '#root'));
|
||||||
expect(view.rawView.rootNodes[0]).toEqual(tb.rootEl);
|
|
||||||
|
|
||||||
tb.renderer.destroyView(view.viewRef);
|
|
||||||
// destroying a root view should not disconnect it!
|
|
||||||
expect(tb.rootEl.parentNode).toBeTruthy();
|
expect(tb.rootEl.parentNode).toBeTruthy();
|
||||||
|
expect(view.hostElement).toEqual(tb.rootEl);
|
||||||
|
|
||||||
|
tb.renderer.detachFragment(view.fragments[0]);
|
||||||
|
tb.renderer.destroyView(view.viewRef);
|
||||||
|
expect(tb.rootEl.parentNode).toBeFalsy();
|
||||||
|
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should attach and detach component views',
|
it('should update text nodes',
|
||||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
inject([AsyncTestCompleter, DomTestbed], (async, tb: DomTestbed) => {
|
||||||
tb.compileAll([
|
tb.compileAndMerge(
|
||||||
someComponent,
|
someComponent,
|
||||||
new ViewDefinition({componentId: 'someComponent', template: 'hello', directives: []})
|
[
|
||||||
])
|
new ViewDefinition(
|
||||||
.then((protoViewDtos) => {
|
{componentId: 'someComponent', template: '{{a}}', directives: []})
|
||||||
var rootView = tb.createRootView(protoViewDtos[0]);
|
])
|
||||||
var cmpView = tb.createComponentView(rootView.viewRef, 0, protoViewDtos[1]);
|
.then((protoViewMergeMappings) => {
|
||||||
expect(tb.rootEl).toHaveText('hello');
|
var rootView = tb.createView(protoViewMergeMappings[0]);
|
||||||
tb.destroyComponentView(rootView.viewRef, 0, cmpView.viewRef);
|
|
||||||
expect(tb.rootEl).toHaveText('');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should not create LightDom instances if the host element is empty',
|
tb.renderer.setText(rootView.viewRef, 0, 'hello');
|
||||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
expect(rootView.hostElement).toHaveText('hello');
|
||||||
tb.compileAll([
|
|
||||||
someComponent,
|
|
||||||
new ViewDefinition({
|
|
||||||
componentId: 'someComponent',
|
|
||||||
template: '<some-comp> <!-- comment -->\n </some-comp>',
|
|
||||||
directives: [someComponent]
|
|
||||||
})
|
|
||||||
])
|
|
||||||
.then((protoViewDtos) => {
|
|
||||||
var rootView = tb.createRootView(protoViewDtos[0]);
|
|
||||||
var cmpView = tb.createComponentView(rootView.viewRef, 0, protoViewDtos[1]);
|
|
||||||
expect(cmpView.rawView.proto.elementBinders[0].componentId).toBe('someComponent');
|
|
||||||
expect(cmpView.rawView.boundElements[0].lightDom).toBe(null);
|
|
||||||
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should update text nodes', inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
|
||||||
tb.compileAll([
|
|
||||||
someComponent,
|
|
||||||
new ViewDefinition({componentId: 'someComponent', template: '{{a}}', directives: []})
|
|
||||||
])
|
|
||||||
.then((protoViewDtos) => {
|
|
||||||
var rootView = tb.createRootView(protoViewDtos[0]);
|
|
||||||
var cmpView = tb.createComponentView(rootView.viewRef, 0, protoViewDtos[1]);
|
|
||||||
|
|
||||||
tb.renderer.setText(cmpView.viewRef, 0, 'hello');
|
|
||||||
expect(tb.rootEl).toHaveText('hello');
|
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should update any element property/attributes/class/style independent of the compilation',
|
it('should update any element property/attributes/class/style independent of the compilation',
|
||||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
inject([AsyncTestCompleter, DomTestbed], (async, tb: DomTestbed) => {
|
||||||
tb.compileAll([
|
tb.compileAndMerge(someComponent,
|
||||||
someComponent,
|
[
|
||||||
new ViewDefinition({
|
new ViewDefinition({
|
||||||
componentId: 'someComponent',
|
componentId: 'someComponent',
|
||||||
template: '<input [title]="y" style="position:absolute">',
|
template: '<input [title]="y" style="position:absolute">',
|
||||||
directives: []
|
directives: []
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
.then((protoViewDtos) => {
|
.then((protoViewMergeMappings) => {
|
||||||
var rootView = tb.createRootView(protoViewDtos[0]);
|
var rootView = tb.createView(protoViewMergeMappings[0]);
|
||||||
var cmpView = tb.createComponentView(rootView.viewRef, 0, protoViewDtos[1]);
|
|
||||||
|
|
||||||
var el = DOM.childNodes(tb.rootEl)[0];
|
var elr = elRef(rootView.viewRef, 1);
|
||||||
tb.renderer.setElementProperty(elRef(cmpView.viewRef, 0), 'value', 'hello');
|
var el = DOM.childNodes(rootView.hostElement)[0];
|
||||||
|
tb.renderer.setElementProperty(elr, 'value', 'hello');
|
||||||
expect((<HTMLInputElement>el).value).toEqual('hello');
|
expect((<HTMLInputElement>el).value).toEqual('hello');
|
||||||
|
|
||||||
tb.renderer.setElementClass(elRef(cmpView.viewRef, 0), 'a', true);
|
tb.renderer.setElementClass(elr, 'a', true);
|
||||||
expect((<HTMLInputElement>DOM.childNodes(tb.rootEl)[0]).value).toEqual('hello');
|
expect((<HTMLInputElement>DOM.childNodes(rootView.hostElement)[0]).value)
|
||||||
tb.renderer.setElementClass(elRef(cmpView.viewRef, 0), 'a', false);
|
.toEqual('hello');
|
||||||
|
tb.renderer.setElementClass(elr, 'a', false);
|
||||||
expect(DOM.hasClass(el, 'a')).toBe(false);
|
expect(DOM.hasClass(el, 'a')).toBe(false);
|
||||||
|
|
||||||
tb.renderer.setElementStyle(elRef(cmpView.viewRef, 0), 'width', '10px');
|
tb.renderer.setElementStyle(elr, 'width', '10px');
|
||||||
expect(DOM.getStyle(el, 'width')).toEqual('10px');
|
expect(DOM.getStyle(el, 'width')).toEqual('10px');
|
||||||
tb.renderer.setElementStyle(elRef(cmpView.viewRef, 0), 'width', null);
|
tb.renderer.setElementStyle(elr, 'width', null);
|
||||||
expect(DOM.getStyle(el, 'width')).toEqual('');
|
expect(DOM.getStyle(el, 'width')).toEqual('');
|
||||||
|
|
||||||
tb.renderer.setElementAttribute(elRef(cmpView.viewRef, 0), 'someAttr', 'someValue');
|
tb.renderer.setElementAttribute(elr, 'someAttr', 'someValue');
|
||||||
expect(DOM.getAttribute(el, 'some-attr')).toEqual('someValue');
|
expect(DOM.getAttribute(el, 'some-attr')).toEqual('someValue');
|
||||||
|
|
||||||
async.done();
|
async.done();
|
||||||
@ -131,16 +100,18 @@ export function main() {
|
|||||||
|
|
||||||
it('should NOT reflect property values as attributes if flag is NOT set',
|
it('should NOT reflect property values as attributes if flag is NOT set',
|
||||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
||||||
tb.compileAll([
|
tb.compileAndMerge(someComponent,
|
||||||
someComponent,
|
[
|
||||||
new ViewDefinition(
|
new ViewDefinition({
|
||||||
{componentId: 'someComponent', template: '<input [title]="y">', directives: []})
|
componentId: 'someComponent',
|
||||||
])
|
template: '<input [title]="y">',
|
||||||
.then((protoViewDtos) => {
|
directives: []
|
||||||
var rootView = tb.createRootView(protoViewDtos[0]);
|
})
|
||||||
var cmpView = tb.createComponentView(rootView.viewRef, 0, protoViewDtos[1]);
|
])
|
||||||
var el = DOM.childNodes(tb.rootEl)[0];
|
.then((protoViewMergeMappings) => {
|
||||||
tb.renderer.setElementProperty(elRef(cmpView.viewRef, 0), 'maxLength', '20');
|
var rootView = tb.createView(protoViewMergeMappings[0]);
|
||||||
|
var el = DOM.childNodes(rootView.hostElement)[0];
|
||||||
|
tb.renderer.setElementProperty(elRef(rootView.viewRef, 1), 'maxLength', '20');
|
||||||
expect(DOM.getAttribute(<HTMLInputElement>el, 'ng-reflect-max-length'))
|
expect(DOM.getAttribute(<HTMLInputElement>el, 'ng-reflect-max-length'))
|
||||||
.toEqual(null);
|
.toEqual(null);
|
||||||
|
|
||||||
@ -150,21 +121,21 @@ export function main() {
|
|||||||
|
|
||||||
describe('reflection', () => {
|
describe('reflection', () => {
|
||||||
beforeEachBindings(() => [bind(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES).toValue(true)]);
|
beforeEachBindings(() => [bind(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES).toValue(true)]);
|
||||||
|
|
||||||
it('should reflect property values as attributes if flag is set',
|
it('should reflect property values as attributes if flag is set',
|
||||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
||||||
tb.compileAll([
|
tb.compileAndMerge(someComponent,
|
||||||
someComponent,
|
[
|
||||||
new ViewDefinition({
|
new ViewDefinition({
|
||||||
componentId: 'someComponent',
|
componentId: 'someComponent',
|
||||||
template: '<input [title]="y">',
|
template: '<input [title]="y">',
|
||||||
directives: []
|
directives: []
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
.then((protoViewDtos) => {
|
.then((protoViewMergeMappings) => {
|
||||||
var rootView = tb.createRootView(protoViewDtos[0]);
|
var rootView = tb.createView(protoViewMergeMappings[0]);
|
||||||
var cmpView = tb.createComponentView(rootView.viewRef, 0, protoViewDtos[1]);
|
var el = DOM.childNodes(rootView.hostElement)[0];
|
||||||
var el = DOM.childNodes(tb.rootEl)[0];
|
tb.renderer.setElementProperty(elRef(rootView.viewRef, 1), 'maxLength', '20');
|
||||||
tb.renderer.setElementProperty(elRef(cmpView.viewRef, 0), 'maxLength', '20');
|
|
||||||
expect(DOM.getAttribute(<HTMLInputElement>el, 'ng-reflect-max-length'))
|
expect(DOM.getAttribute(<HTMLInputElement>el, 'ng-reflect-max-length'))
|
||||||
.toEqual('20');
|
.toEqual('20');
|
||||||
async.done();
|
async.done();
|
||||||
@ -174,70 +145,69 @@ export function main() {
|
|||||||
|
|
||||||
if (DOM.supportsDOMEvents()) {
|
if (DOM.supportsDOMEvents()) {
|
||||||
it('should call actions on the element independent of the compilation',
|
it('should call actions on the element independent of the compilation',
|
||||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
inject([AsyncTestCompleter, DomTestbed], (async, tb: DomTestbed) => {
|
||||||
tb.compileAll([
|
tb.compileAndMerge(someComponent,
|
||||||
someComponent,
|
[
|
||||||
new ViewDefinition({
|
new ViewDefinition({
|
||||||
componentId: 'someComponent',
|
componentId: 'someComponent',
|
||||||
template: '<input [title]="y"></input>',
|
template: '<input [title]="y"></input>',
|
||||||
directives: []
|
directives: []
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
.then((protoViewDtos) => {
|
.then((protoViewMergeMappings) => {
|
||||||
var views = tb.createRootViews(protoViewDtos);
|
var rootView = tb.createView(protoViewMergeMappings[0]);
|
||||||
var componentView = views[1];
|
|
||||||
|
|
||||||
tb.renderer.invokeElementMethod(elRef(componentView.viewRef, 0), 'setAttribute',
|
tb.renderer.invokeElementMethod(elRef(rootView.viewRef, 1), 'setAttribute',
|
||||||
['a', 'b']);
|
['a', 'b']);
|
||||||
|
|
||||||
expect(DOM.getAttribute(DOM.childNodes(tb.rootEl)[0], 'a')).toEqual('b');
|
expect(DOM.getAttribute(DOM.childNodes(rootView.hostElement)[0], 'a'))
|
||||||
|
.toEqual('b');
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should add and remove views to and from containers',
|
it('should add and remove fragments',
|
||||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
inject([AsyncTestCompleter, DomTestbed], (async, tb: DomTestbed) => {
|
||||||
tb.compileAll([
|
tb.compileAndMerge(someComponent,
|
||||||
someComponent,
|
[
|
||||||
new ViewDefinition({
|
new ViewDefinition({
|
||||||
componentId: 'someComponent',
|
componentId: 'someComponent',
|
||||||
template: '<template>hello</template>',
|
template: '<template>hello</template>',
|
||||||
directives: []
|
directives: []
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
.then((protoViewDtos) => {
|
.then((protoViewMergeMappings) => {
|
||||||
var rootView = tb.createRootView(protoViewDtos[0]);
|
var rootView = tb.createView(protoViewMergeMappings[0]);
|
||||||
var cmpView = tb.createComponentView(rootView.viewRef, 0, protoViewDtos[1]);
|
|
||||||
|
|
||||||
var childProto = protoViewDtos[1].elementBinders[0].nestedProtoView;
|
var elr = elRef(rootView.viewRef, 1);
|
||||||
expect(tb.rootEl).toHaveText('');
|
expect(rootView.hostElement).toHaveText('');
|
||||||
var childView = tb.createViewInContainer(cmpView.viewRef, 0, 0, childProto);
|
var fragment = rootView.fragments[1];
|
||||||
expect(tb.rootEl).toHaveText('hello');
|
tb.renderer.attachFragmentAfterElement(elr, fragment);
|
||||||
tb.destroyViewInContainer(cmpView.viewRef, 0, 0, childView.viewRef);
|
expect(rootView.hostElement).toHaveText('hello');
|
||||||
expect(tb.rootEl).toHaveText('');
|
tb.renderer.detachFragment(fragment);
|
||||||
|
expect(rootView.hostElement).toHaveText('');
|
||||||
|
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should handle events', inject([AsyncTestCompleter, DomTestbed], (async, tb: DomTestbed) => {
|
it('should handle events', inject([AsyncTestCompleter, DomTestbed], (async, tb: DomTestbed) => {
|
||||||
tb.compileAll([
|
tb.compileAndMerge(someComponent,
|
||||||
someComponent,
|
[
|
||||||
new ViewDefinition({
|
new ViewDefinition({
|
||||||
componentId: 'someComponent',
|
componentId: 'someComponent',
|
||||||
template: '<input (change)="doSomething()">',
|
template: '<input (change)="doSomething()">',
|
||||||
directives: []
|
directives: []
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
.then((protoViewDtos) => {
|
.then((protoViewDtos) => {
|
||||||
var rootView = tb.createRootView(protoViewDtos[0]);
|
var rootView = tb.createView(protoViewDtos[0]);
|
||||||
var cmpView = tb.createComponentView(rootView.viewRef, 0, protoViewDtos[1]);
|
|
||||||
|
|
||||||
tb.triggerEvent(cmpView.viewRef, 0, 'change');
|
tb.triggerEvent(elRef(rootView.viewRef, 1), 'change');
|
||||||
var eventEntry = cmpView.events[0];
|
var eventEntry = rootView.events[0];
|
||||||
// bound element index
|
// bound element index
|
||||||
expect(eventEntry[0]).toEqual(0);
|
expect(eventEntry[0]).toEqual(1);
|
||||||
// event type
|
// event type
|
||||||
expect(eventEntry[1]).toEqual('change');
|
expect(eventEntry[1]).toEqual('change');
|
||||||
// actual event
|
// actual event
|
||||||
@ -247,6 +217,30 @@ export function main() {
|
|||||||
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
if (DOM.supportsNativeShadowDOM()) {
|
||||||
|
describe('native shadow dom support', () => {
|
||||||
|
beforeEachBindings(
|
||||||
|
() => { return [bind(ShadowDomStrategy).toValue(new NativeShadowDomStrategy())]; });
|
||||||
|
|
||||||
|
it('should support shadow dom components',
|
||||||
|
inject([AsyncTestCompleter, DomTestbed], (async, tb: DomTestbed) => {
|
||||||
|
tb.compileAndMerge(
|
||||||
|
someComponent,
|
||||||
|
[
|
||||||
|
new ViewDefinition(
|
||||||
|
{componentId: 'someComponent', template: 'hello', directives: []})
|
||||||
|
])
|
||||||
|
.then((protoViewMergeMappings) => {
|
||||||
|
var rootView = tb.createView(protoViewMergeMappings[0]);
|
||||||
|
expect(DOM.getShadowRoot(rootView.hostElement)).toHaveText('hello');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,48 +1,57 @@
|
|||||||
import {Inject, Injectable} from 'angular2/di';
|
import {Inject, Injectable} from 'angular2/di';
|
||||||
|
import {isPresent} from 'angular2/src/facade/lang';
|
||||||
import {MapWrapper, ListWrapper, List, Map} from 'angular2/src/facade/collection';
|
import {MapWrapper, ListWrapper, List, Map} from 'angular2/src/facade/collection';
|
||||||
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
|
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
|
||||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||||
|
|
||||||
import {DomRenderer, DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer';
|
import {DomRenderer, DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer';
|
||||||
import {DefaultDomCompiler} from 'angular2/src/render/dom/compiler/compiler';
|
import {DefaultDomCompiler} from 'angular2/src/render/dom/compiler/compiler';
|
||||||
import {DomView} from 'angular2/src/render/dom/view/view';
|
|
||||||
import {
|
import {
|
||||||
|
RenderViewWithFragments,
|
||||||
|
RenderFragmentRef,
|
||||||
RenderViewRef,
|
RenderViewRef,
|
||||||
ProtoViewDto,
|
ProtoViewDto,
|
||||||
ViewDefinition,
|
ViewDefinition,
|
||||||
EventDispatcher,
|
RenderEventDispatcher,
|
||||||
DirectiveMetadata,
|
DirectiveMetadata,
|
||||||
RenderElementRef
|
RenderElementRef,
|
||||||
|
RenderProtoViewMergeMapping,
|
||||||
|
RenderProtoViewRef
|
||||||
} from 'angular2/src/render/api';
|
} from 'angular2/src/render/api';
|
||||||
import {resolveInternalDomView} from 'angular2/src/render/dom/view/view';
|
import {resolveInternalDomView} from 'angular2/src/render/dom/view/view';
|
||||||
|
import {resolveInternalDomFragment} from 'angular2/src/render/dom/view/fragment';
|
||||||
import {el, dispatchEvent} from 'angular2/test_lib';
|
import {el, dispatchEvent} from 'angular2/test_lib';
|
||||||
|
|
||||||
export class TestView {
|
export class TestRootView {
|
||||||
rawView: DomView;
|
|
||||||
viewRef: RenderViewRef;
|
viewRef: RenderViewRef;
|
||||||
|
fragments: RenderFragmentRef[];
|
||||||
|
hostElement: Element;
|
||||||
events: List<List<any>>;
|
events: List<List<any>>;
|
||||||
|
|
||||||
constructor(viewRef: RenderViewRef) {
|
constructor(viewWithFragments: RenderViewWithFragments) {
|
||||||
this.viewRef = viewRef;
|
this.viewRef = viewWithFragments.viewRef;
|
||||||
this.rawView = resolveInternalDomView(viewRef);
|
this.fragments = viewWithFragments.fragmentRefs;
|
||||||
|
this.hostElement = <Element>resolveInternalDomFragment(this.fragments[0])[0];
|
||||||
this.events = [];
|
this.events = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class TestRenderElementRef implements RenderElementRef {
|
||||||
|
constructor(public renderView: RenderViewRef, public renderBoundElementIndex: number) {}
|
||||||
|
}
|
||||||
|
|
||||||
export function elRef(renderView: RenderViewRef, boundElementIndex: number) {
|
export function elRef(renderView: RenderViewRef, boundElementIndex: number) {
|
||||||
return new TestRenderElementRef(renderView, boundElementIndex);
|
return new TestRenderElementRef(renderView, boundElementIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestRenderElementRef implements RenderElementRef {
|
export function rootNodes(view: RenderViewRef) {}
|
||||||
constructor(public renderView: RenderViewRef, public boundElementIndex: number) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
class LoggingEventDispatcher implements EventDispatcher {
|
class LoggingEventDispatcher implements RenderEventDispatcher {
|
||||||
log: List<List<any>>;
|
log: List<List<any>>;
|
||||||
|
|
||||||
constructor(log: List<List<any>>) { this.log = log; }
|
constructor(log: List<List<any>>) { this.log = log; }
|
||||||
|
|
||||||
dispatchEvent(elementIndex: number, eventName: string, locals: Map<string, any>) {
|
dispatchRenderEvent(elementIndex: number, eventName: string, locals: Map<string, any>) {
|
||||||
this.log.push([elementIndex, eventName, locals]);
|
this.log.push([elementIndex, eventName, locals]);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -67,71 +76,62 @@ export class DomTestbed {
|
|||||||
DOM.appendChild(DOM.querySelector(document, 'body'), this.rootEl);
|
DOM.appendChild(DOM.querySelector(document, 'body'), this.rootEl);
|
||||||
}
|
}
|
||||||
|
|
||||||
compileAll(directivesOrViewDefinitions:
|
compile(host: DirectiveMetadata, componentViews: ViewDefinition[]): Promise<ProtoViewDto[]> {
|
||||||
List<DirectiveMetadata | ViewDefinition>): Promise<List<ProtoViewDto>> {
|
var promises = [this.compiler.compileHost(host)];
|
||||||
return PromiseWrapper.all(ListWrapper.map(directivesOrViewDefinitions, (entry) => {
|
componentViews.forEach(view => promises.push(this.compiler.compile(view)));
|
||||||
if (entry instanceof DirectiveMetadata) {
|
return PromiseWrapper.all(promises);
|
||||||
return this.compiler.compileHost(entry);
|
|
||||||
} else {
|
|
||||||
return this.compiler.compile(entry);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_createTestView(viewRef: RenderViewRef) {
|
merge(protoViews:
|
||||||
var testView = new TestView(viewRef);
|
List<ProtoViewDto | RenderProtoViewRef>): Promise<RenderProtoViewMergeMapping[]> {
|
||||||
this.renderer.setEventDispatcher(viewRef, new LoggingEventDispatcher(testView.events));
|
return this.compiler.mergeProtoViewsRecursively(collectMergeRenderProtoViewsRecurse(
|
||||||
|
<ProtoViewDto>protoViews[0], ListWrapper.slice(protoViews, 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
compileAndMerge(host: DirectiveMetadata,
|
||||||
|
componentViews: ViewDefinition[]): Promise<RenderProtoViewMergeMapping[]> {
|
||||||
|
return this.compile(host, componentViews).then(protoViewDtos => this.merge(protoViewDtos));
|
||||||
|
}
|
||||||
|
|
||||||
|
_createTestView(viewWithFragments: RenderViewWithFragments) {
|
||||||
|
var testView = new TestRootView(viewWithFragments);
|
||||||
|
this.renderer.setEventDispatcher(viewWithFragments.viewRef,
|
||||||
|
new LoggingEventDispatcher(testView.events));
|
||||||
return testView;
|
return testView;
|
||||||
}
|
}
|
||||||
|
|
||||||
createRootView(rootProtoView: ProtoViewDto): TestView {
|
createView(protoView: RenderProtoViewMergeMapping): TestRootView {
|
||||||
var viewRef = this.renderer.createRootHostView(rootProtoView.render, '#root');
|
var viewWithFragments = this.renderer.createView(protoView.mergedProtoViewRef, 0);
|
||||||
this.renderer.hydrateView(viewRef);
|
this.renderer.hydrateView(viewWithFragments.viewRef);
|
||||||
return this._createTestView(viewRef);
|
return this._createTestView(viewWithFragments);
|
||||||
}
|
}
|
||||||
|
|
||||||
createComponentView(parentViewRef: RenderViewRef, boundElementIndex: number,
|
triggerEvent(elementRef: RenderElementRef, eventName: string) {
|
||||||
componentProtoView: ProtoViewDto): TestView {
|
var element = resolveInternalDomView(elementRef.renderView)
|
||||||
var componentViewRef = this.renderer.createView(componentProtoView.render);
|
.boundElements[elementRef.renderBoundElementIndex];
|
||||||
this.renderer.attachComponentView(elRef(parentViewRef, boundElementIndex), componentViewRef);
|
|
||||||
this.renderer.hydrateView(componentViewRef);
|
|
||||||
return this._createTestView(componentViewRef);
|
|
||||||
}
|
|
||||||
|
|
||||||
createRootViews(protoViews: List<ProtoViewDto>): List<TestView> {
|
|
||||||
var views = [];
|
|
||||||
var lastView = this.createRootView(protoViews[0]);
|
|
||||||
views.push(lastView);
|
|
||||||
for (var i = 1; i < protoViews.length; i++) {
|
|
||||||
lastView = this.createComponentView(lastView.viewRef, 0, protoViews[i]);
|
|
||||||
views.push(lastView);
|
|
||||||
}
|
|
||||||
return views;
|
|
||||||
}
|
|
||||||
|
|
||||||
destroyComponentView(parentViewRef: RenderViewRef, boundElementIndex: number,
|
|
||||||
componentView: RenderViewRef) {
|
|
||||||
this.renderer.dehydrateView(componentView);
|
|
||||||
this.renderer.detachComponentView(elRef(parentViewRef, boundElementIndex), componentView);
|
|
||||||
}
|
|
||||||
|
|
||||||
createViewInContainer(parentViewRef: RenderViewRef, boundElementIndex: number, atIndex: number,
|
|
||||||
protoView: ProtoViewDto): TestView {
|
|
||||||
var viewRef = this.renderer.createView(protoView.render);
|
|
||||||
this.renderer.attachViewInContainer(elRef(parentViewRef, boundElementIndex), atIndex, viewRef);
|
|
||||||
this.renderer.hydrateView(viewRef);
|
|
||||||
return this._createTestView(viewRef);
|
|
||||||
}
|
|
||||||
|
|
||||||
destroyViewInContainer(parentViewRef: RenderViewRef, boundElementIndex: number, atIndex: number,
|
|
||||||
viewRef: RenderViewRef) {
|
|
||||||
this.renderer.dehydrateView(viewRef);
|
|
||||||
this.renderer.detachViewInContainer(elRef(parentViewRef, boundElementIndex), atIndex, viewRef);
|
|
||||||
this.renderer.destroyView(viewRef);
|
|
||||||
}
|
|
||||||
|
|
||||||
triggerEvent(viewRef: RenderViewRef, boundElementIndex: number, eventName: string) {
|
|
||||||
var element = resolveInternalDomView(viewRef).boundElements[boundElementIndex].element;
|
|
||||||
dispatchEvent(element, eventName);
|
dispatchEvent(element, eventName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function collectMergeRenderProtoViewsRecurse(current: ProtoViewDto,
|
||||||
|
components: List<ProtoViewDto | RenderProtoViewRef>):
|
||||||
|
List<RenderProtoViewRef | List<any>> {
|
||||||
|
var result = [current.render];
|
||||||
|
current.elementBinders.forEach((elementBinder) => {
|
||||||
|
if (isPresent(elementBinder.nestedProtoView)) {
|
||||||
|
result.push(collectMergeRenderProtoViewsRecurse(elementBinder.nestedProtoView, components));
|
||||||
|
} else if (elementBinder.directives.length > 0) {
|
||||||
|
if (components.length > 0) {
|
||||||
|
var comp = components.shift();
|
||||||
|
if (comp instanceof ProtoViewDto) {
|
||||||
|
result.push(collectMergeRenderProtoViewsRecurse(comp, components));
|
||||||
|
} else {
|
||||||
|
result.push(comp);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.push(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
@ -1,55 +0,0 @@
|
|||||||
import {
|
|
||||||
describe,
|
|
||||||
beforeEach,
|
|
||||||
it,
|
|
||||||
expect,
|
|
||||||
ddescribe,
|
|
||||||
iit,
|
|
||||||
SpyObject,
|
|
||||||
el,
|
|
||||||
proxy
|
|
||||||
} from 'angular2/test_lib';
|
|
||||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
|
||||||
import {Content} from 'angular2/src/render/dom/shadow_dom/content_tag';
|
|
||||||
|
|
||||||
var _scriptStart = `<script start=""></script>`;
|
|
||||||
var _scriptEnd = `<script end=""></script>`;
|
|
||||||
|
|
||||||
export function main() {
|
|
||||||
describe('Content', function() {
|
|
||||||
var parent;
|
|
||||||
var content;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
parent = el(`<div>${_scriptStart}${_scriptEnd}`);
|
|
||||||
content = DOM.firstChild(parent);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should insert the nodes", () => {
|
|
||||||
var c = new Content(content, '');
|
|
||||||
c.init(null);
|
|
||||||
c.insert([el("<a></a>"), el("<b></b>")])
|
|
||||||
|
|
||||||
expect(DOM.getInnerHTML(parent))
|
|
||||||
.toEqual(`${_scriptStart}<a></a><b></b>${_scriptEnd}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should remove the nodes from the previous insertion", () => {
|
|
||||||
var c = new Content(content, '');
|
|
||||||
c.init(null);
|
|
||||||
c.insert([el("<a></a>")]);
|
|
||||||
c.insert([el("<b></b>")]);
|
|
||||||
|
|
||||||
expect(DOM.getInnerHTML(parent)).toEqual(`${_scriptStart}<b></b>${_scriptEnd}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should insert empty list", () => {
|
|
||||||
var c = new Content(content, '');
|
|
||||||
c.init(null);
|
|
||||||
c.insert([el("<a></a>")]);
|
|
||||||
c.insert([]);
|
|
||||||
|
|
||||||
expect(DOM.getInnerHTML(parent)).toEqual(`${_scriptStart}${_scriptEnd}`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
@ -32,10 +32,8 @@ export function main() {
|
|||||||
resetShadowDomCache();
|
resetShadowDomCache();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should use the host element as shadow root', () => {
|
it('should report that this is not the native strategy',
|
||||||
var host = el('<div><span>original content</span></div>');
|
() => { expect(strategy.hasNativeContentElement()).toBe(false); });
|
||||||
expect(strategy.prepareShadowRoot(host)).toBe(host);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should scope styles', () => {
|
it('should scope styles', () => {
|
||||||
var styleElement = el('<style>.foo {} :host {}</style>');
|
var styleElement = el('<style>.foo {} :host {}</style>');
|
||||||
|
@ -34,10 +34,8 @@ export function main() {
|
|||||||
resetShadowDomCache();
|
resetShadowDomCache();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should use the host element as shadow root', () => {
|
it('should report that this is not the native strategy',
|
||||||
var host = el('<div><span>original content</span></div>');
|
() => { expect(strategy.hasNativeContentElement()).toBe(false); });
|
||||||
expect(strategy.prepareShadowRoot(host)).toBe(host);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should move the style element to the style host', () => {
|
it('should move the style element to the style host', () => {
|
||||||
var compileElement = el('<div><style>.one {}</style></div>');
|
var compileElement = el('<div><style>.one {}</style></div>');
|
||||||
|
@ -1,253 +0,0 @@
|
|||||||
import {
|
|
||||||
describe,
|
|
||||||
beforeEach,
|
|
||||||
it,
|
|
||||||
expect,
|
|
||||||
ddescribe,
|
|
||||||
iit,
|
|
||||||
SpyObject,
|
|
||||||
el,
|
|
||||||
proxy,
|
|
||||||
stringifyElement
|
|
||||||
} from 'angular2/test_lib';
|
|
||||||
import {IMPLEMENTS, isBlank, isPresent} from 'angular2/src/facade/lang';
|
|
||||||
import {ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
|
|
||||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
|
||||||
import {Content} from 'angular2/src/render/dom/shadow_dom/content_tag';
|
|
||||||
import {LightDom} from 'angular2/src/render/dom/shadow_dom/light_dom';
|
|
||||||
import {DomView} from 'angular2/src/render/dom/view/view';
|
|
||||||
import {DomProtoView} from 'angular2/src/render/dom/view/proto_view';
|
|
||||||
import {DomViewContainer} from 'angular2/src/render/dom/view/view_container';
|
|
||||||
import {DomElement} from 'angular2/src/render/dom/view/element';
|
|
||||||
|
|
||||||
@proxy
|
|
||||||
@IMPLEMENTS(DomProtoView)
|
|
||||||
class FakeProtoView extends SpyObject {
|
|
||||||
constructor(public transitiveContentTagCount: number) { super(DomProtoView); }
|
|
||||||
|
|
||||||
noSuchMethod(i) { super.noSuchMethod(i); }
|
|
||||||
}
|
|
||||||
|
|
||||||
@proxy
|
|
||||||
@IMPLEMENTS(DomView)
|
|
||||||
class FakeView extends SpyObject {
|
|
||||||
boundElements: any[];
|
|
||||||
proto;
|
|
||||||
|
|
||||||
constructor(containers = null, transitiveContentTagCount: number = 1) {
|
|
||||||
super(DomView);
|
|
||||||
this.proto = new FakeProtoView(transitiveContentTagCount);
|
|
||||||
this.boundElements = [];
|
|
||||||
if (isPresent(containers)) {
|
|
||||||
ListWrapper.forEach(containers, (c) => {
|
|
||||||
var element = null;
|
|
||||||
var contentTag = null;
|
|
||||||
var vc = null;
|
|
||||||
if (c instanceof FakeContentTag) {
|
|
||||||
contentTag = c;
|
|
||||||
element = c.contentStartElement;
|
|
||||||
}
|
|
||||||
if (c instanceof FakeViewContainer) {
|
|
||||||
vc = c;
|
|
||||||
element = c.templateElement;
|
|
||||||
}
|
|
||||||
var boundElement = new DomElement(null, element, contentTag);
|
|
||||||
boundElement.viewContainer = vc;
|
|
||||||
this.boundElements.push(boundElement);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
noSuchMethod(i) { super.noSuchMethod(i); }
|
|
||||||
}
|
|
||||||
|
|
||||||
@proxy
|
|
||||||
@IMPLEMENTS(DomViewContainer)
|
|
||||||
class FakeViewContainer extends SpyObject {
|
|
||||||
_nodes;
|
|
||||||
_contentTagContainers;
|
|
||||||
templateElement;
|
|
||||||
|
|
||||||
constructor(templateEl, nodes = null, views = null) {
|
|
||||||
super(DomViewContainer);
|
|
||||||
this.templateElement = templateEl;
|
|
||||||
this._nodes = nodes;
|
|
||||||
this._contentTagContainers = views;
|
|
||||||
}
|
|
||||||
|
|
||||||
nodes() { return this._nodes; }
|
|
||||||
|
|
||||||
contentTagContainers() { return this._contentTagContainers; }
|
|
||||||
|
|
||||||
noSuchMethod(i) { super.noSuchMethod(i); }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@proxy
|
|
||||||
@IMPLEMENTS(Content)
|
|
||||||
class FakeContentTag extends SpyObject {
|
|
||||||
select;
|
|
||||||
_nodes;
|
|
||||||
contentStartElement;
|
|
||||||
|
|
||||||
constructor(contentEl, select = '', nodes = null) {
|
|
||||||
super(Content);
|
|
||||||
this.contentStartElement = contentEl;
|
|
||||||
this.select = select;
|
|
||||||
this._nodes = nodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
insert(nodes) { this._nodes = nodes; }
|
|
||||||
|
|
||||||
nodes() { return this._nodes; }
|
|
||||||
|
|
||||||
noSuchMethod(i) { super.noSuchMethod(i); }
|
|
||||||
}
|
|
||||||
|
|
||||||
function createLightDom(hostView, shadowView, el) {
|
|
||||||
var lightDom = new LightDom(hostView, el);
|
|
||||||
lightDom.attachShadowDomView(shadowView);
|
|
||||||
return lightDom;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function main() {
|
|
||||||
describe('LightDom', function() {
|
|
||||||
var lightDomView;
|
|
||||||
|
|
||||||
beforeEach(() => { lightDomView = new FakeView(); });
|
|
||||||
|
|
||||||
describe("contentTags", () => {
|
|
||||||
it("should collect unconditional content tags", () => {
|
|
||||||
var tag = new FakeContentTag(el('<script></script>'));
|
|
||||||
var shadowDomView = new FakeView([tag]);
|
|
||||||
|
|
||||||
var lightDom = createLightDom(lightDomView, shadowDomView, el("<div></div>"));
|
|
||||||
|
|
||||||
expect(lightDom.contentTags()).toEqual([tag]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should collect content tags from ViewContainers", () => {
|
|
||||||
var tag = new FakeContentTag(el('<script></script>'));
|
|
||||||
var vc = new FakeViewContainer(null, null, [new FakeView([tag])]);
|
|
||||||
var shadowDomView = new FakeView([vc]);
|
|
||||||
var lightDom = createLightDom(lightDomView, shadowDomView, el("<div></div>"));
|
|
||||||
|
|
||||||
expect(lightDom.contentTags()).toEqual([tag]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not walk views that can't have content tags", () => {
|
|
||||||
var tag = new FakeContentTag(el('<script></script>'));
|
|
||||||
var shadowDomView = new FakeView([tag], 0);
|
|
||||||
|
|
||||||
var lightDom = createLightDom(lightDomView, shadowDomView, el("<div></div>"));
|
|
||||||
|
|
||||||
expect(lightDom.contentTags()).toEqual([]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("expandedDomNodes", () => {
|
|
||||||
it("should contain root nodes", () => {
|
|
||||||
var lightDomEl = el("<div><a></a></div>");
|
|
||||||
var lightDom = createLightDom(lightDomView, new FakeView(), lightDomEl);
|
|
||||||
expect(toHtml(lightDom.expandedDomNodes())).toEqual(["<a></a>"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should include view container nodes", () => {
|
|
||||||
var lightDomEl = el("<div><template></template></div>");
|
|
||||||
var lightDom = createLightDom(
|
|
||||||
new FakeView([
|
|
||||||
new FakeViewContainer(DOM.firstChild(lightDomEl), // template element
|
|
||||||
[el('<a></a>')] // light DOM nodes of view container
|
|
||||||
)
|
|
||||||
]),
|
|
||||||
null, lightDomEl);
|
|
||||||
|
|
||||||
expect(toHtml(lightDom.expandedDomNodes())).toEqual(["<a></a>"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should include content nodes", () => {
|
|
||||||
var lightDomEl = el("<div><content></content></div>");
|
|
||||||
var lightDom =
|
|
||||||
createLightDom(new FakeView([
|
|
||||||
new FakeContentTag(DOM.firstChild(lightDomEl), // content element
|
|
||||||
'', // selector
|
|
||||||
[el('<a></a>')] // light DOM nodes of content tag
|
|
||||||
)
|
|
||||||
]),
|
|
||||||
null, lightDomEl);
|
|
||||||
|
|
||||||
expect(toHtml(lightDom.expandedDomNodes())).toEqual(["<a></a>"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should work when the element injector array contains nulls", () => {
|
|
||||||
var lightDomEl = el("<div><a></a></div>")
|
|
||||||
|
|
||||||
var lightDomView = new FakeView();
|
|
||||||
|
|
||||||
var lightDom = createLightDom(lightDomView, new FakeView(), lightDomEl);
|
|
||||||
|
|
||||||
expect(toHtml(lightDom.expandedDomNodes())).toEqual(["<a></a>"]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("redistribute", () => {
|
|
||||||
it("should redistribute nodes between content tags with select property set", () => {
|
|
||||||
var contentA = new FakeContentTag(null, "a");
|
|
||||||
var contentB = new FakeContentTag(null, "b");
|
|
||||||
|
|
||||||
var lightDomEl = el("<div><a>1</a><b>2</b><a>3</a></div>")
|
|
||||||
|
|
||||||
var lightDom =
|
|
||||||
createLightDom(lightDomView, new FakeView([contentA, contentB]), lightDomEl);
|
|
||||||
|
|
||||||
lightDom.redistribute();
|
|
||||||
|
|
||||||
expect(toHtml(contentA.nodes())).toEqual(["<a>1</a>", "<a>3</a>"]);
|
|
||||||
expect(toHtml(contentB.nodes())).toEqual(["<b>2</b>"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should support wildcard content tags", () => {
|
|
||||||
var wildcard = new FakeContentTag(null, '');
|
|
||||||
var contentB = new FakeContentTag(null, "b");
|
|
||||||
|
|
||||||
var lightDomEl = el("<div><a>1</a><b>2</b><a>3</a></div>")
|
|
||||||
|
|
||||||
var lightDom =
|
|
||||||
createLightDom(lightDomView, new FakeView([wildcard, contentB]), lightDomEl);
|
|
||||||
|
|
||||||
lightDom.redistribute();
|
|
||||||
|
|
||||||
expect(toHtml(wildcard.nodes())).toEqual(["<a>1</a>", "<b>2</b>", "<a>3</a>"]);
|
|
||||||
expect(toHtml(contentB.nodes())).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should remove all nodes if there are no content tags", () => {
|
|
||||||
var lightDomEl = el("<div><a>1</a><b>2</b><a>3</a></div>")
|
|
||||||
|
|
||||||
var lightDom = createLightDom(lightDomView, new FakeView([]), lightDomEl);
|
|
||||||
|
|
||||||
lightDom.redistribute();
|
|
||||||
|
|
||||||
expect(DOM.childNodes(lightDomEl).length).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should remove all not projected nodes", () => {
|
|
||||||
var lightDomEl = el("<div><a>1</a><b>2</b><a>3</a></div>");
|
|
||||||
var bNode = DOM.childNodes(lightDomEl)[1];
|
|
||||||
|
|
||||||
var lightDom =
|
|
||||||
createLightDom(lightDomView, new FakeView([new FakeContentTag(null, "a")]), lightDomEl);
|
|
||||||
|
|
||||||
lightDom.redistribute();
|
|
||||||
|
|
||||||
expect(bNode.parentNode).toBe(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function toHtml(nodes) {
|
|
||||||
if (isBlank(nodes)) return [];
|
|
||||||
return ListWrapper.map(nodes, stringifyElement);
|
|
||||||
}
|
|
@ -16,19 +16,13 @@ import {
|
|||||||
NativeShadowDomStrategy
|
NativeShadowDomStrategy
|
||||||
} from 'angular2/src/render/dom/shadow_dom/native_shadow_dom_strategy';
|
} from 'angular2/src/render/dom/shadow_dom/native_shadow_dom_strategy';
|
||||||
|
|
||||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
var strategy;
|
var strategy: NativeShadowDomStrategy;
|
||||||
|
|
||||||
describe('NativeShadowDomStrategy', () => {
|
describe('NativeShadowDomStrategy', () => {
|
||||||
beforeEach(() => { strategy = new NativeShadowDomStrategy(); });
|
beforeEach(() => { strategy = new NativeShadowDomStrategy(); });
|
||||||
|
|
||||||
if (DOM.supportsNativeShadowDOM()) {
|
it('should report that this is the native strategy',
|
||||||
it('should use the native shadow root', () => {
|
() => { expect(strategy.hasNativeContentElement()).toBe(true); });
|
||||||
var host = el('<div><span>original content</span></div>');
|
|
||||||
expect(strategy.prepareShadowRoot(host)).toBe(DOM.getShadowRoot(host));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,555 +0,0 @@
|
|||||||
import {
|
|
||||||
AsyncTestCompleter,
|
|
||||||
beforeEach,
|
|
||||||
ddescribe,
|
|
||||||
describe,
|
|
||||||
el,
|
|
||||||
expect,
|
|
||||||
iit,
|
|
||||||
inject,
|
|
||||||
it,
|
|
||||||
xit,
|
|
||||||
beforeEachBindings,
|
|
||||||
SpyObject,
|
|
||||||
} from 'angular2/test_lib';
|
|
||||||
|
|
||||||
import {bind} from 'angular2/di';
|
|
||||||
import {MapWrapper, ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
|
||||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
|
||||||
|
|
||||||
import {ViewDefinition, DirectiveMetadata} from 'angular2/src/render/api';
|
|
||||||
|
|
||||||
import {ShadowDomStrategy} from 'angular2/src/render/dom/shadow_dom/shadow_dom_strategy';
|
|
||||||
import {
|
|
||||||
EmulatedScopedShadowDomStrategy
|
|
||||||
} from 'angular2/src/render/dom/shadow_dom/emulated_scoped_shadow_dom_strategy';
|
|
||||||
import {
|
|
||||||
EmulatedUnscopedShadowDomStrategy
|
|
||||||
} from 'angular2/src/render/dom/shadow_dom/emulated_unscoped_shadow_dom_strategy';
|
|
||||||
import {
|
|
||||||
NativeShadowDomStrategy
|
|
||||||
} from 'angular2/src/render/dom/shadow_dom/native_shadow_dom_strategy';
|
|
||||||
|
|
||||||
import {DomTestbed, elRef} from './dom_testbed';
|
|
||||||
|
|
||||||
export function main() {
|
|
||||||
describe('ShadowDom integration tests', function() {
|
|
||||||
var styleHost = DOM.createElement('div');
|
|
||||||
|
|
||||||
var strategies = {
|
|
||||||
"scoped": bind(ShadowDomStrategy).toValue(new EmulatedScopedShadowDomStrategy(styleHost)),
|
|
||||||
"unscoped": bind(ShadowDomStrategy).toValue(new EmulatedUnscopedShadowDomStrategy(styleHost))
|
|
||||||
};
|
|
||||||
if (DOM.supportsNativeShadowDOM()) {
|
|
||||||
StringMapWrapper.set(strategies, "native",
|
|
||||||
bind(ShadowDomStrategy).toValue(new NativeShadowDomStrategy()));
|
|
||||||
}
|
|
||||||
|
|
||||||
StringMapWrapper.forEach(strategies, (strategyBinding, name) => {
|
|
||||||
describe(`${name} shadow dom strategy`, () => {
|
|
||||||
beforeEachBindings(() => { return [strategyBinding, DomTestbed]; });
|
|
||||||
|
|
||||||
// GH-2095 - https://github.com/angular/angular/issues/2095
|
|
||||||
// important as we are adding a content end element during compilation,
|
|
||||||
// which could skrew up text node indices.
|
|
||||||
it('should support text nodes after content tags',
|
|
||||||
inject([DomTestbed, AsyncTestCompleter], (tb, async) => {
|
|
||||||
tb.compileAll([
|
|
||||||
simple,
|
|
||||||
new ViewDefinition({
|
|
||||||
componentId: 'simple',
|
|
||||||
template: '<content></content><p>P,</p>{{a}}',
|
|
||||||
directives: []
|
|
||||||
})
|
|
||||||
])
|
|
||||||
.then((protoViewDtos) => {
|
|
||||||
var rootView = tb.createRootView(protoViewDtos[0]);
|
|
||||||
var cmpView = tb.createComponentView(rootView.viewRef, 0, protoViewDtos[1]);
|
|
||||||
|
|
||||||
tb.renderer.setText(cmpView.viewRef, 0, 'text');
|
|
||||||
expect(tb.rootEl).toHaveText('P,text');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
// important as we are moving style tags around during compilation,
|
|
||||||
// which could skrew up text node indices.
|
|
||||||
it('should support text nodes after style tags',
|
|
||||||
inject([DomTestbed, AsyncTestCompleter], (tb, async) => {
|
|
||||||
tb.compileAll([
|
|
||||||
simple,
|
|
||||||
new ViewDefinition({
|
|
||||||
componentId: 'simple',
|
|
||||||
template: '<style></style><p>P,</p>{{a}}',
|
|
||||||
directives: []
|
|
||||||
})
|
|
||||||
])
|
|
||||||
.then((protoViewDtos) => {
|
|
||||||
var rootView = tb.createRootView(protoViewDtos[0]);
|
|
||||||
var cmpView = tb.createComponentView(rootView.viewRef, 0, protoViewDtos[1]);
|
|
||||||
|
|
||||||
tb.renderer.setText(cmpView.viewRef, 0, 'text');
|
|
||||||
expect(tb.rootEl).toHaveText('P,text');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should support simple components',
|
|
||||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
|
||||||
tb.compileAll([
|
|
||||||
mainDir,
|
|
||||||
new ViewDefinition({
|
|
||||||
componentId: 'main',
|
|
||||||
template: '<simple>' +
|
|
||||||
'<div>A</div>' +
|
|
||||||
'</simple>',
|
|
||||||
directives: [simple]
|
|
||||||
}),
|
|
||||||
simpleTemplate
|
|
||||||
])
|
|
||||||
.then((protoViews) => {
|
|
||||||
tb.createRootViews(protoViews);
|
|
||||||
|
|
||||||
expect(tb.rootEl).toHaveText('SIMPLE(A)');
|
|
||||||
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should support simple components with text interpolation as direct children',
|
|
||||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
|
||||||
tb.compileAll([
|
|
||||||
mainDir,
|
|
||||||
new ViewDefinition({
|
|
||||||
componentId: 'main',
|
|
||||||
template: '<simple>' +
|
|
||||||
'{{text}}' +
|
|
||||||
'</simple>',
|
|
||||||
directives: [simple]
|
|
||||||
}),
|
|
||||||
simpleTemplate
|
|
||||||
])
|
|
||||||
.then((protoViews) => {
|
|
||||||
var cmpView = tb.createRootViews(protoViews)[1];
|
|
||||||
tb.renderer.setText(cmpView.viewRef, 0, 'A');
|
|
||||||
|
|
||||||
expect(tb.rootEl).toHaveText('SIMPLE(A)');
|
|
||||||
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should not show the light dom even if there is not content tag',
|
|
||||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
|
||||||
tb.compileAll([
|
|
||||||
mainDir,
|
|
||||||
new ViewDefinition({
|
|
||||||
componentId: 'main',
|
|
||||||
template: '<empty>' +
|
|
||||||
'<div>A</div>' +
|
|
||||||
'</empty>',
|
|
||||||
directives: [empty]
|
|
||||||
}),
|
|
||||||
emptyTemplate
|
|
||||||
])
|
|
||||||
.then((protoViews) => {
|
|
||||||
tb.createRootViews(protoViews);
|
|
||||||
|
|
||||||
expect(tb.rootEl).toHaveText('');
|
|
||||||
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should support dynamic components',
|
|
||||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
|
||||||
tb.compileAll([
|
|
||||||
mainDir,
|
|
||||||
new ViewDefinition({
|
|
||||||
componentId: 'main',
|
|
||||||
template: '<dynamic>' +
|
|
||||||
'<div>A</div>' +
|
|
||||||
'</dynamic>',
|
|
||||||
directives: [dynamicComponent]
|
|
||||||
}),
|
|
||||||
simpleTemplate
|
|
||||||
])
|
|
||||||
.then((protoViews) => {
|
|
||||||
var views = tb.createRootViews(ListWrapper.slice(protoViews, 0, 2));
|
|
||||||
tb.createComponentView(views[1].viewRef, 0, protoViews[2]);
|
|
||||||
|
|
||||||
expect(tb.rootEl).toHaveText('SIMPLE(A)');
|
|
||||||
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should support multiple content tags',
|
|
||||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
|
||||||
tb.compileAll([
|
|
||||||
mainDir,
|
|
||||||
new ViewDefinition({
|
|
||||||
componentId: 'main',
|
|
||||||
template: '<multiple-content-tags>' +
|
|
||||||
'<div>B</div>' +
|
|
||||||
'<div>C</div>' +
|
|
||||||
'<div class="left">A</div>' +
|
|
||||||
'</multiple-content-tags>',
|
|
||||||
directives: [multipleContentTagsComponent]
|
|
||||||
}),
|
|
||||||
multipleContentTagsTemplate
|
|
||||||
])
|
|
||||||
.then((protoViews) => {
|
|
||||||
tb.createRootViews(protoViews);
|
|
||||||
|
|
||||||
expect(tb.rootEl).toHaveText('(A, BC)');
|
|
||||||
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should redistribute only direct children',
|
|
||||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
|
||||||
tb.compileAll([
|
|
||||||
mainDir,
|
|
||||||
new ViewDefinition({
|
|
||||||
componentId: 'main',
|
|
||||||
template: '<multiple-content-tags>' +
|
|
||||||
'<div>B<div class="left">A</div></div>' +
|
|
||||||
'<div>C</div>' +
|
|
||||||
'</multiple-content-tags>',
|
|
||||||
directives: [multipleContentTagsComponent]
|
|
||||||
}),
|
|
||||||
multipleContentTagsTemplate
|
|
||||||
])
|
|
||||||
.then((protoViews) => {
|
|
||||||
tb.createRootViews(protoViews);
|
|
||||||
|
|
||||||
expect(tb.rootEl).toHaveText('(, BAC)');
|
|
||||||
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("should redistribute direct child viewcontainers when the light dom changes",
|
|
||||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
|
||||||
tb.compileAll([
|
|
||||||
mainDir,
|
|
||||||
new ViewDefinition({
|
|
||||||
componentId: 'main',
|
|
||||||
template: '<multiple-content-tags>' +
|
|
||||||
'<div><div template="manual" class="left">A</div></div>' +
|
|
||||||
'<div>B</div>' +
|
|
||||||
'</multiple-content-tags>',
|
|
||||||
directives: [multipleContentTagsComponent, manualViewportDirective]
|
|
||||||
}),
|
|
||||||
multipleContentTagsTemplate
|
|
||||||
])
|
|
||||||
.then((protoViews) => {
|
|
||||||
var views = tb.createRootViews(protoViews);
|
|
||||||
var childProtoView = protoViews[1].elementBinders[1].nestedProtoView;
|
|
||||||
expect(tb.rootEl).toHaveText('(, B)');
|
|
||||||
|
|
||||||
var childView = tb.createViewInContainer(views[1].viewRef, 1, 0, childProtoView);
|
|
||||||
|
|
||||||
expect(tb.rootEl).toHaveText('(, AB)');
|
|
||||||
|
|
||||||
tb.destroyViewInContainer(views[1].viewRef, 1, 0, childView.viewRef);
|
|
||||||
|
|
||||||
expect(tb.rootEl).toHaveText('(, B)');
|
|
||||||
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("should redistribute when the light dom changes",
|
|
||||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
|
||||||
tb.compileAll([
|
|
||||||
mainDir,
|
|
||||||
new ViewDefinition({
|
|
||||||
componentId: 'main',
|
|
||||||
template: '<multiple-content-tags>' +
|
|
||||||
'<div template="manual" class="left">A</div>' +
|
|
||||||
'<div>B</div>' +
|
|
||||||
'</multiple-content-tags>',
|
|
||||||
directives: [multipleContentTagsComponent, manualViewportDirective]
|
|
||||||
}),
|
|
||||||
multipleContentTagsTemplate
|
|
||||||
])
|
|
||||||
.then((protoViews) => {
|
|
||||||
var views = tb.createRootViews(protoViews);
|
|
||||||
var childProtoView = protoViews[1].elementBinders[1].nestedProtoView;
|
|
||||||
|
|
||||||
expect(tb.rootEl).toHaveText('(, B)');
|
|
||||||
|
|
||||||
var childView = tb.createViewInContainer(views[1].viewRef, 1, 0, childProtoView);
|
|
||||||
|
|
||||||
expect(tb.rootEl).toHaveText('(A, B)');
|
|
||||||
|
|
||||||
tb.destroyViewInContainer(views[1].viewRef, 1, 0, childView.viewRef);
|
|
||||||
|
|
||||||
expect(tb.rootEl).toHaveText('(, B)');
|
|
||||||
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("should support nested components",
|
|
||||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
|
||||||
tb.compileAll([
|
|
||||||
mainDir,
|
|
||||||
new ViewDefinition({
|
|
||||||
componentId: 'main',
|
|
||||||
template: '<outer-with-indirect-nested>' +
|
|
||||||
'<div>A</div>' +
|
|
||||||
'<div>B</div>' +
|
|
||||||
'</outer-with-indirect-nested>',
|
|
||||||
directives: [outerWithIndirectNestedComponent]
|
|
||||||
}),
|
|
||||||
outerWithIndirectNestedTemplate,
|
|
||||||
simpleTemplate
|
|
||||||
])
|
|
||||||
.then((protoViews) => {
|
|
||||||
tb.createRootViews(protoViews);
|
|
||||||
|
|
||||||
expect(tb.rootEl).toHaveText('OUTER(SIMPLE(AB))');
|
|
||||||
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("should support nesting with content being direct child of a nested component",
|
|
||||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
|
||||||
tb.compileAll([
|
|
||||||
mainDir,
|
|
||||||
new ViewDefinition({
|
|
||||||
componentId: 'main',
|
|
||||||
template: '<outer>' +
|
|
||||||
'<div template="manual" class="left">A</div>' +
|
|
||||||
'<div>B</div>' +
|
|
||||||
'<div>C</div>' +
|
|
||||||
'</outer>',
|
|
||||||
directives: [outerComponent, manualViewportDirective]
|
|
||||||
}),
|
|
||||||
outerTemplate,
|
|
||||||
innerTemplate,
|
|
||||||
innerInnerTemplate
|
|
||||||
])
|
|
||||||
.then((protoViews) => {
|
|
||||||
var views = tb.createRootViews(protoViews);
|
|
||||||
var childProtoView = protoViews[1].elementBinders[1].nestedProtoView;
|
|
||||||
|
|
||||||
expect(tb.rootEl).toHaveText('OUTER(INNER(INNERINNER(,BC)))');
|
|
||||||
|
|
||||||
tb.createViewInContainer(views[1].viewRef, 1, 0, childProtoView);
|
|
||||||
|
|
||||||
expect(tb.rootEl).toHaveText('OUTER(INNER(INNERINNER(A,BC)))');
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should redistribute when the shadow dom changes',
|
|
||||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
|
||||||
tb.compileAll([
|
|
||||||
mainDir,
|
|
||||||
new ViewDefinition({
|
|
||||||
componentId: 'main',
|
|
||||||
template: '<conditional-content>' +
|
|
||||||
'<div class="left">A</div>' +
|
|
||||||
'<div>B</div>' +
|
|
||||||
'<div>C</div>' +
|
|
||||||
'</conditional-content>',
|
|
||||||
directives: [conditionalContentComponent]
|
|
||||||
}),
|
|
||||||
conditionalContentTemplate
|
|
||||||
])
|
|
||||||
.then((protoViews) => {
|
|
||||||
var views = tb.createRootViews(protoViews);
|
|
||||||
var childProtoView = protoViews[2].elementBinders[0].nestedProtoView;
|
|
||||||
|
|
||||||
expect(tb.rootEl).toHaveText('(, ABC)');
|
|
||||||
|
|
||||||
var childView = tb.createViewInContainer(views[2].viewRef, 0, 0, childProtoView);
|
|
||||||
|
|
||||||
expect(tb.rootEl).toHaveText('(A, BC)');
|
|
||||||
|
|
||||||
tb.destroyViewInContainer(views[2].viewRef, 0, 0, childView.viewRef);
|
|
||||||
|
|
||||||
expect(tb.rootEl).toHaveText('(, ABC)');
|
|
||||||
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("should support tabs with view caching",
|
|
||||||
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
|
|
||||||
tb.compileAll([
|
|
||||||
mainDir,
|
|
||||||
new ViewDefinition({
|
|
||||||
componentId: 'main',
|
|
||||||
template: '(<tab><span>0</span></tab>' +
|
|
||||||
'<tab><span>1</span></tab>' +
|
|
||||||
'<tab><span>2</span></tab>)',
|
|
||||||
directives: [tabComponent]
|
|
||||||
}),
|
|
||||||
tabTemplate
|
|
||||||
])
|
|
||||||
.then((protoViews) => {
|
|
||||||
var views = tb.createRootViews(ListWrapper.slice(protoViews, 0, 2));
|
|
||||||
var tabProtoView = protoViews[2];
|
|
||||||
var tabChildProtoView = tabProtoView.elementBinders[0].nestedProtoView;
|
|
||||||
|
|
||||||
var tab1View = tb.createComponentView(views[1].viewRef, 0, tabProtoView);
|
|
||||||
var tab2View = tb.createComponentView(views[1].viewRef, 1, tabProtoView);
|
|
||||||
var tab3View = tb.createComponentView(views[1].viewRef, 2, tabProtoView);
|
|
||||||
|
|
||||||
expect(tb.rootEl).toHaveText('()');
|
|
||||||
|
|
||||||
var tabChildView =
|
|
||||||
tb.createViewInContainer(tab1View.viewRef, 0, 0, tabChildProtoView);
|
|
||||||
|
|
||||||
expect(tb.rootEl).toHaveText('(TAB(0))');
|
|
||||||
|
|
||||||
tb.renderer.dehydrateView(tabChildView.viewRef);
|
|
||||||
tb.renderer.detachViewInContainer(elRef(tab1View.viewRef, 0), 0,
|
|
||||||
tabChildView.viewRef);
|
|
||||||
|
|
||||||
tb.renderer.attachViewInContainer(elRef(tab2View.viewRef, 0), 0,
|
|
||||||
tabChildView.viewRef);
|
|
||||||
tb.renderer.hydrateView(tabChildView.viewRef);
|
|
||||||
|
|
||||||
expect(tb.rootEl).toHaveText('(TAB(1))');
|
|
||||||
|
|
||||||
tb.renderer.dehydrateView(tabChildView.viewRef);
|
|
||||||
tb.renderer.detachViewInContainer(elRef(tab2View.viewRef, 0), 0,
|
|
||||||
tabChildView.viewRef);
|
|
||||||
|
|
||||||
tb.renderer.attachViewInContainer(elRef(tab3View.viewRef, 0), 0,
|
|
||||||
tabChildView.viewRef);
|
|
||||||
tb.renderer.hydrateView(tabChildView.viewRef);
|
|
||||||
|
|
||||||
expect(tb.rootEl).toHaveText('(TAB(2))');
|
|
||||||
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Implement once ElementRef support changing a class
|
|
||||||
// it("should redistribute when a class has been added or removed");
|
|
||||||
// it('should not lose focus', () => {
|
|
||||||
// var temp = `<simple>aaa<input type="text" id="focused-input" ng-class="{'aClass' :
|
|
||||||
// showClass}"> bbb</simple>`;
|
|
||||||
//
|
|
||||||
// compile(temp, (view, lc) => {
|
|
||||||
// var input = view.rootNodes[1];
|
|
||||||
// input.focus();
|
|
||||||
//
|
|
||||||
// expect(document.activeElement.id).toEqual("focused-input");
|
|
||||||
//
|
|
||||||
// // update class of input
|
|
||||||
//
|
|
||||||
// expect(document.activeElement.id).toEqual("focused-input");
|
|
||||||
// });
|
|
||||||
//});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var mainDir = DirectiveMetadata.create(
|
|
||||||
{selector: 'main', id: 'main', type: DirectiveMetadata.COMPONENT_TYPE});
|
|
||||||
|
|
||||||
var simple = DirectiveMetadata.create(
|
|
||||||
{selector: 'simple', id: 'simple', type: DirectiveMetadata.COMPONENT_TYPE});
|
|
||||||
|
|
||||||
var empty = DirectiveMetadata.create(
|
|
||||||
{selector: 'empty', id: 'empty', type: DirectiveMetadata.COMPONENT_TYPE});
|
|
||||||
|
|
||||||
var dynamicComponent = DirectiveMetadata.create(
|
|
||||||
{selector: 'dynamic', id: 'dynamic', type: DirectiveMetadata.COMPONENT_TYPE});
|
|
||||||
|
|
||||||
var multipleContentTagsComponent = DirectiveMetadata.create({
|
|
||||||
selector: 'multiple-content-tags',
|
|
||||||
id: 'multiple-content-tags',
|
|
||||||
type: DirectiveMetadata.COMPONENT_TYPE
|
|
||||||
});
|
|
||||||
|
|
||||||
var manualViewportDirective = DirectiveMetadata.create(
|
|
||||||
{selector: '[manual]', id: 'manual', type: DirectiveMetadata.DIRECTIVE_TYPE});
|
|
||||||
|
|
||||||
var outerWithIndirectNestedComponent = DirectiveMetadata.create({
|
|
||||||
selector: 'outer-with-indirect-nested',
|
|
||||||
id: 'outer-with-indirect-nested',
|
|
||||||
type: DirectiveMetadata.COMPONENT_TYPE
|
|
||||||
});
|
|
||||||
|
|
||||||
var outerComponent = DirectiveMetadata.create(
|
|
||||||
{selector: 'outer', id: 'outer', type: DirectiveMetadata.COMPONENT_TYPE});
|
|
||||||
|
|
||||||
var innerComponent = DirectiveMetadata.create(
|
|
||||||
{selector: 'inner', id: 'inner', type: DirectiveMetadata.COMPONENT_TYPE});
|
|
||||||
|
|
||||||
var innerInnerComponent = DirectiveMetadata.create(
|
|
||||||
{selector: 'innerinner', id: 'innerinner', type: DirectiveMetadata.COMPONENT_TYPE});
|
|
||||||
|
|
||||||
var conditionalContentComponent = DirectiveMetadata.create({
|
|
||||||
selector: 'conditional-content',
|
|
||||||
id: 'conditional-content',
|
|
||||||
type: DirectiveMetadata.COMPONENT_TYPE
|
|
||||||
});
|
|
||||||
|
|
||||||
var autoViewportDirective = DirectiveMetadata.create(
|
|
||||||
{selector: '[auto]', id: 'auto', properties: ['auto'], type: DirectiveMetadata.DIRECTIVE_TYPE});
|
|
||||||
|
|
||||||
var tabComponent =
|
|
||||||
DirectiveMetadata.create({selector: 'tab', id: 'tab', type: DirectiveMetadata.COMPONENT_TYPE});
|
|
||||||
|
|
||||||
var simpleTemplate = new ViewDefinition(
|
|
||||||
{componentId: 'simple', template: 'SIMPLE(<content></content>)', directives: []});
|
|
||||||
|
|
||||||
var emptyTemplate = new ViewDefinition({componentId: 'empty', template: '', directives: []});
|
|
||||||
|
|
||||||
var multipleContentTagsTemplate = new ViewDefinition({
|
|
||||||
componentId: 'multiple-content-tags',
|
|
||||||
template: '(<content select=".left"></content>, <content></content>)',
|
|
||||||
directives: []
|
|
||||||
});
|
|
||||||
|
|
||||||
var outerWithIndirectNestedTemplate = new ViewDefinition({
|
|
||||||
componentId: 'outer-with-indirect-nested',
|
|
||||||
template: 'OUTER(<simple><div><content></content></div></simple>)',
|
|
||||||
directives: [simple]
|
|
||||||
});
|
|
||||||
|
|
||||||
var outerTemplate = new ViewDefinition({
|
|
||||||
componentId: 'outer',
|
|
||||||
template: 'OUTER(<inner><content></content></inner>)',
|
|
||||||
directives: [innerComponent]
|
|
||||||
});
|
|
||||||
|
|
||||||
var innerTemplate = new ViewDefinition({
|
|
||||||
componentId: 'inner',
|
|
||||||
template: 'INNER(<innerinner><content></content></innerinner>)',
|
|
||||||
directives: [innerInnerComponent]
|
|
||||||
});
|
|
||||||
|
|
||||||
var innerInnerTemplate = new ViewDefinition({
|
|
||||||
componentId: 'innerinner',
|
|
||||||
template: 'INNERINNER(<content select=".left"></content>,<content></content>)',
|
|
||||||
directives: []
|
|
||||||
});
|
|
||||||
|
|
||||||
var conditionalContentTemplate = new ViewDefinition({
|
|
||||||
componentId: 'conditional-content',
|
|
||||||
template:
|
|
||||||
'<div>(<div *auto="cond"><content select=".left"></content></div>, <content></content>)</div>',
|
|
||||||
directives: [autoViewportDirective]
|
|
||||||
});
|
|
||||||
|
|
||||||
var tabTemplate = new ViewDefinition({
|
|
||||||
componentId: 'tab',
|
|
||||||
template: '<div><div *auto="cond">TAB(<content></content>)</div></div>',
|
|
||||||
directives: [autoViewportDirective]
|
|
||||||
});
|
|
@ -14,13 +14,15 @@ import {
|
|||||||
import {ProtoViewBuilder} from 'angular2/src/render/dom/view/proto_view_builder';
|
import {ProtoViewBuilder} from 'angular2/src/render/dom/view/proto_view_builder';
|
||||||
import {ASTWithSource, AST} from 'angular2/change_detection';
|
import {ASTWithSource, AST} from 'angular2/change_detection';
|
||||||
import {PropertyBindingType, ViewType} from 'angular2/src/render/api';
|
import {PropertyBindingType, ViewType} from 'angular2/src/render/api';
|
||||||
|
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
function emptyExpr() { return new ASTWithSource(new AST(), 'empty', 'empty'); }
|
function emptyExpr() { return new ASTWithSource(new AST(), 'empty', 'empty'); }
|
||||||
|
|
||||||
describe('ProtoViewBuilder', () => {
|
describe('ProtoViewBuilder', () => {
|
||||||
var builder;
|
var builder;
|
||||||
beforeEach(() => { builder = new ProtoViewBuilder(el('<div/>'), ViewType.EMBEDDED); });
|
beforeEach(
|
||||||
|
() => { builder = new ProtoViewBuilder(DOM.createTemplate(''), ViewType.EMBEDDED); });
|
||||||
|
|
||||||
if (!IS_DARTIUM) {
|
if (!IS_DARTIUM) {
|
||||||
describe('verification of properties', () => {
|
describe('verification of properties', () => {
|
||||||
|
@ -0,0 +1,291 @@
|
|||||||
|
import {
|
||||||
|
AsyncTestCompleter,
|
||||||
|
beforeEach,
|
||||||
|
ddescribe,
|
||||||
|
describe,
|
||||||
|
el,
|
||||||
|
expect,
|
||||||
|
iit,
|
||||||
|
inject,
|
||||||
|
it,
|
||||||
|
xit,
|
||||||
|
beforeEachBindings,
|
||||||
|
SpyObject,
|
||||||
|
stringifyElement
|
||||||
|
} from 'angular2/test_lib';
|
||||||
|
|
||||||
|
import {isPresent} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
|
import {DomTestbed} from '../dom_testbed';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ViewDefinition,
|
||||||
|
DirectiveMetadata,
|
||||||
|
RenderProtoViewMergeMapping
|
||||||
|
} from 'angular2/src/render/api';
|
||||||
|
import {bind} from 'angular2/di';
|
||||||
|
|
||||||
|
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||||
|
import {cloneAndQueryProtoView} from 'angular2/src/render/dom/util';
|
||||||
|
import {resolveInternalDomProtoView} from 'angular2/src/render/dom/view/proto_view';
|
||||||
|
|
||||||
|
import {ShadowDomStrategy, NativeShadowDomStrategy} from 'angular2/render';
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
describe('ProtoViewMerger integration test', () => {
|
||||||
|
beforeEachBindings(() => [DomTestbed]);
|
||||||
|
|
||||||
|
describe('component views', () => {
|
||||||
|
it('should merge a component view',
|
||||||
|
runAndAssert('root', ['a'], ['<root class="ng-binding" idx="0">a</root>']));
|
||||||
|
|
||||||
|
it('should merge component views with interpolation at root level',
|
||||||
|
runAndAssert('root', ['{{a}}'], ['<root class="ng-binding" idx="0">{0}</root>']));
|
||||||
|
|
||||||
|
it('should merge component views with interpolation not at root level',
|
||||||
|
runAndAssert('root', ['<div>{{a}}</div>'], [
|
||||||
|
'<root class="ng-binding" idx="0"><div class="ng-binding" idx="1">{0}</div></root>'
|
||||||
|
]));
|
||||||
|
|
||||||
|
it('should merge component views with bound elements',
|
||||||
|
runAndAssert('root', ['<div #a></div>'], [
|
||||||
|
'<root class="ng-binding" idx="0"><div #a="" class="ng-binding" idx="1"></div></root>'
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('embedded views', () => {
|
||||||
|
|
||||||
|
it('should merge embedded views as fragments',
|
||||||
|
runAndAssert('root', ['<template>a</template>'], [
|
||||||
|
'<root class="ng-binding" idx="0"><template class="ng-binding" idx="1"></template></root>',
|
||||||
|
'a'
|
||||||
|
]));
|
||||||
|
|
||||||
|
it('should merge embedded views with interpolation at root level',
|
||||||
|
runAndAssert('root', ['<template>{{a}}</template>'], [
|
||||||
|
'<root class="ng-binding" idx="0"><template class="ng-binding" idx="1"></template></root>',
|
||||||
|
'{0}'
|
||||||
|
]));
|
||||||
|
|
||||||
|
it('should merge embedded views with interpolation not at root level',
|
||||||
|
runAndAssert('root', ['<div *ng-if>{{a}}</div>'], [
|
||||||
|
'<root class="ng-binding" idx="0"><template class="ng-binding" idx="1" ng-if=""></template></root>',
|
||||||
|
'<div *ng-if="" class="ng-binding" idx="2">{0}</div>'
|
||||||
|
]));
|
||||||
|
|
||||||
|
it('should merge embedded views with bound elements',
|
||||||
|
runAndAssert('root', ['<div *ng-if #a></div>'], [
|
||||||
|
'<root class="ng-binding" idx="0"><template class="ng-binding" idx="1" ng-if=""></template></root>',
|
||||||
|
'<div #a="" *ng-if="" class="ng-binding" idx="2"></div>'
|
||||||
|
]));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('projection', () => {
|
||||||
|
|
||||||
|
it('should remove text nodes if there is no ng-content',
|
||||||
|
runAndAssert(
|
||||||
|
'root', ['<a>b</a>', ''],
|
||||||
|
['<root class="ng-binding" idx="0"><a class="ng-binding" idx="1"></a></root>']));
|
||||||
|
|
||||||
|
it('should project static text',
|
||||||
|
runAndAssert(
|
||||||
|
'root', ['<a>b</a>', 'A(<ng-content></ng-content>)'],
|
||||||
|
['<root class="ng-binding" idx="0"><a class="ng-binding" idx="1">A(b)</a></root>']));
|
||||||
|
|
||||||
|
it('should project text interpolation',
|
||||||
|
runAndAssert(
|
||||||
|
'root', ['<a>{{b}}</a>', 'A(<ng-content></ng-content>)'],
|
||||||
|
['<root class="ng-binding" idx="0"><a class="ng-binding" idx="1">A({0})</a></root>']));
|
||||||
|
|
||||||
|
it('should project elements',
|
||||||
|
runAndAssert('root', ['<a><div></div></a>', 'A(<ng-content></ng-content>)'], [
|
||||||
|
'<root class="ng-binding" idx="0"><a class="ng-binding" idx="1">A(<div></div>)</a></root>'
|
||||||
|
]));
|
||||||
|
|
||||||
|
it('should project elements using the selector',
|
||||||
|
runAndAssert(
|
||||||
|
'root',
|
||||||
|
[
|
||||||
|
'<a><div class="x">a</div><span></span><div class="x">b</div></a>',
|
||||||
|
'A(<ng-content select=".x"></ng-content>)'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'<root class="ng-binding" idx="0"><a class="ng-binding" idx="1">A(<div class="x">a</div><div class="x">b</div>)</a></root>'
|
||||||
|
]));
|
||||||
|
|
||||||
|
it('should reproject',
|
||||||
|
runAndAssert(
|
||||||
|
'root',
|
||||||
|
['<a>x</a>', 'A(<b><ng-content></ng-content></b>)', 'B(<ng-content></ng-content>)'], [
|
||||||
|
'<root class="ng-binding" idx="0"><a class="ng-binding" idx="1">A(<b class="ng-binding" idx="2">B(x)</b>)</a></root>'
|
||||||
|
]));
|
||||||
|
|
||||||
|
it('should reproject by combining selectors',
|
||||||
|
runAndAssert(
|
||||||
|
'root',
|
||||||
|
[
|
||||||
|
'<a><div class="x"></div><div class="x y"></div><div class="y"></div></a>',
|
||||||
|
'A(<b><ng-content select=".x"></ng-content></b>)',
|
||||||
|
'B(<ng-content select=".y"></ng-content>)'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'<root class="ng-binding" idx="0"><a class="ng-binding" idx="1">A(<b class="ng-binding" idx="2">B(<div class="x y"></div>)</b>)</a></root>'
|
||||||
|
]));
|
||||||
|
|
||||||
|
it('should keep non projected embedded views (so that they can be moved manually)',
|
||||||
|
runAndAssert(
|
||||||
|
'root', ['<a><template class="x">b</template></a>', ''],
|
||||||
|
['<root class="ng-binding" idx="0"><a class="ng-binding" idx="1"></a></root>', 'b']));
|
||||||
|
|
||||||
|
it('should project embedded views and match the template element',
|
||||||
|
runAndAssert(
|
||||||
|
'root', ['<a><template class="x">b</template></a>', 'A(<ng-content></ng-content>)'], [
|
||||||
|
'<root class="ng-binding" idx="0"><a class="ng-binding" idx="1">A(<template class="x ng-binding" idx="2"></template>)</a></root>',
|
||||||
|
'b'
|
||||||
|
]));
|
||||||
|
|
||||||
|
it('should project embedded views and match the single root element',
|
||||||
|
runAndAssert(
|
||||||
|
'root', ['<a><div class="x" *ng-if></div></a>', 'A(<ng-content></ng-content>)'], [
|
||||||
|
'<root class="ng-binding" idx="0"><a class="ng-binding" idx="1">A(<template class="ng-binding" idx="2" ng-if=""></template>)</a></root>',
|
||||||
|
'<div *ng-if="" class="x"></div>'
|
||||||
|
]));
|
||||||
|
|
||||||
|
it('should project nodes using the ng-content in embedded views',
|
||||||
|
runAndAssert('root', ['<a>b</a>', 'A(<ng-content *ng-if></ng-content>)'], [
|
||||||
|
'<root class="ng-binding" idx="0"><a class="ng-binding" idx="1">A(<template class="ng-binding" idx="2" ng-if=""></template>)</a></root>',
|
||||||
|
'b'
|
||||||
|
]));
|
||||||
|
|
||||||
|
it('should allow to use wildcard selector after embedded view with non wildcard selector',
|
||||||
|
runAndAssert(
|
||||||
|
'root',
|
||||||
|
[
|
||||||
|
'<a><div class="x">a</div>b</a>',
|
||||||
|
'A(<ng-content select=".x" *ng-if></ng-content>, <ng-content></ng-content>)'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'<root class="ng-binding" idx="0"><a class="ng-binding" idx="1">A(<template class="ng-binding" idx="2" ng-if=""></template>, b)</a></root>',
|
||||||
|
'<div class="x">a</div>'
|
||||||
|
]));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('composition', () => {
|
||||||
|
it('should merge multiple component views',
|
||||||
|
runAndAssert('root', ['<a></a><b></b>', 'c', 'd'], [
|
||||||
|
'<root class="ng-binding" idx="0"><a class="ng-binding" idx="1">c</a><b class="ng-binding" idx="2">d</b></root>'
|
||||||
|
]));
|
||||||
|
|
||||||
|
it('should merge multiple embedded views as fragments',
|
||||||
|
runAndAssert('root', ['<div *ng-if></div><span *ng-for></span>'], [
|
||||||
|
'<root class="ng-binding" idx="0"><template class="ng-binding" idx="1" ng-if=""></template><template class="ng-binding" idx="2" ng-for=""></template></root>',
|
||||||
|
'<div *ng-if=""></div>',
|
||||||
|
'<span *ng-for=""></span>'
|
||||||
|
]));
|
||||||
|
|
||||||
|
it('should merge nested embedded views as fragments',
|
||||||
|
runAndAssert('root', ['<div *ng-if><span *ng-for></span></div>'], [
|
||||||
|
'<root class="ng-binding" idx="0"><template class="ng-binding" idx="1" ng-if=""></template></root>',
|
||||||
|
'<div *ng-if=""><template class="ng-binding" idx="2" ng-for=""></template></div>',
|
||||||
|
'<span *ng-for=""></span>'
|
||||||
|
]));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('element index mapping should be grouped by view and view depth first', () => {
|
||||||
|
|
||||||
|
it('should map component views correctly',
|
||||||
|
runAndAssert('root', ['<a></a><b></b>', '<c></c>'], [
|
||||||
|
'<root class="ng-binding" idx="0"><a class="ng-binding" idx="1"><c class="ng-binding" idx="3"></c></a><b class="ng-binding" idx="2"></b></root>'
|
||||||
|
]));
|
||||||
|
|
||||||
|
it('should map moved projected elements correctly',
|
||||||
|
runAndAssert(
|
||||||
|
'root',
|
||||||
|
[
|
||||||
|
'<a><b></b><c></c></a>',
|
||||||
|
'<ng-content select="c"></ng-content><ng-content select="b"></ng-content>'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'<root class="ng-binding" idx="0"><a class="ng-binding" idx="1"><c class="ng-binding" idx="3"></c><b class="ng-binding" idx="2"></b></a></root>'
|
||||||
|
]));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('text index mapping should be grouped by view and view depth first', () => {
|
||||||
|
|
||||||
|
it('should map component views correctly', runAndAssert('root', ['<a></a>{{b}}', '{{c}}'], [
|
||||||
|
'<root class="ng-binding" idx="0"><a class="ng-binding" idx="1">{1}</a>{0}</root>'
|
||||||
|
]));
|
||||||
|
|
||||||
|
it('should map moved projected elements correctly',
|
||||||
|
runAndAssert(
|
||||||
|
'root',
|
||||||
|
[
|
||||||
|
'<a><div x>{{x}}</div><div y>{{y}}</div></a>',
|
||||||
|
'<ng-content select="[y]"></ng-content><ng-content select="[x]"></ng-content>'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'<root class="ng-binding" idx="0"><a class="ng-binding" idx="1"><div class="ng-binding" idx="3" y="">{1}</div><div class="ng-binding" idx="2" x="">{0}</div></a></root>'
|
||||||
|
]));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('native shadow dom support', () => {
|
||||||
|
beforeEachBindings(
|
||||||
|
() => { return [bind(ShadowDomStrategy).toValue(new NativeShadowDomStrategy())]; });
|
||||||
|
|
||||||
|
it('should keep the non projected light dom and wrap the component view into a shadow-root element',
|
||||||
|
runAndAssert('root', ['<a>b</a>', 'c'], [
|
||||||
|
'<root class="ng-binding" idx="0"><shadow-root><a class="ng-binding" idx="1"><shadow-root>c</shadow-root>b</a></shadow-root></root>'
|
||||||
|
]));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function runAndAssert(hostElementName: string, componentTemplates: string[],
|
||||||
|
expectedFragments: string[]) {
|
||||||
|
var rootComp = DirectiveMetadata.create(
|
||||||
|
{id: 'rootComp', type: DirectiveMetadata.COMPONENT_TYPE, selector: hostElementName});
|
||||||
|
return inject([AsyncTestCompleter, DomTestbed], (async, tb: DomTestbed) => {
|
||||||
|
tb.compileAndMerge(rootComp, componentTemplates.map(template => new ViewDefinition({
|
||||||
|
componentId: 'someComp',
|
||||||
|
template: template,
|
||||||
|
directives: [aComp, bComp, cComp]
|
||||||
|
})))
|
||||||
|
.then((mergeMappings) => {
|
||||||
|
expect(stringify(mergeMappings[0])).toEqual(expectedFragments);
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function stringify(protoViewMergeMapping: RenderProtoViewMergeMapping): string[] {
|
||||||
|
var testView = cloneAndQueryProtoView(
|
||||||
|
resolveInternalDomProtoView(protoViewMergeMapping.mergedProtoViewRef), false);
|
||||||
|
for (var i = 0; i < protoViewMergeMapping.mappedElementIndices.length; i++) {
|
||||||
|
var renderElIdx = protoViewMergeMapping.mappedElementIndices[i];
|
||||||
|
if (isPresent(renderElIdx)) {
|
||||||
|
DOM.setAttribute(testView.boundElements[renderElIdx], 'idx', `${i}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var i = 0; i < protoViewMergeMapping.mappedTextIndices.length; i++) {
|
||||||
|
var renderTextIdx = protoViewMergeMapping.mappedTextIndices[i];
|
||||||
|
if (isPresent(renderTextIdx)) {
|
||||||
|
DOM.setText(testView.boundTextNodes[renderTextIdx], `{${i}}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect(protoViewMergeMapping.fragmentCount).toEqual(testView.fragments.length);
|
||||||
|
return testView.fragments.map(nodes => nodes.map(node => stringifyElement(node)).join(''));
|
||||||
|
}
|
||||||
|
|
||||||
|
var aComp =
|
||||||
|
DirectiveMetadata.create({id: 'aComp', type: DirectiveMetadata.COMPONENT_TYPE, selector: 'a'});
|
||||||
|
var bComp =
|
||||||
|
DirectiveMetadata.create({id: 'bComp', type: DirectiveMetadata.COMPONENT_TYPE, selector: 'b'});
|
||||||
|
var cComp =
|
||||||
|
DirectiveMetadata.create({id: 'cComp', type: DirectiveMetadata.COMPONENT_TYPE, selector: 'c'});
|
@ -0,0 +1,56 @@
|
|||||||
|
import {
|
||||||
|
AsyncTestCompleter,
|
||||||
|
beforeEach,
|
||||||
|
ddescribe,
|
||||||
|
describe,
|
||||||
|
el,
|
||||||
|
expect,
|
||||||
|
iit,
|
||||||
|
inject,
|
||||||
|
it,
|
||||||
|
xit,
|
||||||
|
beforeEachBindings,
|
||||||
|
SpyObject,
|
||||||
|
stringifyElement
|
||||||
|
} from 'angular2/test_lib';
|
||||||
|
|
||||||
|
import {mergeSelectors} from 'angular2/src/render/dom/view/proto_view_merger';
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
describe('ProtoViewMerger test', () => {
|
||||||
|
|
||||||
|
describe('mergeSelectors', () => {
|
||||||
|
it('should merge empty selectors', () => {
|
||||||
|
expect(mergeSelectors('', 'a')).toEqual('a');
|
||||||
|
expect(mergeSelectors('a', '')).toEqual('a');
|
||||||
|
expect(mergeSelectors('', '')).toEqual('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should merge wildcard selectors', () => {
|
||||||
|
expect(mergeSelectors('*', 'a')).toEqual('a');
|
||||||
|
expect(mergeSelectors('a', '*')).toEqual('a');
|
||||||
|
expect(mergeSelectors('*', '*')).toEqual('*');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should merge 2 element selectors',
|
||||||
|
() => { expect(mergeSelectors('a', 'b')).toEqual('_not-matchable_'); });
|
||||||
|
|
||||||
|
it('should merge elements and non element selector', () => {
|
||||||
|
expect(mergeSelectors('a', '.b')).toEqual('a.b');
|
||||||
|
expect(mergeSelectors('.b', 'a')).toEqual('a.b');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should merge attributes', () => {
|
||||||
|
expect(mergeSelectors('[a]', '[b]')).toEqual('[a][b]');
|
||||||
|
expect(mergeSelectors('[a][b]', '[c][d]')).toEqual('[a][b][c][d]');
|
||||||
|
expect(mergeSelectors('[a=1]', '[b=2]')).toEqual('[a=1][b=2]');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should merge classes', () => {
|
||||||
|
expect(mergeSelectors('.a', '.b')).toEqual('.a.b');
|
||||||
|
expect(mergeSelectors('.a.b', '.c.d')).toEqual('.a.b.c.d');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
@ -19,9 +19,8 @@ import {isBlank} from 'angular2/src/facade/lang';
|
|||||||
import {ListWrapper} from 'angular2/src/facade/collection';
|
import {ListWrapper} from 'angular2/src/facade/collection';
|
||||||
|
|
||||||
import {DomProtoView} from 'angular2/src/render/dom/view/proto_view';
|
import {DomProtoView} from 'angular2/src/render/dom/view/proto_view';
|
||||||
import {ElementBinder} from 'angular2/src/render/dom/view/element_binder';
|
import {DomElementBinder} from 'angular2/src/render/dom/view/element_binder';
|
||||||
import {DomView} from 'angular2/src/render/dom/view/view';
|
import {DomView} from 'angular2/src/render/dom/view/view';
|
||||||
import {DomElement} from 'angular2/src/render/dom/view/element';
|
|
||||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
@ -30,54 +29,33 @@ export function main() {
|
|||||||
if (isBlank(binders)) {
|
if (isBlank(binders)) {
|
||||||
binders = [];
|
binders = [];
|
||||||
}
|
}
|
||||||
var rootEl = el('<div></div>');
|
var rootEl = DOM.createTemplate('<div></div>');
|
||||||
return new DomProtoView({
|
return DomProtoView.create(null, <Element>rootEl, [1], [], binders, null, null, null);
|
||||||
element: rootEl,
|
|
||||||
elementBinders: binders,
|
|
||||||
transitiveContentTagCount: 0,
|
|
||||||
boundTextNodeCount: 0
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createElementBinder() { return new DomElementBinder({textNodeIndices: []}); }
|
||||||
|
|
||||||
function createView(pv = null, boundElementCount = 0) {
|
function createView(pv = null, boundElementCount = 0) {
|
||||||
if (isBlank(pv)) {
|
if (isBlank(pv)) {
|
||||||
pv = createProtoView(ListWrapper.createFixedSize(boundElementCount));
|
var elementBinders = ListWrapper.createFixedSize(boundElementCount);
|
||||||
|
for (var i = 0; i < boundElementCount; i++) {
|
||||||
|
elementBinders[i] = createElementBinder();
|
||||||
|
}
|
||||||
|
pv = createProtoView(elementBinders);
|
||||||
}
|
}
|
||||||
var root = el('<div><div></div></div>');
|
var root = el('<div><div></div></div>');
|
||||||
var boundElements = [];
|
var boundElements = [];
|
||||||
for (var i = 0; i < boundElementCount; i++) {
|
for (var i = 0; i < boundElementCount; i++) {
|
||||||
boundElements.push(new DomElement(pv.elementBinders[i], el('<span></span'), null));
|
boundElements.push(el('<span></span'));
|
||||||
}
|
}
|
||||||
return new DomView(pv, [DOM.childNodes(root)[0]], [], boundElements);
|
return new DomView(pv, [DOM.childNodes(root)[0]], boundElements);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createElementBinder(parentIndex: number = 0, distanceToParent: number = 1) {
|
|
||||||
return new ElementBinder(
|
|
||||||
{parentIndex: parentIndex, distanceToParent: distanceToParent, textNodeIndices: []});
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('getDirectParentElement', () => {
|
|
||||||
|
|
||||||
it('should return the DomElement of the direct parent', () => {
|
|
||||||
var pv = createProtoView([createElementBinder(), createElementBinder(0, 1)]);
|
|
||||||
var view = createView(pv, 2);
|
|
||||||
expect(view.getDirectParentElement(1)).toBe(view.boundElements[0]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return null if the direct parent is not bound', () => {
|
|
||||||
var pv = createProtoView(
|
|
||||||
[createElementBinder(), createElementBinder(), createElementBinder(0, 2)]);
|
|
||||||
var view = createView(pv, 3);
|
|
||||||
expect(view.getDirectParentElement(2)).toBe(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('setElementProperty', () => {
|
describe('setElementProperty', () => {
|
||||||
var el, view;
|
var el, view;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
view = createView(null, 1);
|
view = createView(null, 1);
|
||||||
el = view.boundElements[0].element;
|
el = view.boundElements[0];
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update the property value', () => {
|
it('should update the property value', () => {
|
||||||
@ -91,7 +69,7 @@ export function main() {
|
|||||||
var el, view;
|
var el, view;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
view = createView(null, 1);
|
view = createView(null, 1);
|
||||||
el = view.boundElements[0].element;
|
el = view.boundElements[0];
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update and remove an attribute', () => {
|
it('should update and remove an attribute', () => {
|
||||||
@ -111,7 +89,7 @@ export function main() {
|
|||||||
var el, view;
|
var el, view;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
view = createView(null, 1);
|
view = createView(null, 1);
|
||||||
el = view.boundElements[0].element;
|
el = view.boundElements[0];
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set and remove a class', () => {
|
it('should set and remove a class', () => {
|
||||||
@ -135,7 +113,7 @@ export function main() {
|
|||||||
var el, view;
|
var el, view;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
view = createView(null, 1);
|
view = createView(null, 1);
|
||||||
el = view.boundElements[0].element;
|
el = view.boundElements[0];
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set and remove styles', () => {
|
it('should set and remove styles', () => {
|
||||||
|
@ -206,11 +206,9 @@ export function main() {
|
|||||||
.then((_) => rtr.navigate('/page/1'))
|
.then((_) => rtr.navigate('/page/1'))
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
rootTC.detectChanges();
|
rootTC.detectChanges();
|
||||||
expect(DOM.getAttribute(rootTC.componentViewChildren[1]
|
expect(DOM.getAttribute(
|
||||||
.componentViewChildren[0]
|
rootTC.componentViewChildren[1].componentViewChildren[0].nativeElement,
|
||||||
.children[0]
|
'href'))
|
||||||
.nativeElement,
|
|
||||||
'href'))
|
|
||||||
.toEqual('/page/2');
|
.toEqual('/page/2');
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
@ -232,7 +230,6 @@ export function main() {
|
|||||||
expect(DOM.getAttribute(rootTC.componentViewChildren[1]
|
expect(DOM.getAttribute(rootTC.componentViewChildren[1]
|
||||||
.componentViewChildren[2]
|
.componentViewChildren[2]
|
||||||
.componentViewChildren[0]
|
.componentViewChildren[0]
|
||||||
.children[0]
|
|
||||||
.nativeElement,
|
.nativeElement,
|
||||||
'href'))
|
'href'))
|
||||||
.toEqual('/book/1984/page/2');
|
.toEqual('/book/1984/page/2');
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
<style>@import "angular2_material/src/components/button/button.css";</style>
|
<style>@import "angular2_material/src/components/button/button.css";</style>
|
||||||
<span class="md-button-wrapper"><content></content></span>
|
<span class="md-button-wrapper"><ng-content></ng-content></span>
|
||||||
|
@ -4,5 +4,5 @@
|
|||||||
<div class="md-checkbox-container">
|
<div class="md-checkbox-container">
|
||||||
<div class="md-checkbox-icon"></div>
|
<div class="md-checkbox-icon"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="md-checkbox-label"><content></content></div>
|
<div class="md-checkbox-label"><ng-content></ng-content></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -64,7 +64,7 @@ export class MdDialog {
|
|||||||
// TODO(jelbourn): Don't use direct DOM access. Need abstraction to create an element
|
// TODO(jelbourn): Don't use direct DOM access. Need abstraction to create an element
|
||||||
// directly on the document body (also needed for web workers stuff).
|
// directly on the document body (also needed for web workers stuff).
|
||||||
// Create a DOM node to serve as a physical host element for the dialog.
|
// Create a DOM node to serve as a physical host element for the dialog.
|
||||||
var dialogElement = this.domRenderer.getRootNodes(containerRef.hostView.render)[0];
|
var dialogElement = containerRef.location.nativeElement;
|
||||||
DOM.appendChild(DOM.query('body'), dialogElement);
|
DOM.appendChild(DOM.query('body'), dialogElement);
|
||||||
|
|
||||||
// TODO(jelbourn): Use hostProperties binding to set these once #1539 is fixed.
|
// TODO(jelbourn): Use hostProperties binding to set these once #1539 is fixed.
|
||||||
@ -109,7 +109,7 @@ export class MdDialog {
|
|||||||
.then((componentRef) => {
|
.then((componentRef) => {
|
||||||
// TODO(tbosch): clean this up when we have custom renderers
|
// TODO(tbosch): clean this up when we have custom renderers
|
||||||
// (https://github.com/angular/angular/issues/1807)
|
// (https://github.com/angular/angular/issues/1807)
|
||||||
var backdropElement = this.domRenderer.getRootNodes(componentRef.hostView.render)[0];
|
var backdropElement = componentRef.location.nativeElement;
|
||||||
DOM.addClass(backdropElement, 'md-backdrop');
|
DOM.addClass(backdropElement, 'md-backdrop');
|
||||||
DOM.appendChild(DOM.query('body'), backdropElement);
|
DOM.appendChild(DOM.query('body'), backdropElement);
|
||||||
return componentRef;
|
return componentRef;
|
||||||
|
@ -5,5 +5,5 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="md-grid-list">
|
<div class="md-grid-list">
|
||||||
<content></content>
|
<ng-content></ng-content>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<style>@import "angular2_material/src/components/grid_list/grid-list.css";</style>
|
<style>@import "angular2_material/src/components/grid_list/grid-list.css";</style>
|
||||||
|
|
||||||
<figure>
|
<figure>
|
||||||
<content></content>
|
<ng-content></ng-content>
|
||||||
</figure>
|
</figure>
|
||||||
|
@ -58,7 +58,7 @@ md-radio-group {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is the style applied to the content (included via <content>). If we could rely on shadow
|
// This is the style applied to the content (included via <ng-content>). If we could rely on shadow
|
||||||
// DOM always being present, this would use the ::content psuedo-class.
|
// DOM always being present, this would use the ::content psuedo-class.
|
||||||
.md-radio-label {
|
.md-radio-label {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -13,6 +13,6 @@
|
|||||||
|
|
||||||
<!-- The label for radio control. -->
|
<!-- The label for radio control. -->
|
||||||
<div class="md-radio-label">
|
<div class="md-radio-label">
|
||||||
<content></content>
|
<ng-content></ng-content>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
<style>@import "angular2_material/src/components/radio/radio-group.css";</style>
|
<style>@import "angular2_material/src/components/radio/radio-group.css";</style>
|
||||||
<content></content>
|
<ng-content></ng-content>
|
||||||
|
@ -7,5 +7,5 @@
|
|||||||
<div class="md-switch-thumb"></div>
|
<div class="md-switch-thumb"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="md-switch-label"><content></content></div>
|
<div class="md-switch-label"><ng-content></ng-content></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,6 +9,7 @@ describe('md-dialog', function() {
|
|||||||
it('should open a dialog', function() {
|
it('should open a dialog', function() {
|
||||||
var openButton = element(by.id('open'));
|
var openButton = element(by.id('open'));
|
||||||
openButton.click();
|
openButton.click();
|
||||||
|
browser.sleep(500);
|
||||||
expect(element(by.css('.md-dialog')).isPresent()).toEqual(true);
|
expect(element(by.css('.md-dialog')).isPresent()).toEqual(true);
|
||||||
|
|
||||||
var dialog = element(by.css('.md-dialog'));
|
var dialog = element(by.css('.md-dialog'));
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
{{ visible ? '▾' : '▸' }} {{title}}
|
{{ visible ? '▾' : '▸' }} {{title}}
|
||||||
</div>
|
</div>
|
||||||
<div [hidden]="!visible" class="zippy__content">
|
<div [hidden]="!visible" class="zippy__content">
|
||||||
<content></content>
|
<ng-content></ng-content>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user