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:
Tobias Bosch 2015-06-24 13:46:39 -07:00
parent d449ea5ca4
commit b1df54501a
82 changed files with 3418 additions and 2806 deletions

View File

@ -58,11 +58,13 @@ export {
export * from './http';
export {
EventDispatcher,
RenderEventDispatcher,
Renderer,
RenderElementRef,
RenderViewRef,
RenderProtoViewRef
RenderProtoViewRef,
RenderFragmentRef,
RenderViewWithFragments
} from 'angular2/src/render/api';
export {
DomRenderer,

View File

@ -137,7 +137,7 @@ class Pane { | Component controller class
<div class="outer">
<h1>{{title}}</h1>
<div class="inner" [hidden]="!open">
<content></content>
<ng-content></ng-content>
</div>
</div>
```

View File

@ -15,8 +15,7 @@ import {List, ListWrapper, Map, MapWrapper} from 'angular2/src/facade/collection
import {DirectiveResolver} from './directive_resolver';
import {AppProtoView} from './view';
import {ElementBinder} from './element_binder';
import {AppProtoView, AppProtoViewMergeMapping} from './view';
import {ProtoViewRef} from './view_ref';
import {DirectiveBinding} from './element_injector';
import {ViewResolver} from './view_resolver';
@ -91,6 +90,7 @@ export class Compiler {
private _appUrl: string;
private _render: renderApi.RenderCompiler;
private _protoViewFactory: ProtoViewFactory;
private _unmergedCyclicEmbeddedProtoViews: RecursiveEmbeddedProtoView[] = [];
/**
* @private
@ -137,13 +137,17 @@ export class Compiler {
Compiler._assertTypeIsComponent(componentBinding);
var directiveMetadata = componentBinding.metadata;
hostPvPromise = this._render.compileHost(directiveMetadata)
.then((hostRenderPv) => {
return this._compileNestedProtoViews(componentBinding, hostRenderPv,
[componentBinding]);
});
hostPvPromise =
this._render.compileHost(directiveMetadata)
.then((hostRenderPv) => {
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 {
@ -156,12 +160,12 @@ export class Compiler {
return protoView;
}
var pvPromise = this._compiling.get(component);
if (isPresent(pvPromise)) {
var resultPromise = this._compiling.get(component);
if (isPresent(resultPromise)) {
// The component is already being compiled, attach to the existing Promise
// instead of re-compiling the component.
// It happens when a template references a component multiple times.
return pvPromise;
return resultPromise;
}
var view = this._viewResolver.resolve(component);
@ -178,14 +182,19 @@ export class Compiler {
ListWrapper.map(directives, (directive) => this._bindDirective(directive)));
var renderTemplate = this._buildRenderTemplate(component, view, boundDirectives);
pvPromise =
this._render.compile(renderTemplate)
.then((renderPv) => {
return this._compileNestedProtoViews(componentBinding, renderPv, boundDirectives);
});
resultPromise = this._render.compile(renderTemplate)
.then((renderPv) => {
var protoView = this._protoViewFactory.createAppProtoViews(
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 pvPromise;
return this._compileNestedProtoViews(renderPv, protoView, component);
});
this._compiling.set(component, resultPromise);
return resultPromise;
}
private _removeDuplicatedDirectives(directives: List<DirectiveBinding>): List<DirectiveBinding> {
@ -194,24 +203,11 @@ export class Compiler {
return MapWrapper.values(directivesMap);
}
private _compileNestedProtoViews(componentBinding, renderPv, directives): Promise<AppProtoView>|
AppProtoView {
var protoViews =
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);
}
}
private _compileNestedProtoViews(renderProtoView: renderApi.ProtoViewDto,
appProtoView: AppProtoView,
componentType: Type): Promise<AppProtoView> {
var nestedPVPromises = [];
ListWrapper.forEach(this._collectComponentElementBinders(protoViews), (elementBinder) => {
this._loopComponentElementBinders(appProtoView, (parentPv, elementBinder) => {
var nestedComponent = elementBinder.componentDirective;
var elementBinderDone =
(nestedPv: AppProtoView) => { elementBinder.nestedProtoView = nestedPv; };
@ -222,24 +218,85 @@ export class Compiler {
elementBinderDone(<AppProtoView>nestedCall);
}
});
if (nestedPVPromises.length > 0) {
return PromiseWrapper.all(nestedPVPromises).then((_) => protoView);
} else {
return protoView;
}
return PromiseWrapper.all(nestedPVPromises)
.then((_) => {
var appProtoViewsToMergeInto = [];
var mergeRenderProtoViews = this._collectMergeRenderProtoViewsRecurse(
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> {
var componentElementBinders = [];
ListWrapper.forEach(protoViews, (protoView) => {
ListWrapper.forEach(protoView.elementBinders, (elementBinder) => {
if (isPresent(elementBinder.componentDirective)) {
componentElementBinders.push(elementBinder);
private _mergeProtoViews(
appProtoViewsToMergeInto: AppProtoView[],
mergeRenderProtoViews:
List<renderApi.RenderProtoViewRef | List<any>>): Promise<AppProtoView> {
return this._render.mergeProtoViewsRecursively(mergeRenderProtoViews)
.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 {
@ -299,3 +356,7 @@ export class Compiler {
}
}
}
class RecursiveEmbeddedProtoView {
constructor(public appProtoView: AppProtoView, public renderProtoView: renderApi.ProtoViewDto) {}
}

View File

@ -278,7 +278,7 @@ export class DirectiveBinding extends ResolvedBinding {
// TODO(rado): benchmark and consider rolling in as ElementInjector fields.
export class PreBuiltObjects {
constructor(public viewManager: avmModule.AppViewManager, public view: viewModule.AppView,
public protoView: viewModule.AppProtoView) {}
public elementRef: ElementRef, public protoView: viewModule.AppProtoView) {}
}
export class EventEmitterAccessor {
@ -575,7 +575,7 @@ export class ElementInjector extends TreeNode<ElementInjector> implements Depend
getComponent(): any { return this._strategy.getComponent(); }
getElementRef(): ElementRef { return this._preBuiltObjects.view.elementRefs[this._proto.index]; }
getElementRef(): ElementRef { return this._preBuiltObjects.elementRef; }
getViewContainerRef(): ViewContainerRef {
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
// the surrounding component's change detector to directives.
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;
} else {
return this._preBuiltObjects.view.changeDetector.ref;

View File

@ -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 {RenderViewRef, RenderElementRef, Renderer} from 'angular2/src/render/api';
@ -24,9 +24,18 @@ export class ElementRef implements RenderElementRef {
*/
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.boundElementIndex = boundElementIndex;
this.renderBoundElementIndex = renderBoundElementIndex;
}
/**

View File

@ -11,7 +11,8 @@ import {
DirectiveRecord,
ProtoChangeDetector,
DEFAULT,
ChangeDetectorDefinition
ChangeDetectorDefinition,
ASTWithSource
} from 'angular2/change_detection';
import * as renderApi from 'angular2/src/render/api';
@ -21,16 +22,16 @@ import {ProtoElementInjector, DirectiveBinding} from './element_injector';
class BindingRecordsCreator {
_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> {
var bindings = [];
this._createTextNodeRecords(bindings, textBindings);
for (var boundElementIndex = 0; boundElementIndex < elementBinders.length;
boundElementIndex++) {
var renderElementBinder = elementBinders[boundElementIndex];
this._createTextNodeRecords(bindings, renderElementBinder);
this._createElementPropertyRecords(bindings, boundElementIndex, renderElementBinder);
this._createDirectiveRecords(bindings, boundElementIndex, renderElementBinder.directives,
allDirectiveMetadatas);
@ -55,13 +56,10 @@ class BindingRecordsCreator {
return directiveRecords;
}
_createTextNodeRecords(bindings: List<BindingRecord>,
renderElementBinder: renderApi.ElementBinder) {
if (isBlank(renderElementBinder.textBindings)) return;
ListWrapper.forEach(renderElementBinder.textBindings, (b) => {
bindings.push(BindingRecord.createForTextNode(b, this._textNodeIndex++));
});
_createTextNodeRecords(bindings: List<BindingRecord>, textBindings: List<ASTWithSource>) {
for (var i = 0; i < textBindings.length; i++) {
bindings.push(BindingRecord.createForTextNode(textBindings[i], i));
}
}
_createElementPropertyRecords(bindings: List<BindingRecord>, boundElementIndex: number,
@ -162,7 +160,7 @@ export class ProtoViewFactory {
createAppProtoViews(hostComponentBinding: DirectiveBinding,
rootRenderProtoView: renderApi.ProtoViewDto,
allDirectives: List<DirectiveBinding>): List<AppProtoView> {
allDirectives: List<DirectiveBinding>): AppProtoView {
var allRenderDirectiveMetadata =
ListWrapper.map(allDirectives, directiveBinding => directiveBinding.metadata);
var nestedPvsWithIndex = _collectNestedProtoViews(rootRenderProtoView);
@ -176,7 +174,7 @@ export class ProtoViewFactory {
changeDetectorDefs,
changeDetectorDef => this._changeDetection.createProtoChangeDetector(changeDetectorDef));
var appProtoViews = ListWrapper.createFixedSize(nestedPvsWithIndex.length);
ListWrapper.forEach(nestedPvsWithIndex, (pvWithIndex) => {
ListWrapper.forEach(nestedPvsWithIndex, (pvWithIndex: RenderProtoViewWithIndex) => {
var appProtoView =
_createAppProtoView(pvWithIndex.renderProtoView, protoChangeDetectors[pvWithIndex.index],
nestedPvVariableBindings[pvWithIndex.index], allDirectives);
@ -186,7 +184,7 @@ export class ProtoViewFactory {
}
appProtoViews[pvWithIndex.index] = appProtoView;
});
return appProtoViews;
return appProtoViews[0];
}
}
@ -209,6 +207,7 @@ function _collectNestedProtoViews(
if (isBlank(result)) {
result = [];
}
// reserve the place in the array
result.push(
new RenderProtoViewWithIndex(renderProtoView, result.length, parentIndex, boundElementIndex));
var currentIndex = result.length - 1;
@ -230,8 +229,8 @@ function _getChangeDetectorDefinitions(
return ListWrapper.map(nestedPvsWithIndex, (pvWithIndex) => {
var elementBinders = pvWithIndex.renderProtoView.elementBinders;
var bindingRecordsCreator = new BindingRecordsCreator();
var bindingRecords =
bindingRecordsCreator.getBindingRecords(elementBinders, allRenderDirectiveMetadata);
var bindingRecords = bindingRecordsCreator.getBindingRecords(
pvWithIndex.renderProtoView.textBindings, elementBinders, allRenderDirectiveMetadata);
var directiveRecords =
bindingRecordsCreator.getDirectiveRecords(elementBinders, allRenderDirectiveMetadata);
var strategyName = DEFAULT;
@ -255,8 +254,9 @@ function _createAppProtoView(
renderProtoView: renderApi.ProtoViewDto, protoChangeDetector: ProtoChangeDetector,
variableBindings: Map<string, string>, allDirectives: List<DirectiveBinding>): AppProtoView {
var elementBinders = renderProtoView.elementBinders;
var protoView = new AppProtoView(renderProtoView.render, protoChangeDetector, variableBindings,
createVariableLocations(elementBinders));
var protoView = new AppProtoView(renderProtoView.type, protoChangeDetector, variableBindings,
createVariableLocations(elementBinders),
renderProtoView.textBindings.length);
_createElementBinders(protoView, elementBinders, allDirectives);
_bindDirectiveEvents(protoView, elementBinders);

View File

@ -20,10 +20,46 @@ import {
import {ElementBinder} from './element_binder';
import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
import * as renderApi from 'angular2/src/render/api';
import {EventDispatcher} from 'angular2/src/render/api';
import {ViewRef} from './view_ref';
import {RenderEventDispatcher} from 'angular2/src/render/api';
import {ViewRef, internalView} from './view_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 {
// The order in this list matches the DOM order.
views: List<AppView> = [];
@ -33,17 +69,34 @@ export class AppViewContainer {
* Cost of making objects: http://jsperf.com/instantiate-size-of-object
*
*/
export class AppView implements ChangeDispatcher, EventDispatcher {
render: renderApi.RenderViewRef = null;
/// This list matches the _nodes list. It is sparse, since only Elements have ElementInjector
export class AppView implements ChangeDispatcher, RenderEventDispatcher {
// AppViews that have been merged in depth first order.
// 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>;
// 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;
changeDetector: ChangeDetector = null;
componentChildViews: List<AppView> = null;
viewContainers: List<AppViewContainer>;
// ViewContainers of all AppViews in views grouped by view.
// This list is shared between all merged views. Use this.elementOffset to get the local
// 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;
// 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>;
ref: ViewRef;
changeDetector: ChangeDetector = null;
/**
* 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;
constructor(public renderer: renderApi.Renderer, public proto: AppProtoView,
protoLocals: Map<string, any>) {
this.viewContainers = ListWrapper.createFixedSize(this.proto.elementBinders.length);
this.elementRefs = ListWrapper.createFixedSize(this.proto.elementBinders.length);
public mainMergeMapping: AppProtoViewMergeMapping, public viewOffset: number,
public elementOffset: number, public textOffset: number,
protoLocals: Map<string, any>, public render: renderApi.RenderViewRef,
public renderFragment: renderApi.RenderFragmentRef) {
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
}
init(changeDetector: ChangeDetector, elementInjectors: List<ElementInjector>,
rootElementInjectors: List<ElementInjector>, preBuiltObjects: List<PreBuiltObjects>,
componentChildViews: List<AppView>) {
views: List<AppView>, elementRefs: List<ElementRef>,
viewContainers: List<AppViewContainer>) {
this.changeDetector = changeDetector;
this.elementInjectors = elementInjectors;
this.rootElementInjectors = rootElementInjectors;
this.preBuiltObjects = preBuiltObjects;
this.componentChildViews = componentChildViews;
this.views = views;
this.elementRefs = elementRefs;
this.viewContainers = viewContainers;
}
setLocal(contextName: string, value): void {
@ -98,49 +153,57 @@ export class AppView implements ChangeDispatcher, EventDispatcher {
*
* @param {string} eventName
* @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();
locals.set('$event', eventObj);
this.dispatchEvent(binderIndex, eventName, locals);
this.dispatchEvent(boundElementIndex, eventName, locals);
}
// dispatch to element injector or text nodes based on context
notifyOnBinding(b: BindingRecord, currentValue: any): void {
if (b.isElementProperty()) {
this.renderer.setElementProperty(this.elementRefs[b.elementIndex], b.propertyName,
currentValue);
} else if (b.isElementAttribute()) {
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);
if (b.isTextNode()) {
this.renderer.setText(
this.render, this.mainMergeMapping.renderTextIndices[b.elementIndex + this.textOffset],
currentValue);
} 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 {
var eiCount = this.proto.elementBinders.length;
var ei = this.elementInjectors;
for (var i = ei.length - 1; i >= 0; i--) {
if (isPresent(ei[i])) ei[i].onAllChangesDone();
for (var i = eiCount - 1; i >= 0; i--) {
if (isPresent(ei[i + this.elementOffset])) ei[i + this.elementOffset].onAllChangesDone();
}
}
getDirectiveFor(directive: DirectiveIndex): any {
var elementInjector = this.elementInjectors[directive.elementIndex];
var elementInjector = this.elementInjectors[this.elementOffset + directive.elementIndex];
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 {
var childView = this.componentChildViews[directive.elementIndex];
var childView = this.getNestedView(this.elementOffset + directive.elementIndex);
return isPresent(childView) ? childView.changeDetector : null;
}
@ -148,15 +211,24 @@ export class AppView implements ChangeDispatcher, EventDispatcher {
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
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.
// However, in a rare circumstance the view might get dehydrated, in between the event
// queuing up and firing.
var allowDefaultBehavior = true;
if (this.hydrated()) {
var elBinder = this.proto.elementBinders[elementIndex];
var elBinder = this.proto.elementBinders[boundElementIndex - this.elementOffset];
if (isBlank(elBinder.hostListeners)) return allowDefaultBehavior;
var eventMap = elBinder.hostListeners[eventName];
if (isBlank(eventMap)) return allowDefaultBehavior;
@ -165,7 +237,7 @@ export class AppView implements ChangeDispatcher, EventDispatcher {
if (directiveIndex === -1) {
context = this.context;
} else {
context = this.elementInjectors[elementIndex].getDirectiveAtIndex(directiveIndex);
context = this.elementInjectors[boundElementIndex].getDirectiveAtIndex(directiveIndex);
}
var result = expr.eval(context, new Locals(this.locals, locals));
if (isPresent(result)) {
@ -183,11 +255,11 @@ export class AppView implements ChangeDispatcher, EventDispatcher {
export class AppProtoView {
elementBinders: List<ElementBinder> = [];
protoLocals: Map<string, any> = new Map();
mergeMapping: AppProtoViewMergeMapping;
constructor(public render: renderApi.RenderProtoViewRef,
public protoChangeDetector: ProtoChangeDetector,
constructor(public type: renderApi.ViewType, public protoChangeDetector: ProtoChangeDetector,
public variableBindings: Map<string, string>,
public variableLocations: Map<string, number>) {
public variableLocations: Map<string, number>, public textBindingCount: number) {
if (isPresent(variableBindings)) {
MapWrapper.forEach(variableBindings,
(templateName, _) => { this.protoLocals.set(templateName, null); });

View File

@ -4,7 +4,13 @@ import * as viewModule from './view';
import {ElementRef} from './element_ref';
import {ProtoViewRef, ViewRef, internalView, internalProtoView} from './view_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 {AppViewPool} from './view_pool';
import {AppViewListener} from './view_listener';
@ -22,18 +28,6 @@ export class AppViewManager {
constructor(private _viewPool: AppViewPool, private _viewListener: AppViewListener,
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.
*/
@ -47,7 +41,8 @@ export class AppViewManager {
*/
// TODO(misko): remove https://github.com/angular/angular/issues/2891
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 {
var hostView = internalView(hostLocation.parentView);
var boundElementIndex = hostLocation.boundElementIndex;
var componentView = hostView.componentChildViews[boundElementIndex];
var componentView = hostView.getNestedView(boundElementIndex);
if (isBlank(componentView)) {
throw new BaseException(`There is no component directive at element ${boundElementIndex}`);
}
var elementIndex = componentView.proto.variableLocations.get(variableName);
if (isBlank(elementIndex)) {
var binderIdx = componentView.proto.variableLocations.get(variableName);
if (isBlank(binderIdx)) {
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)) {
hostElementSelector = hostProtoView.elementBinders[0].componentDirective.metadata.selector;
}
var renderView = this._renderer.createRootHostView(hostProtoView.render, hostElementSelector);
var hostView = this._utils.createView(hostProtoView, renderView, this, this._renderer);
this._renderer.setEventDispatcher(hostView.render, hostView);
this._createViewRecurse(hostView);
this._viewListener.viewCreated(hostView);
var renderViewWithFragments = this._renderer.createRootHostView(
hostProtoView.mergeMapping.renderProtoViewRef,
hostProtoView.mergeMapping.renderFragmentCount, hostElementSelector);
var hostView = this._createMainView(hostProtoView, renderViewWithFragments);
this._renderer.hydrateView(hostView.render);
this._utils.hydrateRootHostView(hostView, injector);
this._viewHydrateRecurse(hostView);
return hostView.ref;
}
@ -162,14 +156,14 @@ export class AppViewManager {
* Remove the View created with {@link AppViewManager#createRootHostView}.
*/
destroyRootHostView(hostViewRef: ViewRef) {
// Note: Don't detach the hostView as we want to leave the
// root element in place. Also don't put the hostView into the view pool
// Note: Don't put the hostView into the view pool
// as it is depending on the element for which it was created.
var hostView = internalView(hostViewRef);
// We do want to destroy the component view though.
this._viewDehydrateRecurse(hostView, true);
this._renderer.destroyView(hostView.render);
this._renderer.detachFragment(hostView.renderFragment);
this._renderer.dehydrateView(hostView.render);
this._viewDehydrateRecurse(hostView);
this._viewListener.viewDestroyed(hostView);
this._renderer.destroyView(hostView.render);
}
/**
@ -187,19 +181,42 @@ export class AppViewManager {
if (isPresent(context)) {
contextView = internalView(context.parentView);
contextBoundElementIndex = context.boundElementIndex;
} else {
contextView = parentView;
contextBoundElementIndex = boundElementIndex;
}
var view = this._createPooledView(protoView);
this._renderer.attachViewInContainer(viewContainerLocation, atIndex, view.render);
var embeddedFragmentView = contextView.getNestedView(contextBoundElementIndex);
var view;
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,
contextBoundElementIndex, atIndex, view);
this._utils.hydrateViewInContainer(parentView, boundElementIndex, contextView,
contextBoundElementIndex, atIndex, bindings);
this._viewHydrateRecurse(view);
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}.
@ -226,7 +243,7 @@ export class AppViewManager {
// Right now we are destroying any special
// context view that might have been used.
this._utils.attachViewInContainer(parentView, boundElementIndex, null, null, atIndex, view);
this._renderer.attachViewInContainer(viewContainerLocation, atIndex, view.render);
this._attachRenderView(parentView, boundElementIndex, atIndex, view);
return viewRef;
}
@ -240,86 +257,65 @@ export class AppViewManager {
var viewContainer = parentView.viewContainers[boundElementIndex];
var view = viewContainer.views[atIndex];
this._utils.detachViewInContainer(parentView, boundElementIndex, atIndex);
this._renderer.detachViewInContainer(viewContainerLocation, atIndex, view.render);
this._renderer.detachFragment(view.renderFragment);
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 {
var view = this._viewPool.getView(protoView);
if (isBlank(view)) {
view = this._utils.createView(protoView, this._renderer.createView(protoView.render), this,
this._renderer);
this._renderer.setEventDispatcher(view.render, view);
this._createViewRecurse(view);
this._viewListener.viewCreated(view);
view = this._createMainView(
protoView, this._renderer.createView(protoView.mergeMapping.renderProtoViewRef,
protoView.mergeMapping.renderFragmentCount));
}
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) {
var wasReturned = this._viewPool.returnView(view);
if (!wasReturned) {
this._renderer.destroyView(view.render);
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 view = viewContainer.views[atIndex];
this._viewDehydrateRecurse(view, false);
this._viewDehydrateRecurse(view);
this._utils.detachViewInContainer(parentView, boundElementIndex, atIndex);
this._renderer.detachViewInContainer(parentView.elementRefs[boundElementIndex], atIndex,
view.render);
this._destroyPooledView(view);
}
_destroyComponentView(hostView, boundElementIndex, componentView) {
this._viewDehydrateRecurse(componentView, false);
this._renderer.detachComponentView(hostView.elementRefs[boundElementIndex],
componentView.render);
this._utils.detachComponentView(hostView, boundElementIndex);
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]);
}
if (view.viewOffset > 0) {
// Case 1: a view that is part of another view.
// Just detach the fragment
this._renderer.detachFragment(view.renderFragment);
} else {
// Case 2: a view that is not part of another view.
// dehydrate and destroy it.
this._renderer.dehydrateView(view.render);
this._renderer.detachFragment(view.renderFragment);
this._destroyPooledView(view);
}
}
_viewDehydrateRecurse(view: viewModule.AppView, forceDestroyComponents) {
this._utils.dehydrateView(view);
this._renderer.dehydrateView(view.render);
var binders = view.proto.elementBinders;
for (var i = 0; i < binders.length; i++) {
var componentView = view.componentChildViews[i];
if (isPresent(componentView)) {
if (forceDestroyComponents) {
this._destroyComponentView(view, i, componentView);
} else {
this._viewDehydrateRecurse(componentView, false);
}
}
var vc = view.viewContainers[i];
_viewDehydrateRecurse(view: viewModule.AppView) {
if (view.hydrated()) {
this._utils.dehydrateView(view);
}
var viewContainers = view.viewContainers;
for (var i = view.elementOffset, ii = view.elementOffset + view.proto.mergeMapping.elementCount;
i < ii; i++) {
var vc = viewContainers[i];
if (isPresent(vc)) {
for (var j = vc.views.length - 1; j >= 0; j--) {
this._destroyViewInContainer(view, i, j);

View File

@ -3,10 +3,12 @@ import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src
import * as eli from './element_injector';
import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
import * as viewModule from './view';
import {internalView} from './view_ref';
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 {RenderViewRef} from 'angular2/src/render/api';
import {RenderViewRef, RenderFragmentRef, ViewType} from 'angular2/src/render/api';
@Injectable()
export class AppViewManagerUtils {
@ -17,68 +19,84 @@ export class AppViewManagerUtils {
return eli.getComponent();
}
createView(protoView: viewModule.AppProtoView, renderView: RenderViewRef,
createView(mergedParentViewProto: viewModule.AppProtoView,
renderViewWithFragments: RenderViewWithFragments,
viewManager: avmModule.AppViewManager, renderer: Renderer): viewModule.AppView {
var view = new viewModule.AppView(renderer, protoView, protoView.protoLocals);
// TODO(tbosch): pass RenderViewRef as argument to AppView!
view.render = renderView;
var renderFragments = renderViewWithFragments.fragmentRefs;
var renderView = renderViewWithFragments.viewRef;
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 elementInjectors = ListWrapper.createFixedSize(binders.length);
var rootElementInjectors = [];
var preBuiltObjects = ListWrapper.createFixedSize(binders.length);
var componentChildViews = ListWrapper.createFixedSize(binders.length);
var elementOffset = 0;
var textOffset = 0;
var fragmentIdx = 0;
for (var viewOffset = 0; viewOffset < viewCount; viewOffset++) {
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++) {
var binder = binders[binderIdx];
var elementInjector = null;
// elementInjectors and rootElementInjectors
var protoElementInjector = binder.protoElementInjector;
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
var protoElementInjector = binder.protoElementInjector;
if (isPresent(protoElementInjector)) {
if (isPresent(protoElementInjector.parent)) {
var parentElementInjector = elementInjectors[protoElementInjector.parent.index];
elementInjector = protoElementInjector.instantiate(parentElementInjector);
} else {
elementInjector = protoElementInjector.instantiate(null);
rootElementInjectors.push(elementInjector);
// elementRefs
var el = new ElementRef(
currentView.ref, boundElementIndex,
mergedParentViewProto.mergeMapping.renderElementIndices[boundElementIndex], renderer);
elementRefs[el.boundElementIndex] = el;
// preBuiltObjects
if (isPresent(elementInjector)) {
var embeddedProtoView = binder.hasEmbeddedProtoView() ? binder.nestedProtoView : null;
preBuiltObjects[boundElementIndex] =
new eli.PreBuiltObjects(viewManager, currentView, el, embeddedProtoView);
}
}
elementInjectors[binderIdx] = elementInjector;
// preBuiltObjects
if (isPresent(elementInjector)) {
var embeddedProtoView = binder.hasEmbeddedProtoView() ? binder.nestedProtoView : null;
preBuiltObjects[binderIdx] = new eli.PreBuiltObjects(viewManager, view, embeddedProtoView);
currentView.init(protoView.protoChangeDetector.instantiate(currentView), elementInjectors,
rootElementInjectors, preBuiltObjects, views, elementRefs, viewContainers);
if (isPresent(parentView) && protoView.type === ViewType.COMPONENT) {
parentView.changeDetector.addShadowDomChild(currentView.changeDetector);
}
elementOffset += protoView.elementBinders.length;
textOffset += protoView.textBindingCount;
}
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);
return views[0];
}
hydrateRootHostView(hostView: viewModule.AppView, injector: Injector) {
@ -94,7 +112,11 @@ export class AppViewManagerUtils {
contextBoundElementIndex = boundElementIndex;
}
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);
var sibling;
if (atIndex == 0) {
@ -124,7 +146,9 @@ export class AppViewManagerUtils {
inj.unlink();
} else {
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);
}
_hydrateView(view: viewModule.AppView, imperativelyCreatedInjector: Injector,
_hydrateView(initView: viewModule.AppView, imperativelyCreatedInjector: Injector,
hostElementInjector: eli.ElementInjector, context: Object, parentLocals: Locals) {
view.context = context;
view.locals.parent = parentLocals;
var viewIdx = initView.viewOffset;
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;
for (var i = 0; i < binders.length; ++i) {
var elementInjector = view.elementInjectors[i];
if (isPresent(elementInjector)) {
elementInjector.hydrate(imperativelyCreatedInjector, hostElementInjector,
view.preBuiltObjects[i]);
this._populateViewLocals(view, elementInjector);
this._setUpEventEmitters(view, elementInjector, i);
this._setUpHostActions(view, elementInjector, i);
if (isPresent(elementInjector)) {
elementInjector.hydrate(imperativelyCreatedInjector, hostElementInjector,
currView.preBuiltObjects[boundElementIndex]);
this._populateViewLocals(currView, elementInjector, boundElementIndex);
this._setUpEventEmitters(currView, elementInjector, boundElementIndex);
this._setUpHostActions(currView, elementInjector, boundElementIndex);
}
}
var pipes = this._getPipes(imperativelyCreatedInjector, hostElementInjector);
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) {
@ -174,11 +218,12 @@ export class AppViewManagerUtils {
return null;
}
_populateViewLocals(view: viewModule.AppView, elementInjector: eli.ElementInjector): void {
_populateViewLocals(view: viewModule.AppView, elementInjector: eli.ElementInjector,
boundElementIdx: number): void {
if (isPresent(elementInjector.getDirectiveVariableBindings())) {
MapWrapper.forEach(elementInjector.getDirectiveVariableBindings(), (directiveIndex, name) => {
if (isBlank(directiveIndex)) {
view.locals.set(name, elementInjector.getElementRef().nativeElement);
view.locals.set(name, view.elementRefs[boundElementIdx].nativeElement);
} else {
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,
boundElementIndex: number) {
var emitters = elementInjector.getEventEmitterAccessors();
@ -223,18 +259,25 @@ export class AppViewManagerUtils {
}
}
dehydrateView(view: viewModule.AppView) {
var binders = view.proto.elementBinders;
for (var i = 0; i < binders.length; ++i) {
var elementInjector = view.elementInjectors[i];
if (isPresent(elementInjector)) {
elementInjector.dehydrate();
dehydrateView(initView: viewModule.AppView) {
for (var viewIdx = initView.viewOffset,
endViewOffset = viewIdx + initView.proto.mergeMapping.viewCount;
viewIdx < endViewOffset; viewIdx++) {
var currView = initView.views[viewIdx];
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();
}
}

View File

@ -1,6 +1,6 @@
import {isPresent} from 'angular2/src/facade/lang';
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
export function internalView(viewRef: ViewRef): viewModule.AppView {
@ -71,6 +71,11 @@ export class ViewRef {
*/
get render(): RenderViewRef { return this._view.render; }
/**
* Return {@link RenderFragmentRef}
*/
get renderFragment(): RenderFragmentRef { return this._view.renderFragment; }
/**
* Set local variable for a view.
*

View File

@ -45,9 +45,7 @@ export class DebugElement {
* @return {List<DebugElement>}
*/
get children(): List<DebugElement> {
var thisElementBinder = this._parentView.proto.elementBinders[this._boundElementIndex];
return this._getChildElements(this._parentView, thisElementBinder.index);
return this._getChildElements(this._parentView, this._boundElementIndex);
}
/**
@ -57,7 +55,7 @@ export class DebugElement {
* @return {List<DebugElement>}
*/
get componentViewChildren(): List<DebugElement> {
var shadowView = this._parentView.componentChildViews[this._boundElementIndex];
var shadowView = this._parentView.getNestedView(this._boundElementIndex);
if (!isPresent(shadowView)) {
// The current element is not a component.
@ -120,14 +118,14 @@ export class DebugElement {
var els = [];
var parentElementBinder = null;
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) {
var binder = view.proto.elementBinders[i];
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)) {
ListWrapper.forEach(views.views, (nextView) => {
els = ListWrapper.concat(els, this._getChildElements(nextView, null));
@ -184,7 +182,11 @@ export class By {
static all(): Function { return (debugElement) => true; }
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> {
return (debugElement) => { return debugElement.hasDirective(type); };

View File

@ -56,7 +56,8 @@ export class DebugElementViewListener implements AppViewListener {
_allViewsById.set(viewId, view);
_allIdsByView.set(view, viewId);
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]);
}
}

View File

@ -63,7 +63,7 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
get attrToPropMap(): any { return _attrToPropMap; }
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); }
on(el, evt, listener) { el.addEventListener(evt, listener, false); }
onAndCancel(el, evt, listener): Function {
@ -112,17 +112,16 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
return res;
}
clearNodes(el) {
for (var i = 0; i < el.childNodes.length; i++) {
this.remove(el.childNodes[i]);
while (el.firstChild) {
el.firstChild.remove();
}
}
appendChild(el, node) { el.appendChild(node); }
removeChild(el, node) { el.removeChild(node); }
replaceChild(el: Node, newChild, oldChild) { el.replaceChild(newChild, oldChild); }
remove(el): Node {
var parent = el.parentNode;
parent.removeChild(el);
return el;
remove(node): Node {
node.remove();
return node;
}
insertBefore(el, node) { el.parentNode.insertBefore(node, el); }
insertAllBefore(el, nodes) {

View File

@ -31,7 +31,7 @@ export class DomAdapter {
parse(templateHtml: string) { 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(); }
on(el, evt, listener) { throw _abstract(); }
onAndCancel(el, evt, listener): Function { throw _abstract(); }
@ -76,7 +76,7 @@ export class DomAdapter {
getShadowRoot(el): any { throw _abstract(); }
getHost(el): any { 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(); }
getElementsByTagName(element, name: string): List<HTMLElement> { throw _abstract(); }
classList(element): List<any> { throw _abstract(); }
@ -105,7 +105,7 @@ export class DomAdapter {
isElementNode(node): boolean { throw _abstract(); }
hasShadowRoot(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(); }
isStyleRule(rule): boolean { throw _abstract(); }
isMediaRule(rule): boolean { throw _abstract(); }

View File

@ -19,6 +19,8 @@ var _attrToPropMap = {
};
var defDoc = null;
var mapProps = ['attribs', 'x-attribsNamespace', 'x-attribsPrefix'];
function _notImplemented(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; }
getDistributedNodes(el: any): List<Node> { throw _notImplemented('getDistributedNodes'); }
clone(node: Node): Node {
// e.g. document fragment
if ((<any>node).type === 'root') {
var serialized = serializer.serialize(node);
var newParser = new parse5.Parser(parse5.TreeAdapters.htmlparser2);
return newParser.parseFragment(serialized);
} else {
var temp = treeAdapter.createElement("template", null, []);
treeAdapter.appendChild(temp, node);
var serialized = serializer.serialize(temp);
var newParser = new parse5.Parser(parse5.TreeAdapters.htmlparser2);
return newParser.parseFragment(serialized).childNodes[0];
}
var _recursive = (node) => {
var nodeClone = Object.create(Object.getPrototypeOf(node));
for (var prop in node) {
var desc = Object.getOwnPropertyDescriptor(node, prop);
if (desc && 'value' in desc && typeof desc.value !== 'object') {
nodeClone[prop] = node[prop];
}
}
nodeClone.parent = null;
nodeClone.prev = null;
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> {
return this.querySelectorAll(element, "." + name);

View File

@ -221,13 +221,17 @@ bool isJsObject(o) {
return false;
}
var _assertionsEnabled = null;
bool assertionsEnabled() {
try {
assert(false);
return false;
} catch (e) {
return true;
if (_assertionsEnabled == null) {
try {
assert(false);
_assertionsEnabled = false;
} catch (e) {
_assertionsEnabled = true;
}
}
return _assertionsEnabled;
}
// Can't be all uppercase as our transpiler would think it is a special directive...

View File

@ -48,11 +48,10 @@ export class ElementBinder {
// that replaced the values that should be extracted from the element
// with a local name
eventBindings: List<EventBinding>;
textBindings: List<ASTWithSource>;
readAttributes: Map<string, string>;
constructor({index, parentIndex, distanceToParent, directives, nestedProtoView, propertyBindings,
variableBindings, eventBindings, textBindings, readAttributes}: {
variableBindings, eventBindings, readAttributes}: {
index?: number,
parentIndex?: number,
distanceToParent?: number,
@ -61,7 +60,6 @@ export class ElementBinder {
propertyBindings?: List<ElementPropertyBinding>,
variableBindings?: Map<string, string>,
eventBindings?: List<EventBinding>,
textBindings?: List<ASTWithSource>,
readAttributes?: Map<string, string>
} = {}) {
this.index = index;
@ -72,7 +70,6 @@ export class ElementBinder {
this.propertyBindings = propertyBindings;
this.variableBindings = variableBindings;
this.eventBindings = eventBindings;
this.textBindings = textBindings;
this.readAttributes = readAttributes;
}
}
@ -116,17 +113,20 @@ export class ProtoViewDto {
elementBinders: List<ElementBinder>;
variableBindings: Map<string, string>;
type: ViewType;
textBindings: List<ASTWithSource>;
constructor({render, elementBinders, variableBindings, type}: {
constructor({render, elementBinders, variableBindings, type, textBindings}: {
render?: RenderProtoViewRef,
elementBinders?: List<ElementBinder>,
variableBindings?: Map<string, string>,
type?: ViewType
type?: ViewType,
textBindings?: List<ASTWithSource>
}) {
this.render = render;
this.elementBinders = elementBinders;
this.variableBindings = variableBindings;
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 {}
// 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 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 {
/**
* 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.
*/
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.
*/
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.
*/
boundElementIndex: number;
renderBoundElementIndex: number;
}
export class Renderer {
/**
* 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
* ProtoViewDto.HOST_VIEW_TYPE
* @param {any} hostElementSelector css selector for the host element (will be queried against the
* main document)
* @return {RenderViewRef} the created view
* @return {RenderViewWithFragments} the created view including fragments
*/
createRootHostView(hostProtoViewRef: RenderProtoViewRef,
hostElementSelector: string): RenderViewRef {
createRootHostView(hostProtoViewRef: RenderProtoViewRef, fragmentCount: number,
hostElementSelector: string): RenderViewWithFragments {
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
@ -349,27 +393,20 @@ export class Renderer {
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
* given index.
* Detaches a fragment.
*/
attachViewInContainer(location: RenderElementRef, atIndex: number, viewRef: RenderViewRef) {}
/**
* 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) {}
detachFragment(fragmentRef: RenderFragmentRef) {}
/**
* 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
*/
setEventDispatcher(viewRef: RenderViewRef, dispatcher: EventDispatcher) {}
setEventDispatcher(viewRef: RenderViewRef, dispatcher: RenderEventDispatcher) {}
}
/**
* 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.
* @param {Map<string, any>} locals Locals to be used to evaluate the
* event expressions
*/
dispatchEvent(elementIndex: number, eventName: string, locals: Map<string, any>);
dispatchRenderEvent(elementIndex: number, eventName: string, locals: Map<string, any>);
}

View File

@ -13,7 +13,9 @@ import {ProtoViewDto, ViewType} from '../../api';
*/
export class CompilePipeline {
_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,
compilationCtxtDescription: string = ''): List<CompileElement> {
@ -22,7 +24,8 @@ export class CompilePipeline {
}
var results = [];
var rootCompileElement = new CompileElement(rootElement, compilationCtxtDescription);
rootCompileElement.inheritedProtoView = new ProtoViewBuilder(rootElement, protoViewType);
rootCompileElement.inheritedProtoView =
new ProtoViewBuilder(rootElement, protoViewType, this._useNativeShadowDom);
rootCompileElement.isViewRoot = true;
this._process(results, null, rootCompileElement, compilationCtxtDescription);
return results;

View File

@ -10,13 +10,15 @@ import {
ViewType,
DirectiveMetadata,
RenderCompiler,
RenderProtoViewRef
RenderProtoViewRef,
RenderProtoViewMergeMapping
} from '../../api';
import {CompilePipeline} from './compile_pipeline';
import {ViewLoader} from 'angular2/src/render/dom/compiler/view_loader';
import {CompileStepFactory, DefaultStepFactory} from './compile_step_factory';
import {Parser} from 'angular2/change_detection';
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
@ -24,7 +26,10 @@ import {ShadowDomStrategy} from '../shadow_dom/shadow_dom_strategy';
* the CompilePipeline and the CompileSteps.
*/
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> {
var tplPromise = this._viewLoader.load(view);
@ -42,13 +47,21 @@ export class DomCompiler extends RenderCompiler {
styleAbsUrls: null,
directives: [directiveMetadata]
});
var element = DOM.createElement(directiveMetadata.selector);
return this._compileTemplate(hostViewDef, element, ViewType.HOST);
var template = DOM.createTemplate('');
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,
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);
return PromiseWrapper.resolve(compileElements[0].inheritedProtoView.build());
@ -58,6 +71,7 @@ export class DomCompiler extends RenderCompiler {
@Injectable()
export class DefaultDomCompiler extends DomCompiler {
constructor(parser: Parser, shadowDomStrategy: ShadowDomStrategy, viewLoader: ViewLoader) {
super(new DefaultStepFactory(parser, shadowDomStrategy), viewLoader);
super(new DefaultStepFactory(parser, shadowDomStrategy), viewLoader,
shadowDomStrategy.hasNativeContentElement());
}
}

View File

@ -27,11 +27,11 @@ var _SELECTOR_REGEXP = RegExpWrapper.create(
*/
export class CssSelector {
element: string = null;
classNames: List<string> = [];
attrs: List<string> = [];
notSelectors: List<CssSelector> = [];
classNames: string[] = [];
attrs: string[] = [];
notSelectors: CssSelector[] = [];
static parse(selector: string): List<CssSelector> {
static parse(selector: string): CssSelector[] {
var results: CssSelector[] = [];
var _addResult = (res: CssSelector[], cssSel) => {
if (cssSel.notSelectors.length > 0 && isBlank(cssSel.element) &&
@ -135,21 +135,21 @@ export class CssSelector {
* are contained in a given CssSelector.
*/
export class SelectorMatcher {
static createNotMatcher(notSelectors: List<CssSelector>): SelectorMatcher {
static createNotMatcher(notSelectors: CssSelector[]): SelectorMatcher {
var notMatcher = new SelectorMatcher();
notMatcher.addSelectables(notSelectors, null);
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 _classMap: Map<string, List<SelectorContext>> = new Map();
private _classMap: Map<string, SelectorContext[]> = 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 _listContexts: List<SelectorListContext> = [];
private _listContexts: SelectorListContext[] = [];
addSelectables(cssSelectors: List<CssSelector>, callbackCtxt?: any) {
addSelectables(cssSelectors: CssSelector[], callbackCtxt?: any) {
var listContext = null;
if (cssSelectors.length > 1) {
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) {
var terminalList = map.get(name);
if (isBlank(terminalList)) {
@ -298,7 +298,7 @@ export class SelectorMatcher {
return result;
}
_matchTerminal(map: Map<string, List<SelectorContext>>, name, cssSelector: CssSelector,
_matchTerminal(map: Map<string, SelectorContext[]>, name, cssSelector: CssSelector,
matchedCallback: (CssSelector, any) => void): boolean {
if (isBlank(map) || isBlank(name)) {
return false;
@ -341,12 +341,12 @@ export class SelectorMatcher {
class SelectorListContext {
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
class SelectorContext {
notSelectors: List<CssSelector>;
notSelectors: CssSelector[];
constructor(public selector: CssSelector, public cbContext: any,
public listContext: SelectorListContext) {

View File

@ -26,7 +26,11 @@ export class TextInterpolationParser implements CompileStep {
var expr = this._parser.parseInterpolation(text, current.elementDescription);
if (isPresent(expr)) {
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);
}
}
}
}

View File

@ -65,23 +65,31 @@ export class ViewSplitter implements CompileStep {
}
}
if (hasTemplateBinding) {
var newParent = new CompileElement(DOM.createTemplate(''));
newParent.inheritedProtoView = current.inheritedProtoView;
newParent.inheritedElementBinder = current.inheritedElementBinder;
newParent.distanceToInheritedBinder = current.distanceToInheritedBinder;
var anchor = new CompileElement(DOM.createTemplate(''));
anchor.inheritedProtoView = current.inheritedProtoView;
anchor.inheritedElementBinder = current.inheritedElementBinder;
anchor.distanceToInheritedBinder = current.distanceToInheritedBinder;
// 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
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.distanceToInheritedBinder = 0;
current.isViewRoot = true;
this._parseTemplateBindings(templateBindings, newParent);
this._addParentElement(current.element, newParent.element);
control.addParent(newParent);
DOM.remove(current.element);
this._parseTemplateBindings(templateBindings, anchor);
DOM.insertBefore(current.element, anchor.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) {
var bindings =
this._parser.parseTemplateBindings(templateBindings, compileElement.elementDescription);

View File

@ -10,17 +10,26 @@ import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src
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 {DomProtoView, DomProtoViewRef, resolveInternalDomProtoView} from './view/proto_view';
import {DomView, DomViewRef, resolveInternalDomView} from './view/view';
import {DomElement} from './view/element';
import {DomViewContainer} from './view/view_container';
import {NG_BINDING_CLASS_SELECTOR, NG_BINDING_CLASS, camelCaseToDashCase} from './util';
import {DomFragmentRef, resolveInternalDomFragment} from './view/fragment';
import {
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 DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES =
@ -32,8 +41,7 @@ export class DomRenderer extends Renderer {
_document;
_reflectPropertiesAsAttributes: boolean;
constructor(public _eventManager: EventManager, public _shadowDomStrategy: ShadowDomStrategy,
@Inject(DOCUMENT_TOKEN) document,
constructor(public _eventManager: EventManager, @Inject(DOCUMENT_TOKEN) document,
@Inject(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES) reflectPropertiesAsAttributes:
boolean) {
super();
@ -41,109 +49,57 @@ export class DomRenderer extends Renderer {
this._document = document;
}
createRootHostView(hostProtoViewRef: RenderProtoViewRef,
hostElementSelector: string): RenderViewRef {
createRootHostView(hostProtoViewRef: RenderProtoViewRef, fragmentCount: number,
hostElementSelector: string): RenderViewWithFragments {
var hostProtoView = resolveInternalDomProtoView(hostProtoViewRef);
var element = DOM.querySelector(this._document, hostElementSelector);
if (isBlank(element)) {
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);
return new DomViewRef(this._createView(protoView, null));
return this._createView(protoView, null);
}
destroyView(view: RenderViewRef) {
destroyView(viewRef: RenderViewRef) {
// noop for now
}
getNativeElementSync(location: RenderElementRef): any {
if (isBlank(location.renderBoundElementIndex)) {
return null;
}
return resolveInternalDomView(location.renderView)
.boundElements[location.boundElementIndex]
.element;
.boundElements[location.renderBoundElementIndex];
}
attachComponentView(location: RenderElementRef, componentViewRef: RenderViewRef) {
var hostView = resolveInternalDomView(location.renderView);
var componentView = resolveInternalDomView(componentViewRef);
var element = hostView.boundElements[location.boundElementIndex].element;
var lightDom = hostView.boundElements[location.boundElementIndex].lightDom;
if (isPresent(lightDom)) {
lightDom.attachShadowDomView(componentView);
getRootNodes(fragment: RenderFragmentRef): List<Node> {
return resolveInternalDomFragment(fragment);
}
attachFragmentAfterFragment(previousFragmentRef: RenderFragmentRef,
fragmentRef: RenderFragmentRef) {
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);
this._moveViewNodesIntoParent(shadowRoot, componentView);
componentView.hostLightDom = lightDom;
componentView.shadowRoot = shadowRoot;
var parentView = resolveInternalDomView(elementRef.renderView);
var element = parentView.boundElements[elementRef.renderBoundElementIndex];
moveNodesAfterSibling(element, resolveInternalDomFragment(fragmentRef));
}
setComponentViewRootNodes(componentViewRef: RenderViewRef, rootNodes: List</*node*/ any>) {
var componentView = resolveInternalDomView(componentViewRef);
this._removeViewNodes(componentView);
componentView.rootNodes = rootNodes;
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();
detachFragment(fragmentRef: RenderFragmentRef) {
var fragmentNodes = resolveInternalDomFragment(fragmentRef);
for (var i = 0; i < fragmentNodes.length; i++) {
DOM.remove(fragmentNodes[i]);
}
}
@ -152,13 +108,6 @@ export class DomRenderer extends Renderer {
if (view.hydrated) throw new BaseException('The view is already hydrated.');
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
view.eventHandlerRemovers = [];
var binders = view.proto.elementBinders;
@ -173,9 +122,6 @@ export class DomRenderer extends Renderer {
}
}
}
if (isPresent(view.hostLightDom)) {
view.hostLightDom.redistribute();
}
}
dehydrateView(viewRef: RenderViewRef) {
@ -191,8 +137,11 @@ export class DomRenderer extends Renderer {
}
setElementProperty(location: RenderElementRef, propertyName: string, propertyValue: any): void {
if (isBlank(location.renderBoundElementIndex)) {
return;
}
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.
if (this._reflectPropertiesAsAttributes) {
this.setElementAttribute(location, `${REFLECT_PREFIX}${camelCaseToDashCase(propertyName)}`,
@ -202,26 +151,41 @@ export class DomRenderer extends Renderer {
setElementAttribute(location: RenderElementRef, attributeName: string, attributeValue: string):
void {
if (isBlank(location.renderBoundElementIndex)) {
return;
}
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 {
if (isBlank(location.renderBoundElementIndex)) {
return;
}
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 {
if (isBlank(location.renderBoundElementIndex)) {
return;
}
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 {
if (isBlank(location.renderBoundElementIndex)) {
return;
}
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 {
if (isBlank(textNodeIndex)) {
return;
}
var view = resolveInternalDomView(viewRef);
DOM.setText(view.boundTextNodes[textNodeIndex], text);
}
@ -231,99 +195,50 @@ export class DomRenderer extends Renderer {
view.eventDispatcher = dispatcher;
}
_createView(protoView: DomProtoView, inplaceElement): DomView {
var rootElementClone;
var elementsWithBindingsDynamic;
var viewRootNodes;
_createView(protoView: DomProtoView, inplaceElement: HTMLElement): RenderViewWithFragments {
var clonedProtoView = cloneAndQueryProtoView(protoView, true);
var boundElements = clonedProtoView.boundElements;
// adopt inplaceElement
if (isPresent(inplaceElement)) {
rootElementClone = inplaceElement;
elementsWithBindingsDynamic = [];
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;
if (protoView.fragmentsRootNodeCount[0] !== 1) {
throw new BaseException('Root proto views can only contain one element!');
}
} else {
rootElementClone = DOM.importIntoDoc(protoView.element);
elementsWithBindingsDynamic = DOM.getElementsByClassName(rootElementClone, NG_BINDING_CLASS);
viewRootNodes = [rootElementClone];
DOM.clearNodes(inplaceElement);
var tempRoot = clonedProtoView.fragments[0][0];
moveChildNodes(tempRoot, inplaceElement);
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 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++) {
var binder = binders[binderIdx];
var element = boundElements[binderIdx];
var domEl = element.element;
// lightDoms
var lightDom = null;
// Note: for the root element we can't use the binder.elementIsEmpty
// information as we don't use the element from the ProtoView
// but an element from the document.
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);
// native shadow DOM
if (binder.hasNativeShadowRoot) {
var shadowRootWrapper = DOM.firstChild(element);
moveChildNodes(shadowRootWrapper, DOM.createShadowRoot(element));
DOM.remove(shadowRootWrapper);
}
// events
if (isPresent(binder.eventLocals) && isPresent(binder.localEvents)) {
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);
}
}
}
return view;
return new RenderViewWithFragments(
new DomViewRef(view), clonedProtoView.fragments.map(nodes => new DomFragmentRef(nodes)));
}
_createEventListener(view, element, elementIndex, eventName, eventLocals) {
@ -331,45 +246,26 @@ export class DomRenderer extends Renderer {
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 {
return this._eventManager.addGlobalEventListener(
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;
}
}

View File

@ -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); }
}

View File

@ -1,8 +1,5 @@
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 {insertSharedStyleText} from './util';
@ -20,12 +17,6 @@ export class EmulatedUnscopedShadowDomStrategy extends ShadowDomStrategy {
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 {
var cssText = DOM.getText(styleEl);
insertSharedStyleText(cssText, this.styleHost, styleEl);

View File

@ -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]);
}
}
}

View File

@ -1,5 +1,4 @@
import {Injectable} from 'angular2/di';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {ShadowDomStrategy} from './shadow_dom_strategy';
/**
@ -10,5 +9,5 @@ import {ShadowDomStrategy} from './shadow_dom_strategy';
*/
@Injectable()
export class NativeShadowDomStrategy extends ShadowDomStrategy {
prepareShadowRoot(el): Node { return DOM.createShadowRoot(el); }
hasNativeContentElement(): boolean { return true; }
}

View File

@ -1,5 +1,3 @@
import {isBlank, isPresent, assertionsEnabled, isPromise} from 'angular2/src/facade/lang';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {CompileStep} from '../compiler/compile_step';
@ -15,8 +13,6 @@ export class ShadowDomCompileStep implements CompileStep {
var tagName = DOM.tagName(current.element).toUpperCase();
if (tagName == 'STYLE') {
this._processStyleElement(current, control);
} else if (tagName == 'CONTENT') {
this._processContentElement(current);
} else {
var componentId = current.isBound() ? current.inheritedElementBinder.componentId : null;
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.
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);
}
}

View File

@ -1,17 +1,9 @@
import {isBlank, isPresent} from 'angular2/src/facade/lang';
import * as viewModule from '../view/view';
import {LightDom} from './light_dom';
export class ShadowDomStrategy {
// Whether the strategy understands the native <content> tag
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.
processStyleElement(hostComponentId: string, templateUrl: string, styleElement): void {}

View File

@ -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 = 'ng-binding';
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 DASH_CASE_REGEXP = /-([a-z])/g;
export function camelCaseToDashCase(input: string): string {
return StringWrapper.replaceAllMapped(input, CAMEL_CASE_REGEXP,
(m) => { return '-' + m[1].toLowerCase(); });
@ -17,3 +25,101 @@ export function dashCaseToCamelCase(input: string): string {
return StringWrapper.replaceAllMapped(input, DASH_CASE_REGEXP,
(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));
}
}
}

View File

@ -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) {}
}

View File

@ -1,42 +1,29 @@
import {AST} from 'angular2/change_detection';
import {List, ListWrapper} from 'angular2/src/facade/collection';
import * as protoViewModule from './proto_view';
export class ElementBinder {
contentTagSelector: string;
export class DomElementBinder {
textNodeIndices: List<number>;
nestedProtoView: protoViewModule.DomProtoView;
hasNestedProtoView: boolean;
eventLocals: AST;
localEvents: List<Event>;
globalEvents: List<Event>;
componentId: string;
parentIndex: number;
distanceToParent: number;
elementIsEmpty: boolean;
hasNativeShadowRoot: boolean;
constructor({textNodeIndices, contentTagSelector, nestedProtoView, componentId, eventLocals,
localEvents, globalEvents, parentIndex, distanceToParent, elementIsEmpty}: {
contentTagSelector?: string,
constructor({textNodeIndices, hasNestedProtoView, eventLocals, localEvents, globalEvents,
hasNativeShadowRoot}: {
textNodeIndices?: List<number>,
nestedProtoView?: protoViewModule.DomProtoView,
hasNestedProtoView?: boolean,
eventLocals?: AST,
localEvents?: List<Event>,
globalEvents?: List<Event>,
componentId?: string,
parentIndex?: number,
distanceToParent?: number,
elementIsEmpty?: boolean
hasNativeShadowRoot?: boolean
} = {}) {
this.textNodeIndices = textNodeIndices;
this.contentTagSelector = contentTagSelector;
this.nestedProtoView = nestedProtoView;
this.componentId = componentId;
this.hasNestedProtoView = hasNestedProtoView;
this.eventLocals = eventLocals;
this.localEvents = localEvents;
this.globalEvents = globalEvents;
this.parentIndex = parentIndex;
this.distanceToParent = distanceToParent;
this.elementIsEmpty = elementIsEmpty;
this.hasNativeShadowRoot = hasNativeShadowRoot;
}
}

View 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(); }
}

View File

@ -1,12 +1,10 @@
import {isPresent} from 'angular2/src/facade/lang';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {isBlank} from 'angular2/src/facade/lang';
import {List, ListWrapper} from 'angular2/src/facade/collection';
import {ElementBinder} from './element_binder';
import {NG_BINDING_CLASS} from '../util';
import {DomElementBinder} from './element_binder';
import {RenderProtoViewRef, ViewType} from '../../api';
import {RenderProtoViewRef} from '../../api';
import {DOM} from 'angular2/src/dom/dom_adapter';
export function resolveInternalDomProtoView(protoViewRef: RenderProtoViewRef): DomProtoView {
return (<DomProtoViewRef>protoViewRef)._protoView;
@ -17,24 +15,40 @@ export class DomProtoViewRef extends RenderProtoViewRef {
}
export class DomProtoView {
element;
elementBinders: List<ElementBinder>;
isTemplateElement: boolean;
rootBindingOffset: number;
// the number of content tags seen in this or any child proto view.
transitiveContentTagCount: number;
boundTextNodeCount: number;
rootNodeCount: number;
constructor({elementBinders, element, transitiveContentTagCount, boundTextNodeCount}) {
this.element = element;
this.elementBinders = elementBinders;
this.transitiveContentTagCount = transitiveContentTagCount;
this.isTemplateElement = DOM.isTemplateElement(this.element);
this.rootBindingOffset =
(isPresent(this.element) && DOM.hasClass(this.element, NG_BINDING_CLASS)) ? 1 : 0;
this.boundTextNodeCount = boundTextNodeCount;
this.rootNodeCount =
this.isTemplateElement ? DOM.childNodes(DOM.content(this.element)).length : 1;
static create(type: ViewType, rootElement: Element, fragmentsRootNodeCount: number[],
rootTextNodeIndices: number[], elementBinders: List<DomElementBinder>,
mappedElementIndices: number[], mappedTextIndices: number[],
hostElementIndicesByViewIndex: number[]): DomProtoView {
var boundTextNodeCount = rootTextNodeIndices.length;
for (var i = 0; i < elementBinders.length; i++) {
boundTextNodeCount += elementBinders[i].textNodeIndices.length;
}
if (isBlank(mappedElementIndices)) {
mappedElementIndices = ListWrapper.createFixedSize(elementBinders.length);
for (var i = 0; i < mappedElementIndices.length; i++) {
mappedElementIndices[i] = i;
}
}
if (isBlank(mappedTextIndices)) {
mappedTextIndices = ListWrapper.createFixedSize(boundTextNodeCount);
for (var i = 0; i < mappedTextIndices.length; i++) {
mappedTextIndices[i] = i;
}
}
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[]) {}
}

View File

@ -19,17 +19,19 @@ import {
} from 'angular2/change_detection';
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 {NG_BINDING_CLASS, EVENT_TARGET_SEPARATOR} from '../util';
import {NG_BINDING_CLASS, EVENT_TARGET_SEPARATOR, queryBoundTextNodeIndices} from '../util';
export class ProtoViewBuilder {
variableBindings: Map<string, string> = new Map();
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 {
var builder = new ElementBinderBuilder(this.elements.length, element, description);
@ -49,12 +51,22 @@ export class ProtoViewBuilder {
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 {
var renderElementBinders = [];
var domElementBinders = [];
var apiElementBinders = [];
var transitiveContentTagCount = 0;
var boundTextNodeCount = 0;
var textNodeExpressions = [];
var rootTextNodeIndices = [];
queryBoundTextNodeIndices(DOM.content(this.rootElement), this.rootTextBindings,
(node, nodeIndex, expression) => {
textNodeExpressions.push(expression);
rootTextNodeIndices.push(nodeIndex);
});
ListWrapper.forEach(this.elements, (ebb: ElementBinderBuilder) => {
var directiveTemplatePropertyNames = new Set();
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 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 textNodeIndices = [];
queryBoundTextNodeIndices(ebb.element, ebb.textBindings, (node, nodeIndex, expression) => {
textNodeExpressions.push(expression);
textNodeIndices.push(nodeIndex);
});
apiElementBinders.push(new api.ElementBinder({
index: ebb.index,
parentIndex: parentIndex,
@ -91,64 +100,28 @@ export class ProtoViewBuilder {
ebb.propertyBindings, directiveTemplatePropertyNames),
variableBindings: ebb.variableBindings,
eventBindings: ebb.eventBindings,
textBindings: ebb.textBindings,
readAttributes: ebb.readAttributes
}));
var childNodeInfo = this._analyzeChildNodes(ebb.element, ebb.textBindingNodes);
boundTextNodeCount += ebb.textBindingNodes.length;
renderElementBinders.push(new ElementBinder({
textNodeIndices: childNodeInfo.boundTextNodeIndices,
contentTagSelector: ebb.contentTagSelector,
parentIndex: parentIndex,
distanceToParent: ebb.distanceToParent,
nestedProtoView:
isPresent(nestedProtoView) ? resolveInternalDomProtoView(nestedProtoView.render) : null,
componentId: ebb.componentId,
domElementBinders.push(new DomElementBinder({
textNodeIndices: textNodeIndices,
hasNestedProtoView: isPresent(nestedProtoView) || isPresent(ebb.componentId),
hasNativeShadowRoot: isPresent(ebb.componentId) && this.useNativeShadowDom,
eventLocals: new LiteralArray(ebb.eventBuilder.buildEventLocals()),
localEvents: ebb.eventBuilder.buildLocalEvents(),
globalEvents: ebb.eventBuilder.buildGlobalEvents(),
elementIsEmpty: childNodeInfo.elementIsEmpty
globalEvents: ebb.eventBuilder.buildGlobalEvents()
}));
});
var rootNodeCount = DOM.childNodes(DOM.content(this.rootElement)).length;
return new api.ProtoViewDto({
render: new DomProtoViewRef(new DomProtoView({
element: this.rootElement,
elementBinders: renderElementBinders,
transitiveContentTagCount: transitiveContentTagCount,
boundTextNodeCount: boundTextNodeCount
})),
render: new DomProtoViewRef(DomProtoView.create(this.type, this.rootElement, [rootNodeCount],
rootTextNodeIndices, domElementBinders, null,
null, null)),
type: this.type,
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 {
@ -161,9 +134,7 @@ export class ElementBinderBuilder {
propertyBindingsToDirectives: Set<string> = new Set();
eventBindings: List<api.EventBinding> = [];
eventBuilder: EventBuilder = new EventBuilder();
textBindingNodes: List</*node*/ any> = [];
textBindings: List<ASTWithSource> = [];
contentTagSelector: string = null;
textBindings: Map<Node, ASTWithSource> = new Map();
readAttributes: Map<string, string> = new Map();
componentId: string = null;
@ -229,12 +200,9 @@ export class ElementBinderBuilder {
this.eventBindings.push(this.eventBuilder.add(name, expression, target));
}
bindText(textNode, expression) {
this.textBindingNodes.push(textNode);
this.textBindings.push(expression);
}
setContentTagSelector(value: string) { this.contentTagSelector = value; }
// Note: We don't store the node index until the compilation is complete,
// as the compiler might change the order of elements.
bindText(textNode, expression) { this.textBindings.set(textNode, expression); }
setComponentId(componentId: string) { this.componentId = componentId; }
}

View 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;
}

View File

@ -3,10 +3,8 @@ import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src
import {isPresent, isBlank, BaseException, stringify} from 'angular2/src/facade/lang';
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';
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
*/
export class DomView {
hostLightDom: LightDom = null;
shadowRoot = null;
hydrated: boolean = false;
eventDispatcher: EventDispatcher = null;
eventDispatcher: RenderEventDispatcher = null;
eventHandlerRemovers: List<Function> = [];
constructor(public proto: DomProtoView, public rootNodes: List</*node*/ any>,
public boundTextNodes: List</*node*/ any>, public boundElements: List<DomElement>) {}
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;
}
constructor(public proto: DomProtoView, public boundTextNodes: List<Node>,
public boundElements: Element[]) {}
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) {
var element = this.boundElements[elementIndex].element;
var element = this.boundElements[elementIndex];
var dashCasedAttributeName = camelCaseToDashCase(attributeName);
if (isPresent(value)) {
DOM.setAttribute(element, dashCasedAttributeName, stringify(value));
@ -54,7 +41,7 @@ export class DomView {
}
setElementClass(elementIndex: number, className: string, isAdd: boolean) {
var element = this.boundElements[elementIndex].element;
var element = this.boundElements[elementIndex];
var dashCasedClassName = camelCaseToDashCase(className);
if (isAdd) {
DOM.addClass(element, dashCasedClassName);
@ -64,7 +51,7 @@ export class DomView {
}
setElementStyle(elementIndex: number, styleName: string, value: string) {
var element = this.boundElements[elementIndex].element;
var element = this.boundElements[elementIndex];
var dashCasedStyleName = camelCaseToDashCase(styleName);
if (isPresent(value)) {
DOM.setStyle(element, dashCasedStyleName, stringify(value));
@ -74,7 +61,7 @@ export class DomView {
}
invokeElementMethod(elementIndex: number, methodName: string, args: List<any>) {
var element = this.boundElements[elementIndex].element;
var element = this.boundElements[elementIndex];
DOM.invoke(element, methodName, args);
}
@ -91,7 +78,7 @@ export class DomView {
// Locals(null, evalLocals));
// this.eventDispatcher.dispatchEvent(elementIndex, eventName, localValues);
allowDefaultBehavior =
this.eventDispatcher.dispatchEvent(elementIndex, eventName, evalLocals);
this.eventDispatcher.dispatchRenderEvent(elementIndex, eventName, evalLocals);
if (!allowDefaultBehavior) {
event.preventDefault();
}

View File

@ -5,14 +5,4 @@ import * as viewModule from './view';
export class DomViewContainer {
// The order in this list matches the DOM order.
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;
}
}

View File

@ -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
* function. */
andCallFake(fn: Function): GuinessCompatibleSpy;
/** removes all recorded calls */
reset();
}
export class SpyObject {
@ -320,6 +322,7 @@ export class SpyObject {
var newSpy: GuinessCompatibleSpy = <any>jasmine.createSpy(name);
newSpy.andCallFake = <any>newSpy.and.callFake;
newSpy.andReturn = <any>newSpy.and.returnValue;
newSpy.reset = <any>newSpy.calls.reset;
// return null by default to satisfy our rtts asserts
newSpy.and.returnValue(null);
return newSpy;

View File

@ -1,7 +1,6 @@
import {List, ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {isPresent, isString, RegExpWrapper, StringWrapper, RegExp} from 'angular2/src/facade/lang';
import {resolveInternalDomView} from 'angular2/src/render/dom/view/view';
export class Log {
_result: List<any>;
@ -17,21 +16,6 @@ export class Log {
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) {
DOM.dispatchEvent(element, DOM.createEvent(eventType));
}

View File

@ -185,7 +185,6 @@ class ElementBinderSerializer {
'propertyBindings': Serializer.serialize(binder.propertyBindings, ElementPropertyBinding),
'variableBindings': Serializer.mapToObject(binder.variableBindings),
'eventBindings': Serializer.serialize(binder.eventBindings, EventBinding),
'textBindings': Serializer.serialize(binder.textBindings, ASTWithSource),
'readAttributes': Serializer.mapToObject(binder.readAttributes)
};
}
@ -200,7 +199,6 @@ class ElementBinderSerializer {
propertyBindings: Serializer.deserialize(obj.propertyBindings, ElementPropertyBinding),
variableBindings: Serializer.objectToMap(obj.variableBindings),
eventBindings: Serializer.deserialize(obj.eventBindings, EventBinding),
textBindings: Serializer.deserialize(obj.textBindings, ASTWithSource, "interpolation"),
readAttributes: Serializer.objectToMap(obj.readAttributes)
});
}
@ -213,6 +211,7 @@ class ProtoViewDtoSerializer {
'render': null,
'elementBinders': Serializer.serialize(view.elementBinders, ElementBinder),
'variableBindings': Serializer.mapToObject(view.variableBindings),
'textBindings': Serializer.serialize(view.textBindings, ASTWithSource),
'type': view.type
};
}
@ -222,6 +221,7 @@ class ProtoViewDtoSerializer {
render: null, // TODO: fix render refs and write a serializer for them
elementBinders: Serializer.deserialize(obj.elementBinders, ElementBinder),
variableBindings: Serializer.objectToMap(obj.variableBindings),
textBindings: Serializer.deserialize(obj.textBindings, ASTWithSource, "interpolation"),
type: obj.type
});
}

View File

@ -48,7 +48,7 @@ import "package:angular2/di.dart" show Injectable;
@Template(
inline: '<div class="greeting">{{greeting}} <span red>world</span>!</div>'
'<button class="changeButton" (click)="changeGreeting()">'
'change greeting</button><content></content>',
'change greeting</button><ng-content></ng-content>',
directives: const [RedDec])
class HelloCmp {
String greeting;

View File

@ -29,7 +29,7 @@ class HelloRootCmp {
}
@Component({selector: 'hello-app'})
@View({template: 'before: <content></content> after: done'})
@View({template: 'before: <ng-content></ng-content> after: done'})
class HelloRootCmpContent {
constructor() {}
}
@ -68,19 +68,19 @@ class HelloRootDirectiveIsNotCmp {
export function main() {
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', () => {
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',
inject([AsyncTestCompleter], (async) => {
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',
inject([AsyncTestCompleter], (async) => {
var refPromise1 = bootstrap(HelloRootCmp, testBindings);

View File

@ -15,7 +15,7 @@ import {
} from 'angular2/test_lib';
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 {Compiler, CompilerCache} from 'angular2/src/core/compiler/compiler';
@ -45,24 +45,41 @@ export function main() {
rootProtoView;
var renderCompileRequests: any[];
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));
});
rootProtoView = createRootProtoView(directiveResolver, MainComponent);
});
function mergeProtoViewsRecursively(
protoViewRefs: List<renderApi.RenderProtoViewRef | List<any>>,
target: renderApi.RenderProtoViewMergeMapping[]): renderApi.RenderProtoViewRef {
var targetIndex = target.length;
target.push(null);
var flattended = protoViewRefs.map(protoViewRefOrArray => {
var resolvedProtoViewRef;
if (isArray(protoViewRefOrArray)) {
resolvedProtoViewRef = mergeProtoViewsRecursively(
<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:
List<renderApi.ProtoViewDto | Promise<renderApi.ProtoViewDto>>,
protoViewFactoryResults: List<List<AppProtoView>>) {
protoViewFactoryResults: List<AppProtoView>) {
var urlResolver = new UrlResolver();
renderCompileRequests = [];
renderCompileResults = ListWrapper.clone(renderCompileResults);
renderCompiler.spy('compile').andCallFake((view) => {
renderCompileRequests.push(view);
return PromiseWrapper.resolve(ListWrapper.removeAt(renderCompileResults, 0));
@ -73,12 +90,31 @@ export function main() {
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', () => {
function captureTemplate(template: viewAnn.View): Promise<renderApi.ViewDefinition> {
tplResolver.setView(MainComponent, template);
var compiler =
createCompiler([createRenderProtoView()], [[rootProtoView], [createProtoView()]]);
createCompiler([createRenderProtoView()], [rootProtoView, createProtoView()]);
return compiler.compileInHost(MainComponent)
.then((_) => {
expect(renderCompileRequests.length).toBe(1);
@ -260,7 +296,7 @@ export function main() {
tplResolver.setView(MainComponent, new viewAnn.View({template: '<div></div>'}));
var renderProtoView = createRenderProtoView();
var expectedProtoView = createProtoView();
var compiler = createCompiler([renderProtoView], [[rootProtoView], [expectedProtoView]]);
var compiler = createCompiler([renderProtoView], [rootProtoView, expectedProtoView]);
compiler.compileInHost(MainComponent)
.then((_) => {
var request = protoViewFactory.requests[1];
@ -272,7 +308,7 @@ export function main() {
it('should pass the component binding', inject([AsyncTestCompleter], (async) => {
tplResolver.setView(MainComponent, new viewAnn.View({template: '<div></div>'}));
var compiler =
createCompiler([createRenderProtoView()], [[rootProtoView], [createProtoView()]]);
createCompiler([createRenderProtoView()], [rootProtoView, createProtoView()]);
compiler.compileInHost(MainComponent)
.then((_) => {
var request = protoViewFactory.requests[1];
@ -286,7 +322,7 @@ export function main() {
MainComponent,
new viewAnn.View({template: '<div></div>', directives: [SomeDirective]}));
var compiler =
createCompiler([createRenderProtoView()], [[rootProtoView], [createProtoView()]]);
createCompiler([createRenderProtoView()], [rootProtoView, createProtoView()]);
compiler.compileInHost(MainComponent)
.then((_) => {
var request = protoViewFactory.requests[1];
@ -300,7 +336,7 @@ export function main() {
inject([AsyncTestCompleter], (async) => {
tplResolver.setView(MainComponent, new viewAnn.View({template: '<div></div>'}));
var compiler =
createCompiler([createRenderProtoView()], [[rootProtoView], [createProtoView()]]);
createCompiler([createRenderProtoView()], [rootProtoView, createProtoView()]);
compiler.compileInHost(MainComponent)
.then((protoViewRef) => {
expect(internalProtoView(protoViewRef)).toBe(rootProtoView);
@ -316,17 +352,21 @@ export function main() {
var mainProtoView =
createProtoView([createComponentElementBinder(directiveResolver, NestedComponent)]);
var nestedProtoView = createProtoView();
var compiler = createCompiler(
[
createRenderProtoView([createRenderComponentElementBinder(0)]),
createRenderProtoView()
],
[[rootProtoView], [mainProtoView], [nestedProtoView]]);
var renderPvDtos = [
createRenderProtoView([createRenderComponentElementBinder(0)]),
createRenderProtoView()
];
var compiler =
createCompiler(renderPvDtos, [rootProtoView, mainProtoView, nestedProtoView]);
compiler.compileInHost(MainComponent)
.then((protoViewRef) => {
expect(internalProtoView(protoViewRef).elementBinders[0].nestedProtoView)
.toBe(mainProtoView);
expect(originalRenderProtoViewRefs(mainProtoView))
.toEqual([renderPvDtos[0].render, renderPvDtos[1].render]);
expect(mainProtoView.elementBinders[0].nestedProtoView).toBe(nestedProtoView);
expect(originalRenderProtoViewRefs(nestedProtoView))
.toEqual([renderPvDtos[1].render]);
async.done();
});
}));
@ -334,25 +374,40 @@ export function main() {
it('should load nested components in viewcontainers', inject([AsyncTestCompleter], (async) => {
tplResolver.setView(MainComponent, new viewAnn.View({template: '<div></div>'}));
tplResolver.setView(NestedComponent, new viewAnn.View({template: '<div></div>'}));
var mainProtoView = createProtoView([createViewportElementBinder(null)]);
var viewportProtoView =
createProtoView([createComponentElementBinder(directiveResolver, NestedComponent)]);
var mainProtoView = createProtoView([createViewportElementBinder(viewportProtoView)]);
var nestedProtoView = createProtoView();
var compiler = createCompiler(
[
createRenderProtoView([
createRenderViewportElementBinder(createRenderProtoView(
[createRenderComponentElementBinder(0)], renderApi.ViewType.EMBEDDED))
]),
createRenderProtoView()
],
[[rootProtoView], [mainProtoView, viewportProtoView], [nestedProtoView]]);
var renderPvDtos = [
createRenderProtoView([
createRenderViewportElementBinder(createRenderProtoView(
[createRenderComponentElementBinder(0)], renderApi.ViewType.EMBEDDED))
]),
createRenderProtoView()
];
var compiler =
createCompiler(renderPvDtos, [rootProtoView, mainProtoView, nestedProtoView]);
compiler.compileInHost(MainComponent)
.then((protoViewRef) => {
expect(internalProtoView(protoViewRef).elementBinders[0].nestedProtoView)
.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(originalRenderProtoViewRefs(viewportProtoView))
.toEqual([
renderPvDtos[0]
.elementBinders[0]
.nestedProtoView.render,
renderPvDtos[1].render
]);
expect(originalRenderProtoViewRefs(nestedProtoView))
.toEqual([renderPvDtos[1].render]);
async.done();
});
}));
@ -360,7 +415,7 @@ export function main() {
it('should cache compiled host components', inject([AsyncTestCompleter], (async) => {
tplResolver.setView(MainComponent, new viewAnn.View({template: '<div></div>'}));
var mainPv = createProtoView();
var compiler = createCompiler([createRenderProtoView()], [[rootProtoView], [mainPv]]);
var compiler = createCompiler([createRenderProtoView()], [rootProtoView, mainPv]);
compiler.compileInHost(MainComponent)
.then((protoViewRef) => {
expect(internalProtoView(protoViewRef).elementBinders[0].nestedProtoView)
@ -404,7 +459,7 @@ export function main() {
var nestedPv = createProtoView([]);
var compiler = createCompiler(
[createRenderProtoView(), createRenderProtoView(), createRenderProtoView()],
[[rootProtoView], [mainPv], [nestedPv], [rootProtoView2], [mainPv]]);
[rootProtoView, mainPv, nestedPv, rootProtoView2, mainPv]);
compiler.compileInHost(MainComponent)
.then((protoViewRef) => {
expect(internalProtoView(protoViewRef)
@ -429,7 +484,7 @@ export function main() {
var renderProtoViewCompleter = PromiseWrapper.completer();
var expectedProtoView = createProtoView();
var compiler = createCompiler([renderProtoViewCompleter.promise],
[[rootProtoView], [rootProtoView], [expectedProtoView]]);
[rootProtoView, rootProtoView, expectedProtoView]);
var result = PromiseWrapper.all([
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>'}));
var mainProtoView =
createProtoView([createComponentElementBinder(directiveResolver, MainComponent)]);
var compiler =
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)
.then((protoViewRef) => {
expect(internalProtoView(protoViewRef).elementBinders[0].nestedProtoView)
.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();
});
}));
@ -466,8 +558,7 @@ export function main() {
var rootProtoView =
createProtoView([createComponentElementBinder(directiveResolver, MainComponent)]);
var mainProtoView = createProtoView();
var compiler =
createCompiler([createRenderProtoView()], [[rootProtoView], [mainProtoView]]);
var compiler = createCompiler([createRenderProtoView()], [rootProtoView, mainProtoView]);
compiler.compileInHost(MainComponent)
.then((protoViewRef) => {
expect(internalProtoView(protoViewRef)).toBe(rootProtoView);
@ -491,7 +582,7 @@ function createDirectiveBinding(directiveResolver, type): DirectiveBinding {
}
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)) {
elementBinders = [];
}
@ -518,7 +609,8 @@ function createRenderProtoView(elementBinders = null, type: renderApi.ViewType =
if (isBlank(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 {
@ -611,14 +703,22 @@ class FakeViewResolver extends ViewResolver {
class FakeProtoViewFactory extends ProtoViewFactory {
requests: List<List<any>>;
constructor(public results: List<List<AppProtoView>>) {
constructor(public results: List<AppProtoView>) {
super(null);
this.requests = [];
}
createAppProtoViews(componentBinding: DirectiveBinding, renderProtoView: renderApi.ProtoViewDto,
directives: List<DirectiveBinding>): List<AppProtoView> {
directives: List<DirectiveBinding>): AppProtoView {
this.requests.push([componentBinding, renderProtoView, directives]);
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;
}

View File

@ -12,7 +12,6 @@ import {
beforeEachBindings,
it,
xit,
viewRootNodes,
TestComponentBuilder,
RootTestComponent,
inspectElement,
@ -240,8 +239,7 @@ export function main() {
componentRef.dispose();
expect(rootEl).toHaveText('');
expect(rootEl.parentNode).toBe(doc.body);
expect(rootEl.parentNode).toBeFalsy();
async.done();
});

View File

@ -51,17 +51,10 @@ import {QueryList} from 'angular2/src/core/compiler/query_list';
@proxy
@IMPLEMENTS(AppView)
class DummyView extends SpyObject {
componentChildViews;
changeDetector;
elementRefs;
constructor(elementCount = 0) {
constructor() {
super(AppView);
this.componentChildViews = [];
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); }
}
@ -69,6 +62,7 @@ class DummyView extends SpyObject {
@proxy
@IMPLEMENTS(ElementRef)
class DummyElementRef extends SpyObject {
boundElementIndex: number = 0;
constructor() { super(ElementRef); }
noSuchMethod(m) { return super.noSuchMethod(m); }
}
@ -246,14 +240,14 @@ class TestNode extends TreeNode<TestNode> {
}
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
var dynamicBindings = [];
for (var i = 0; i < 20; i++) {
dynamicBindings.push(bind(i).toValue(i));
}
}
function createPei(parent, index, bindings, distance = 1, hasShadowRoot = false, dirVariableBindings = null) {
var directiveBinding = ListWrapper.map(bindings, b => {
@ -754,9 +748,9 @@ export function main() {
});
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 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));
});
@ -947,7 +941,7 @@ export function main() {
describe("refs", () => {
it("should inject ElementRef", () => {
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", () => {
@ -955,10 +949,10 @@ export function main() {
var view = <any>new DummyView();
var childView = new DummyView();
childView.changeDetector = cd;
view.componentChildViews = [childView];
view.spy('getNestedView').andReturn(childView);
var binding = DirectiveBinding.createFromType(ComponentNeedsChangeDetectorRef, new dirAnn.Component());
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);
});
@ -969,7 +963,7 @@ export function main() {
view.changeDetector =cd;
var binding = DirectiveBinding.createFromType(DirectiveNeedsChangeDetectorRef, new dirAnn.Directive());
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);
});
@ -980,9 +974,9 @@ export function main() {
});
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,
new PreBuiltObjects(null, null, protoView));
new PreBuiltObjects(null, null, null, 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,
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' +
@ -1198,7 +1192,7 @@ export function main() {
});
});
});
});
});
}
class ContextWithHandler {

View File

@ -15,7 +15,8 @@ import {
xit,
containsRegexp,
stringifyElement,
TestComponentBuilder
TestComponentBuilder,
fakeAsync
} from 'angular2/test_lib';
@ -1369,8 +1370,8 @@ class SimpleImperativeViewComponent {
done;
constructor(self: ElementRef, viewManager: AppViewManager, renderer: DomRenderer) {
var shadowViewRef = viewManager.getComponentView(self);
renderer.setComponentViewRootNodes(shadowViewRef.render, [el('hello imp view')]);
var hostElement = renderer.getNativeElementSync(self);
DOM.appendChild(hostElement, el('hello imp view'));
}
}
@ -1870,7 +1871,7 @@ class SomeImperativeViewport {
}
if (value) {
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++) {
DOM.appendChild(this.anchor, nodes[i]);
}

View File

@ -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;
}

View File

@ -35,7 +35,7 @@ export function main() {
describe('ProtoViewFactory', () => {
var changeDetection;
var protoViewFactory;
var protoViewFactory: ProtoViewFactory;
var directiveResolver;
beforeEach(() => {
@ -63,10 +63,13 @@ export function main() {
describe('createAppProtoViews', () => {
it('should create an AppProtoView for the root render proto view', () => {
var renderPv = createRenderProtoView();
var pvs = protoViewFactory.createAppProtoViews(bindDirective(MainComponent), renderPv, []);
expect(pvs.length).toBe(1);
expect(pvs[0].render).toBe(renderPv.render);
var varBindings = new Map();
varBindings.set('a', 'b');
var renderPv = createRenderProtoView([], null, varBindings);
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);
}
function createRenderProtoView(elementBinders = null, type: renderApi.ViewType = null) {
function createRenderProtoView(elementBinders = null, type: renderApi.ViewType = null,
variableBindings = null) {
if (isBlank(type)) {
type = renderApi.ViewType.COMPONENT;
}
if (isBlank(elementBinders)) {
elementBinders = [];
}
return new renderApi.ProtoViewDto(
{elementBinders: elementBinders, type: type, variableBindings: new Map()});
if (isBlank(variableBindings)) {
variableBindings = new Map();
}
return new renderApi.ProtoViewDto({
elementBinders: elementBinders,
type: type,
variableBindings: variableBindings,
textBindings: []
});
}
function createRenderComponentElementBinder(directiveIndex) {

View File

@ -398,7 +398,7 @@ class NeedsQueryDesc {
}
@Component({selector: 'needs-query-by-var-binding'})
@View({directives: [], template: '<content>'})
@View({directives: [], template: '<ng-content>'})
@Injectable()
class NeedsQueryByLabel {
query: QueryList<any>;
@ -408,7 +408,7 @@ class NeedsQueryByLabel {
}
@Component({selector: 'needs-query-by-var-bindings'})
@View({directives: [], template: '<content>'})
@View({directives: [], template: '<ng-content>'})
@Injectable()
class NeedsQueryByTwoLabels {
query: QueryList<any>;

View File

@ -18,10 +18,11 @@ import {
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 {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() {
// TODO(tbosch): add missing tests
@ -31,34 +32,26 @@ export function main() {
var view;
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); }
beforeEach(() => {
viewManager = new AppViewManagerSpy();
view = createView();
view.viewContainers = [null];
location = view.elementRefs[0];
view = new AppViewSpy();
location = new ElementRef(new ViewRef(view), 0, 0, null);
});
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();
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();
view.viewContainers = [new AppViewContainer()];
view.viewContainers[0].views = [createView()];
var appVc = new AppViewContainer();
view.viewContainers = [appVc];
appVc.views = [<any>new AppViewSpy()];
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
@IMPLEMENTS(AppViewManager)
class AppViewManagerSpy extends SpyObject {

View File

@ -16,240 +16,155 @@ import {
proxy
} from 'angular2/test_lib';
import {Injector, bind} from 'angular2/di';
import {IMPLEMENTS, isBlank, isPresent} from 'angular2/src/facade/lang';
import {MapWrapper, ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {IMPLEMENTS} from 'angular2/src/facade/lang';
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 {Renderer, RenderViewRef, RenderProtoViewRef} from 'angular2/src/render/api';
import {ElementBinder} from 'angular2/src/core/compiler/element_binder';
import {DirectiveBinding, ElementInjector} from 'angular2/src/core/compiler/element_injector';
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
import {Component} from 'angular2/annotations';
import {ElementRef} from 'angular2/src/core/compiler/element_ref';
import {
Renderer,
RenderViewRef,
RenderProtoViewRef,
RenderFragmentRef,
ViewType,
RenderProtoViewMergeMapping,
RenderViewWithFragments
} from 'angular2/src/render/api';
import {AppViewManager} from 'angular2/src/core/compiler/view_manager';
import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils';
import {AppViewListener} from 'angular2/src/core/compiler/view_listener';
import {AppViewPool} from 'angular2/src/core/compiler/view_pool';
import {
createHostPv,
createComponentPv,
createEmbeddedPv,
createEmptyElBinder,
createNestedElBinder,
createProtoElInjector
} from './view_manager_utils_spec';
export function main() {
// TODO(tbosch): add missing tests
describe('AppViewManager', () => {
var renderer;
var utils;
var utils: AppViewManagerUtils;
var viewListener;
var viewPool;
var manager;
var directiveResolver;
var createdViews: any[];
var createdRenderViews: any[];
var manager: AppViewManager;
var createdRenderViews: RenderViewWithFragments[];
function wrapPv(protoView: AppProtoView): ProtoViewRef { return new ProtoViewRef(protoView); }
function wrapView(view: AppView): ViewRef { return new ViewRef(view); }
function elementRef(parentView, boundElementIndex) {
return parentView.elementRefs[boundElementIndex];
}
function createDirectiveBinding(type) {
var annotation = directiveResolver.resolve(type);
return DirectiveBinding.createFromType(type, annotation);
}
function createEmptyElBinder() { return new ElementBinder(0, null, 0, null, null); }
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;
function resetSpies() {
viewListener.spy('viewCreated').reset();
viewListener.spy('viewDestroyed').reset();
renderer.spy('createView').reset();
renderer.spy('destroyView').reset();
renderer.spy('createRootHostView').reset();
renderer.spy('setEventDispatcher').reset();
renderer.spy('hydrateView').reset();
renderer.spy('dehydrateView').reset();
viewPool.spy('returnView').reset();
}
beforeEach(() => {
directiveResolver = new DirectiveResolver();
renderer = new SpyRenderer();
utils = new SpyAppViewManagerUtils();
utils = new AppViewManagerUtils();
viewListener = new SpyAppViewListener();
viewPool = new SpyAppViewPool();
manager = new AppViewManager(viewPool, viewListener, utils, renderer);
createdViews = [];
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')
.andCallFake((_b, _c) => {
var rv = new RenderViewRef();
.andCallFake((_a, renderFragmentCount, _b) => {
var fragments = [];
for (var i = 0; i < renderFragmentCount; i++) {
fragments.push(new RenderFragmentRef());
}
var rv = new RenderViewWithFragments(new RenderViewRef(), fragments);
createdRenderViews.push(rv);
return rv;
});
renderer.spy('createView')
.andCallFake((_a) => {
var rv = new RenderViewRef();
.andCallFake((_a, renderFragmentCount) => {
var fragments = [];
for (var i = 0; i < renderFragmentCount; i++) {
fragments.push(new RenderFragmentRef());
}
var rv = new RenderViewWithFragments(new RenderViewRef(), fragments);
createdRenderViews.push(rv);
return rv;
});
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', () => {
var hostProtoView;
beforeEach(() => { hostProtoView = createProtoView([createComponentElBinder(null)]); });
var hostProtoView: AppProtoView;
beforeEach(
() => { hostProtoView = createHostPv([createNestedElBinder(createComponentPv())]); });
it('should create the view', () => {
expect(internalView(manager.createRootHostView(wrapPv(hostProtoView), null, null)))
.toBe(createdViews[0]);
expect(createdViews[0].proto).toBe(hostProtoView);
expect(viewListener.spy('viewCreated')).toHaveBeenCalledWith(createdViews[0]);
var rootView = internalView(manager.createRootHostView(wrapPv(hostProtoView), null, null));
expect(rootView.proto).toBe(hostProtoView);
expect(viewListener.spy('viewCreated')).toHaveBeenCalledWith(rootView);
});
it('should hydrate the view', () => {
var injector = Injector.resolveAndCreate([]);
manager.createRootHostView(wrapPv(hostProtoView), null, injector);
expect(utils.spy('hydrateRootHostView')).toHaveBeenCalledWith(createdViews[0], injector);
expect(renderer.spy('hydrateView')).toHaveBeenCalledWith(createdViews[0].render);
var rootView =
internalView(manager.createRootHostView(wrapPv(hostProtoView), null, injector));
expect(rootView.hydrated()).toBe(true);
expect(renderer.spy('hydrateView')).toHaveBeenCalledWith(rootView.render);
});
it('should create and set the render view using the component selector', () => {
manager.createRootHostView(wrapPv(hostProtoView), null, null)
expect(renderer.spy('createRootHostView'))
.toHaveBeenCalledWith(hostProtoView.render, 'someComponent');
expect(createdViews[0].render).toBe(createdRenderViews[0]);
var rootView = internalView(manager.createRootHostView(wrapPv(hostProtoView), null, null));
expect(renderer.spy('createRootHostView'))
.toHaveBeenCalledWith(hostProtoView.mergeMapping.renderProtoViewRef,
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', () => {
var selector = 'someOtherSelector';
manager.createRootHostView(wrapPv(hostProtoView), selector, null)
expect(renderer.spy('createRootHostView'))
.toHaveBeenCalledWith(hostProtoView.render, selector);
internalView(manager.createRootHostView(wrapPv(hostProtoView), selector, null));
expect(renderer.spy('createRootHostView'))
.toHaveBeenCalledWith(hostProtoView.mergeMapping.renderProtoViewRef,
hostProtoView.mergeMapping.renderFragmentCount, selector);
});
it('should set the event dispatcher', () => {
manager.createRootHostView(wrapPv(hostProtoView), null, null);
var cmpView = createdViews[0];
expect(renderer.spy('setEventDispatcher')).toHaveBeenCalledWith(cmpView.render, cmpView);
var rootView = internalView(manager.createRootHostView(wrapPv(hostProtoView), null, null));
expect(renderer.spy('setEventDispatcher')).toHaveBeenCalledWith(rootView.render, rootView);
});
});
describe('destroyRootHostView', () => {
var hostProtoView, hostView, hostRenderViewRef;
var hostProtoView: AppProtoView;
var hostView: AppView;
var hostRenderViewRef: RenderViewRef;
beforeEach(() => {
hostProtoView = createProtoView([createComponentElBinder(null)]);
hostProtoView = createHostPv([createNestedElBinder(createComponentPv())]);
hostView = internalView(manager.createRootHostView(wrapPv(hostProtoView), null, null));
hostRenderViewRef = hostView.render;
});
it('should dehydrate', () => {
manager.destroyRootHostView(wrapView(hostView));
expect(utils.spy('dehydrateView')).toHaveBeenCalledWith(hostView);
expect(hostView.hydrated()).toBe(false);
expect(renderer.spy('dehydrateView')).toHaveBeenCalledWith(hostView.render);
});
@ -269,60 +184,120 @@ export function main() {
describe('createViewInContainer', () => {
describe('basic functionality', () => {
var parentView, childProtoView;
var hostView: AppView;
var childProtoView: AppProtoView;
var vcRef: ElementRef;
beforeEach(() => {
parentView = createView(createProtoView([createEmptyElBinder()]));
childProtoView = createProtoView();
childProtoView = createEmbeddedPv();
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', () => {
manager.createViewInContainer(elementRef(parentView, 0), 0, wrapPv(childProtoView), null);
expect(parentView.viewContainers[0]).toBeTruthy();
describe('create the first view', () => {
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', () => {
expect(internalView(manager.createViewInContainer(elementRef(parentView, 0), 0,
wrapPv(childProtoView), null)))
.toBe(createdViews[0]);
expect(createdViews[0].proto).toBe(childProtoView);
expect(viewListener.spy('viewCreated')).toHaveBeenCalledWith(createdViews[0]);
describe('create the second view', () => {
var firstChildView;
beforeEach(() => {
firstChildView =
internalView(manager.createViewInContainer(vcRef, 0, wrapPv(childProtoView), null));
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', () => {
var contextView =
createView(createProtoView([createEmptyElBinder(), createEmptyElBinder()]));
var elRef = elementRef(parentView, 0);
manager.createViewInContainer(elRef, 0, wrapPv(childProtoView),
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);
});
describe('create another view when the first view has been returned', () => {
beforeEach(() => {
internalView(manager.createViewInContainer(vcRef, 0, wrapPv(childProtoView), null));
manager.destroyViewInContainer(vcRef, 0);
resetSpies();
});
it('should hydrate the view', () => {
var contextView =
createView(createProtoView([createEmptyElBinder(), createEmptyElBinder()]));
manager.createViewInContainer(elementRef(parentView, 0), 0, wrapPv(childProtoView),
elementRef(contextView, 1), []);
expect(utils.spy('hydrateViewInContainer'))
.toHaveBeenCalledWith(parentView, 0, contextView, 1, 0, []);
expect(renderer.spy('hydrateView')).toHaveBeenCalledWith(createdViews[0].render);
});
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 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('basic functionality', () => {
var parentView, childProtoView, childView;
var hostView: AppView;
var childProtoView: AppProtoView;
var vcRef: ElementRef;
var firstChildView: AppView;
beforeEach(() => {
parentView = createView(createProtoView([createEmptyElBinder()]));
childProtoView = createProtoView();
childView = internalView(manager.createViewInContainer(elementRef(parentView, 0), 0,
wrapPv(childProtoView), null));
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));
resetSpies();
});
it('should dehydrate', () => {
manager.destroyViewInContainer(elementRef(parentView, 0), 0);
expect(utils.spy('dehydrateView'))
.toHaveBeenCalledWith(parentView.viewContainers[0].views[0]);
expect(renderer.spy('dehydrateView')).toHaveBeenCalledWith(childView.render);
describe('destroy the first view', () => {
it('should dehydrate the app view but not the render view', () => {
manager.destroyViewInContainer(vcRef, 0);
expect(firstChildView.hydrated()).toBe(false);
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', () => {
var elRef = elementRef(parentView, 0);
manager.destroyViewInContainer(elRef, 0);
expect(utils.spy('detachViewInContainer')).toHaveBeenCalledWith(parentView, 0, 0);
expect(renderer.spy('detachViewInContainer'))
.toHaveBeenCalledWith(elRef, 0, childView.render);
});
describe('destroy another view', () => {
var secondChildView;
beforeEach(() => {
secondChildView =
internalView(manager.createViewInContainer(vcRef, 1, wrapPv(childProtoView), null));
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', () => {
var parentView, childProtoView, childView;
beforeEach(() => {
parentView = createView(createProtoView([createEmptyElBinder()]));
childProtoView = createProtoView();
childView = internalView(manager.createViewInContainer(elementRef(parentView, 0), 0,
wrapPv(childProtoView), null));
describe('destroy child views when a component is destroyed', () => {
var hostView: AppView;
var childProtoView: AppProtoView;
var vcRef: ElementRef;
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', () => {
manager.destroyRootHostView(wrapView(parentView));
expect(utils.spy('dehydrateView'))
.toHaveBeenCalledWith(parentView.viewContainers[0].views[0]);
expect(renderer.spy('dehydrateView')).toHaveBeenCalledWith(childView.render);
});
describe('destroy child views over multiple levels', () => {
var hostView: AppView;
var childProtoView: AppProtoView;
var nestedChildProtoView: AppProtoView;
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', () => {
manager.destroyRootHostView(wrapView(parentView));
expect(utils.spy('detachViewInContainer')).toHaveBeenCalledWith(parentView, 0, 0);
expect(renderer.spy('detachViewInContainer'))
.toHaveBeenCalledWith(parentView.elementRefs[0], 0, childView.render);
});
it('should dehydrate all child views', () => {
manager.destroyRootHostView(wrapView(hostView));
childViews.forEach((childView) => expect(childView.hydrated()).toBe(false));
nestedChildViews.forEach((childView) => expect(childView.hydrated()).toBe(false));
});
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
@IMPLEMENTS(Renderer)
class SpyRenderer extends SpyObject {
@ -428,23 +489,9 @@ class SpyAppViewPool extends SpyObject {
noSuchMethod(m) { return super.noSuchMethod(m) }
}
@proxy
@IMPLEMENTS(AppViewManagerUtils)
class SpyAppViewManagerUtils extends SpyObject {
constructor() { super(AppViewManagerUtils); }
noSuchMethod(m) { return super.noSuchMethod(m) }
}
@proxy
@IMPLEMENTS(AppViewListener)
class SpyAppViewListener extends SpyObject {
constructor() { super(AppViewListener); }
noSuchMethod(m) { return super.noSuchMethod(m) }
}
@proxy
@IMPLEMENTS(ElementInjector)
class SpyElementInjector extends SpyObject {
constructor() { super(ElementInjector); }
noSuchMethod(m) { return super.noSuchMethod(m) }
}

View File

@ -14,6 +14,7 @@ import {
xit,
SpyObject,
SpyChangeDetector,
SpyProtoChangeDetector,
proxy,
Log
} from 'angular2/test_lib';
@ -22,7 +23,7 @@ import {Injector, bind} from 'angular2/di';
import {IMPLEMENTS, isBlank, isPresent} from 'angular2/src/facade/lang';
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 {
DirectiveBinding,
@ -33,89 +34,31 @@ import {
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
import {Component} from 'angular2/annotations';
import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils';
import {RenderProtoViewMergeMapping, ViewType, RenderViewWithFragments} from 'angular2/render';
export function main() {
// TODO(tbosch): add more tests here!
describe('AppViewManagerUtils', () => {
var directiveResolver;
var utils;
var utils: AppViewManagerUtils;
function createInjector() { return Injector.resolveAndCreate([]); }
beforeEach(() => { utils = new AppViewManagerUtils(); });
function createDirectiveBinding(type) {
var annotation = directiveResolver.resolve(type);
return DirectiveBinding.createFromType(type, annotation);
function createViewWithChildren(pv: AppProtoView): AppView {
var renderViewWithFragments = new RenderViewWithFragments(null, [null, null]);
return utils.createView(pv, renderViewWithFragments, null, null);
}
function createEmptyElBinder() { return new ElementBinder(0, null, 0, null, null); }
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", () => {
describe('shared hydrate functionality', () => {
it("should hydrate the change detector after hydrating element injectors", () => {
var log = new Log();
var componentView = createView(createProtoView([createEmptyElBinder()]));
var hostView = createView(createProtoView([createComponentElBinder(createProtoView())]));
hostView.componentChildViews = [componentView];
var componentProtoView = createComponentPv([createEmptyElBinder()]);
var hostView =
createViewWithChildren(createHostPv([createNestedElBinder(componentProtoView)]));
var componentView = hostView.views[1];
var spyEi = <any>componentView.elementInjectors[0];
spyEi.spy('hydrate').andCallFake(log.fn('hydrate'));
@ -123,20 +66,17 @@ export function main() {
var spyCd = <any>componentView.changeDetector;
spyCd.spy('hydrate').andCallFake(log.fn('hydrateCD'));
utils.hydrateComponentView(hostView, 0);
utils.hydrateRootHostView(hostView, createInjector());
expect(log.result()).toEqual('hydrate; hydrateCD');
});
});
describe('shared hydrate functionality', () => {
it("should set up event listeners", () => {
var dir = new Object();
var hostPv = createProtoView([createComponentElBinder(null), createEmptyElBinder()]);
var hostView = createView(hostPv);
var hostPv =
createHostPv([createNestedElBinder(createComponentPv()), createEmptyElBinder()]);
var hostView = createViewWithChildren(hostPv);
var spyEventAccessor1 = SpyObject.stub({"subscribe": null});
SpyObject.stub(hostView.elementInjectors[0], {
'getHostActionAccessors': [],
@ -150,9 +90,6 @@ export function main() {
'getDirectiveAtIndex': dir
});
var shadowView = createView();
utils.attachComponentView(hostView, 0, shadowView);
utils.hydrateRootHostView(hostView, createInjector());
expect(spyEventAccessor1.spy('subscribe')).toHaveBeenCalledWith(hostView, 0, dir);
@ -162,8 +99,9 @@ export function main() {
it("should set up host action listeners", () => {
var dir = new Object();
var hostPv = createProtoView([createComponentElBinder(null), createEmptyElBinder()]);
var hostView = createView(hostPv);
var hostPv =
createHostPv([createNestedElBinder(createComponentPv()), createEmptyElBinder()]);
var hostView = createViewWithChildren(hostPv);
var spyActionAccessor1 = SpyObject.stub({"subscribe": null});
SpyObject.stub(hostView.elementInjectors[0], {
'getHostActionAccessors': [[spyActionAccessor1]],
@ -177,37 +115,51 @@ export function main() {
'getDirectiveAtIndex': dir
});
var shadowView = createView();
utils.attachComponentView(hostView, 0, shadowView);
utils.hydrateRootHostView(hostView, createInjector());
expect(spyActionAccessor1.spy('subscribe')).toHaveBeenCalledWith(hostView, 0, 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', () => {
var parentView, contextView, childView;
function createViews(numInj = 1) {
var parentPv = createProtoView([createEmptyElBinder()]);
parentView = createView(parentPv);
var childPv = createEmbeddedPv([createEmptyElBinder()]);
childView = createViewWithChildren(childPv);
var parentPv = createHostPv([createEmptyElBinder()]);
parentView = createViewWithChildren(parentPv);
var binders = [];
for (var i = 0; i < numInj; i++) binders.push(createEmptyElBinder());
var contextPv = createProtoView(binders);
contextView = createView(contextPv, true);
var childPv = createProtoView([createEmptyElBinder()]);
childView = createView(childPv);
for (var i = 0; i < numInj; i++) {
binders.push(createEmptyElBinder(i > 0 ? binders[i - 1] : null))
};
var contextPv = createHostPv(binders);
contextView = createViewWithChildren(contextPv);
}
it('should link the views rootElementInjectors at the given context', () => {
createViews();
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',
@ -223,14 +175,14 @@ export function main() {
var parentView, contextView, childView;
function createViews() {
var parentPv = createProtoView([createEmptyElBinder()]);
parentView = createView(parentPv);
var parentPv = createHostPv([createEmptyElBinder()]);
parentView = createViewWithChildren(parentPv);
var contextPv = createProtoView([createEmptyElBinder()]);
contextView = createView(contextPv);
var contextPv = createHostPv([createEmptyElBinder()]);
contextView = createViewWithChildren(contextPv);
var childPv = createProtoView([createEmptyElBinder()]);
childView = createView(childPv);
var childPv = createEmbeddedPv([createEmptyElBinder()]);
childView = createViewWithChildren(childPv);
utils.attachViewInContainer(parentView, 0, contextView, 0, 0, childView);
}
@ -249,8 +201,8 @@ export function main() {
var hostView;
function createViews() {
var hostPv = createProtoView([createComponentElBinder()]);
hostView = createView(hostPv);
var hostPv = createHostPv([createNestedElBinder(createComponentPv())]);
hostView = createViewWithChildren(hostPv);
}
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'})
class SomeComponent {
}
@proxy
@IMPLEMENTS(ElementInjector)
class SpyElementInjector extends SpyObject {
constructor() { super(ElementInjector); }
@IMPLEMENTS(ProtoElementInjector)
class SpyProtoElementInjector extends SpyObject {
index: number;
constructor(public parent: ProtoElementInjector) { super(ProtoElementInjector); }
noSuchMethod(m) { return super.noSuchMethod(m) }
}
@proxy
@IMPLEMENTS(ElementInjector)
class SpyElementInjectorWithParent extends SpyObject {
parent: ElementInjector;
constructor(parent) {
super(ElementInjector);
this.parent = parent;
}
class SpyElementInjector extends SpyObject {
constructor(public parent: ElementInjector) { super(ElementInjector); }
noSuchMethod(m) { return super.noSuchMethod(m) }
}

View File

@ -24,9 +24,11 @@ export function main() {
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', () => {
var vf = createViewPool({capacity: 2});

View 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);
});
});
}

View File

@ -36,7 +36,7 @@ export function runCompilerCommonTests() {
}
var tplLoader = new FakeViewLoader(urlData);
mockStepFactory = new MockStepFactory([new MockStep(processClosure)]);
return new DomCompiler(mockStepFactory, tplLoader);
return new DomCompiler(mockStepFactory, tplLoader, false);
}
describe('compile', () => {
@ -64,7 +64,8 @@ export function runCompilerCommonTests() {
{id: 'id', selector: 'CUSTOM', type: DirectiveMetadata.COMPONENT_TYPE});
compiler.compileHost(dirMetadata)
.then((protoView) => {
expect(DOM.tagName(resolveInternalDomProtoView(protoView.render).element))
expect(DOM.tagName(DOM.firstChild(DOM.content(
resolveInternalDomProtoView(protoView.render).rootElement))))
.toEqual('CUSTOM');
expect(mockStepFactory.viewDef.directives).toEqual([dirMetadata]);
expect(protoView.variableBindings)
@ -79,7 +80,7 @@ export function runCompilerCommonTests() {
compiler.compile(
new ViewDefinition({componentId: 'someId', template: 'inline component'}))
.then((protoView) => {
expect(DOM.getInnerHTML(resolveInternalDomProtoView(protoView.render).element))
expect(DOM.getInnerHTML(resolveInternalDomProtoView(protoView.render).rootElement))
.toEqual('inline component');
async.done();
});
@ -90,7 +91,7 @@ export function runCompilerCommonTests() {
var compiler = createCompiler(EMPTY_STEP, urlData);
compiler.compile(new ViewDefinition({componentId: 'someId', templateAbsUrl: 'someUrl'}))
.then((protoView) => {
expect(DOM.getInnerHTML(resolveInternalDomProtoView(protoView.render).element))
expect(DOM.getInnerHTML(resolveInternalDomProtoView(protoView.render).rootElement))
.toEqual('url component');
async.done();
});

View File

@ -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 {CompilePipeline} from 'angular2/src/render/dom/compiler/compile_pipeline';
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 {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';
export function main() {
@ -14,48 +17,55 @@ export function main() {
[new IgnoreChildrenStep(), new TextInterpolationParser(new Parser(new Lexer()))]);
}
function process(element): List<ElementBinderBuilder> {
return ListWrapper.map(createPipeline().process(element),
(compileElement) => compileElement.inheritedElementBinder);
function process(templateString: string): ProtoViewBuilder {
var compileElements = createPipeline().process(DOM.createTemplate(templateString));
return compileElements[0].inheritedProtoView;
}
function assertTextBinding(elementBinder, bindingIndex, nodeIndex, expression) {
expect(elementBinder.textBindings[bindingIndex].source).toEqual(expression);
expect(elementBinder.textBindingNodes[bindingIndex])
.toEqual(DOM.childNodes(DOM.templateAwareRoot(elementBinder.element))[nodeIndex]);
function assertRootTextBinding(protoViewBuilder: ProtoViewBuilder, nodeIndex: number,
expression: string) {
var node = DOM.childNodes(DOM.templateAwareRoot(protoViewBuilder.rootElement))[nodeIndex];
expect(protoViewBuilder.rootTextBindings.get(node).source).toEqual(expression);
}
it('should find text interpolation in normal elements', () => {
var result = process(el('<div>{{expr1}}<span></span>{{expr2}}</div>'))[0];
assertTextBinding(result, 0, 0, "{{expr1}}");
assertTextBinding(result, 1, 2, "{{expr2}}");
function assertElementTextBinding(elementBinderBuilder: ElementBinderBuilder, nodeIndex: number,
expression: string) {
var node = DOM.childNodes(DOM.templateAwareRoot(elementBinderBuilder.element))[nodeIndex];
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', () => {
var result = process(el('<template>{{expr1}}<span></span>{{expr2}}</template>'))[0];
assertTextBinding(result, 0, 0, "{{expr1}}");
assertTextBinding(result, 1, 2, "{{expr2}}");
it('should find text interpolation in normal elements', () => {
var result = process('<div>{{expr1}}<span></span>{{expr2}}</div>');
assertElementTextBinding(result.elements[0], 0, "{{expr1}}");
assertElementTextBinding(result.elements[0], 2, "{{expr2}}");
});
it('should allow multiple expressions', () => {
var result = process(el('<div>{{expr1}}{{expr2}}</div>'))[0];
assertTextBinding(result, 0, 0, "{{expr1}}{{expr2}}");
var result = process('<div>{{expr1}}{{expr2}}</div>');
assertElementTextBinding(result.elements[0], 0, "{{expr1}}{{expr2}}");
});
it('should not interpolate when compileChildren is false', () => {
var results = process(el('<div>{{included}}<span ignore-children>{{excluded}}</span></div>'));
assertTextBinding(results[0], 0, 0, "{{included}}");
expect(results[1]).toBe(results[0]);
var results = process('<div>{{included}}<span ignore-children>{{excluded}}</span></div>');
assertElementTextBinding(results.elements[0], 0, "{{included}}");
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', () => {
var result = process(el('<div>a{{expr1}}b{{expr2}}c</div>'))[0];
assertTextBinding(result, 0, 0, "a{{expr1}}b{{expr2}}c");
var result = process('<div>a{{expr1}}b{{expr2}}c</div>');
assertElementTextBinding(result.elements[0], 0, "a{{expr1}}b{{expr2}}c");
});
it('should escape quotes in fixed parts', () => {
var result = process(el("<div>'\"a{{expr1}}</div>"))[0];
assertTextBinding(result, 0, 0, "'\"a{{expr1}}");
var result = process("<div>'\"a{{expr1}}</div>");
assertElementTextBinding(result.elements[0], 0, "'\"a{{expr1}}");
});
});
}

View File

@ -27,7 +27,7 @@ export function main() {
describe('<template> elements', () => {
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);
expect(stringifyElement(results[1].element))
@ -38,32 +38,32 @@ export function main() {
});
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);
expect(results[2].isViewRoot).toBe(true);
});
it('should not wrap the root element', () => {
var rootElement = el('<div></div>');
var rootElement = DOM.createTemplate('');
var results = createPipeline().process(rootElement);
expect(results.length).toBe(1);
expect(stringifyElement(rootElement)).toEqual('<div></div>');
expect(stringifyElement(rootElement)).toEqual('<template></template>');
});
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);
expect(results[2].elementDescription).toBe(results[1].elementDescription);
});
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);
expect(results[2].inheritedElementBinder).toBe(null);
});
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);
expect(results[2].inheritedProtoView).not.toBe(null);
expect(results[2].inheritedProtoView)
@ -78,18 +78,19 @@ export function main() {
describe('elements with template attribute', () => {
it('should replace the element with an empty <template> element', () => {
var rootElement = el('<div><span template=""></span></div>');
var originalChild = rootElement.childNodes[0];
var rootElement = DOM.createTemplate('<span template=""></span>');
var originalChild = DOM.firstChild(DOM.content(rootElement));
var results = createPipeline().process(rootElement);
expect(results[0].element).toBe(rootElement);
expect(stringifyElement(results[0].element))
.toEqual('<div><template class="ng-binding"></template></div>');
expect(stringifyElement(results[2].element)).toEqual('<span template=""></span>');
expect(results[2].element).toBe(originalChild);
.toEqual('<template><template class="ng-binding"></template></template>');
expect(stringifyElement(results[2].element))
.toEqual('<template><span template=""></span></template>');
expect(DOM.firstChild(DOM.content(results[2].element))).toBe(originalChild);
});
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 results = createPipeline().process(rootElement);
@ -98,17 +99,17 @@ export function main() {
expect(results[2].isViewRoot).toBe(true);
expect(stringifyElement(results[0].element))
.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', () => {
var rootElement = el('<div><div template></div></div>');
var rootElement = DOM.createTemplate('<div template></div>');
var results = createPipeline().process(rootElement);
expect(results[2].isViewRoot).toBe(true);
});
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);
expect(results[1].inheritedElementBinder.propertyBindings.get('someProp').source)
.toEqual('expr');
@ -116,14 +117,14 @@ export function main() {
});
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);
expect(results[2].inheritedProtoView.variableBindings)
.toEqual(MapWrapper.createFromStringMap({'mapName': 'varName'}));
});
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);
expect(results[1].attrs().get('varname')).toEqual('');
expect(results[1].inheritedElementBinder.propertyBindings).toEqual(new Map());
@ -131,32 +132,32 @@ export function main() {
});
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);
// 1 root + 2 initial + 1 generated template elements
expect(results.length).toEqual(4);
// 1 root + 2 initial + 2 generated template elements
expect(results.length).toEqual(5);
});
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);
expect(results[2].elementDescription).toBe(results[1].elementDescription);
});
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);
expect(results[2].inheritedElementBinder).toBe(null);
});
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);
expect(results[2].inheritedProtoView).not.toBe(null);
expect(results[2].inheritedProtoView)
.toBe(results[1].inheritedElementBinder.nestedProtoView);
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', () => {
it('should replace the element with an empty <template> element', () => {
var rootElement = el('<div><span *ng-if></span></div>');
var originalChild = rootElement.childNodes[0];
var rootElement = DOM.createTemplate('<span *ng-if></span>');
var originalChild = DOM.firstChild(DOM.content(rootElement));
var results = createPipeline().process(rootElement);
expect(results[0].element).toBe(rootElement);
expect(stringifyElement(results[0].element))
.toEqual('<div><template class="ng-binding" ng-if=""></template></div>');
expect(stringifyElement(results[2].element)).toEqual('<span *ng-if=""></span>');
expect(results[2].element).toBe(originalChild);
.toEqual('<template><template class="ng-binding" ng-if=""></template></template>');
expect(stringifyElement(results[2].element))
.toEqual('<template><span *ng-if=""></span></template>');
expect(DOM.firstChild(DOM.content(results[2].element))).toBe(originalChild);
});
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);
expect(results[2].isViewRoot).toBe(true);
});
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 results = createPipeline().process(rootElement);
@ -190,11 +192,11 @@ export function main() {
expect(results[2].isViewRoot).toBe(true);
expect(stringifyElement(results[0].element))
.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', () => {
var rootElement = el('<div><div *prop="expr"></div></div>');
var rootElement = DOM.createTemplate('<div *prop="expr"></div>');
var results = createPipeline().process(rootElement);
expect(results[1].inheritedElementBinder.propertyBindings.get('prop').source)
.toEqual('expr');
@ -202,14 +204,14 @@ export function main() {
});
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);
expect(results[2].inheritedProtoView.variableBindings)
.toEqual(MapWrapper.createFromStringMap({'mapName': 'varName'}));
});
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);
expect(results[1].attrs().get('varname')).toEqual('');
expect(results[1].inheritedElementBinder.propertyBindings).toEqual(new Map());
@ -217,32 +219,32 @@ export function main() {
});
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);
// 1 root + 2 initial + 1 generated template elements
expect(results.length).toEqual(4);
// 1 root + 2 initial + 2 generated template elements
expect(results.length).toEqual(5);
});
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);
expect(results[2].elementDescription).toBe(results[1].elementDescription);
});
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);
expect(results[2].inheritedElementBinder).toBe(null);
});
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);
expect(results[2].inheritedProtoView).not.toBe(null);
expect(results[2].inheritedProtoView)
.toBe(results[1].inheritedElementBinder.nestedProtoView);
expect(stringifyElement(results[2].inheritedProtoView.rootElement))
.toEqual('<span *foo=""></span>');
.toEqual('<template><span *foo=""></span></template>');
});
});

View File

@ -16,10 +16,11 @@ import {
import {MapWrapper} from 'angular2/src/facade/collection';
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 {DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES} from 'angular2/src/render/dom/dom_renderer';
import {ShadowDomStrategy, NativeShadowDomStrategy} from 'angular2/render';
import {bind} from 'angular2/di';
export function main() {
@ -27,101 +28,69 @@ export function main() {
beforeEachBindings(() => [DomTestbed]);
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)
.then((hostProtoViewDto) => {
var view =
new TestView(tb.renderer.createRootHostView(hostProtoViewDto.render, '#root'));
expect(view.rawView.rootNodes[0]).toEqual(tb.rootEl);
tb.renderer.destroyView(view.viewRef);
// destroying a root view should not disconnect it!
var view = new TestRootView(
tb.renderer.createRootHostView(hostProtoViewDto.render, 0, '#root'));
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();
});
}));
it('should attach and detach component views',
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
tb.compileAll([
someComponent,
new ViewDefinition({componentId: 'someComponent', template: 'hello', directives: []})
])
.then((protoViewDtos) => {
var rootView = tb.createRootView(protoViewDtos[0]);
var cmpView = tb.createComponentView(rootView.viewRef, 0, protoViewDtos[1]);
expect(tb.rootEl).toHaveText('hello');
tb.destroyComponentView(rootView.viewRef, 0, cmpView.viewRef);
expect(tb.rootEl).toHaveText('');
async.done();
});
}));
it('should update text nodes',
inject([AsyncTestCompleter, DomTestbed], (async, tb: DomTestbed) => {
tb.compileAndMerge(
someComponent,
[
new ViewDefinition(
{componentId: 'someComponent', template: '{{a}}', directives: []})
])
.then((protoViewMergeMappings) => {
var rootView = tb.createView(protoViewMergeMappings[0]);
it('should not create LightDom instances if the host element is empty',
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
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');
tb.renderer.setText(rootView.viewRef, 0, 'hello');
expect(rootView.hostElement).toHaveText('hello');
async.done();
});
}));
it('should update any element property/attributes/class/style independent of the compilation',
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
tb.compileAll([
someComponent,
new ViewDefinition({
componentId: 'someComponent',
template: '<input [title]="y" style="position:absolute">',
directives: []
})
])
.then((protoViewDtos) => {
var rootView = tb.createRootView(protoViewDtos[0]);
var cmpView = tb.createComponentView(rootView.viewRef, 0, protoViewDtos[1]);
inject([AsyncTestCompleter, DomTestbed], (async, tb: DomTestbed) => {
tb.compileAndMerge(someComponent,
[
new ViewDefinition({
componentId: 'someComponent',
template: '<input [title]="y" style="position:absolute">',
directives: []
})
])
.then((protoViewMergeMappings) => {
var rootView = tb.createView(protoViewMergeMappings[0]);
var el = DOM.childNodes(tb.rootEl)[0];
tb.renderer.setElementProperty(elRef(cmpView.viewRef, 0), 'value', 'hello');
var elr = elRef(rootView.viewRef, 1);
var el = DOM.childNodes(rootView.hostElement)[0];
tb.renderer.setElementProperty(elr, 'value', 'hello');
expect((<HTMLInputElement>el).value).toEqual('hello');
tb.renderer.setElementClass(elRef(cmpView.viewRef, 0), 'a', true);
expect((<HTMLInputElement>DOM.childNodes(tb.rootEl)[0]).value).toEqual('hello');
tb.renderer.setElementClass(elRef(cmpView.viewRef, 0), 'a', false);
tb.renderer.setElementClass(elr, 'a', true);
expect((<HTMLInputElement>DOM.childNodes(rootView.hostElement)[0]).value)
.toEqual('hello');
tb.renderer.setElementClass(elr, 'a', 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');
tb.renderer.setElementStyle(elRef(cmpView.viewRef, 0), 'width', null);
tb.renderer.setElementStyle(elr, 'width', null);
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');
async.done();
@ -131,16 +100,18 @@ export function main() {
it('should NOT reflect property values as attributes if flag is NOT set',
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
tb.compileAll([
someComponent,
new ViewDefinition(
{componentId: 'someComponent', template: '<input [title]="y">', directives: []})
])
.then((protoViewDtos) => {
var rootView = tb.createRootView(protoViewDtos[0]);
var cmpView = tb.createComponentView(rootView.viewRef, 0, protoViewDtos[1]);
var el = DOM.childNodes(tb.rootEl)[0];
tb.renderer.setElementProperty(elRef(cmpView.viewRef, 0), 'maxLength', '20');
tb.compileAndMerge(someComponent,
[
new ViewDefinition({
componentId: 'someComponent',
template: '<input [title]="y">',
directives: []
})
])
.then((protoViewMergeMappings) => {
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'))
.toEqual(null);
@ -150,21 +121,21 @@ export function main() {
describe('reflection', () => {
beforeEachBindings(() => [bind(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES).toValue(true)]);
it('should reflect property values as attributes if flag is set',
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
tb.compileAll([
someComponent,
new ViewDefinition({
componentId: 'someComponent',
template: '<input [title]="y">',
directives: []
})
])
.then((protoViewDtos) => {
var rootView = tb.createRootView(protoViewDtos[0]);
var cmpView = tb.createComponentView(rootView.viewRef, 0, protoViewDtos[1]);
var el = DOM.childNodes(tb.rootEl)[0];
tb.renderer.setElementProperty(elRef(cmpView.viewRef, 0), 'maxLength', '20');
tb.compileAndMerge(someComponent,
[
new ViewDefinition({
componentId: 'someComponent',
template: '<input [title]="y">',
directives: []
})
])
.then((protoViewMergeMappings) => {
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'))
.toEqual('20');
async.done();
@ -174,70 +145,69 @@ export function main() {
if (DOM.supportsDOMEvents()) {
it('should call actions on the element independent of the compilation',
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
tb.compileAll([
someComponent,
new ViewDefinition({
componentId: 'someComponent',
template: '<input [title]="y"></input>',
directives: []
})
])
.then((protoViewDtos) => {
var views = tb.createRootViews(protoViewDtos);
var componentView = views[1];
inject([AsyncTestCompleter, DomTestbed], (async, tb: DomTestbed) => {
tb.compileAndMerge(someComponent,
[
new ViewDefinition({
componentId: 'someComponent',
template: '<input [title]="y"></input>',
directives: []
})
])
.then((protoViewMergeMappings) => {
var rootView = tb.createView(protoViewMergeMappings[0]);
tb.renderer.invokeElementMethod(elRef(componentView.viewRef, 0), 'setAttribute',
tb.renderer.invokeElementMethod(elRef(rootView.viewRef, 1), 'setAttribute',
['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();
});
}));
}
it('should add and remove views to and from containers',
inject([AsyncTestCompleter, DomTestbed], (async, tb) => {
tb.compileAll([
someComponent,
new ViewDefinition({
componentId: 'someComponent',
template: '<template>hello</template>',
directives: []
})
])
.then((protoViewDtos) => {
var rootView = tb.createRootView(protoViewDtos[0]);
var cmpView = tb.createComponentView(rootView.viewRef, 0, protoViewDtos[1]);
it('should add and remove fragments',
inject([AsyncTestCompleter, DomTestbed], (async, tb: DomTestbed) => {
tb.compileAndMerge(someComponent,
[
new ViewDefinition({
componentId: 'someComponent',
template: '<template>hello</template>',
directives: []
})
])
.then((protoViewMergeMappings) => {
var rootView = tb.createView(protoViewMergeMappings[0]);
var childProto = protoViewDtos[1].elementBinders[0].nestedProtoView;
expect(tb.rootEl).toHaveText('');
var childView = tb.createViewInContainer(cmpView.viewRef, 0, 0, childProto);
expect(tb.rootEl).toHaveText('hello');
tb.destroyViewInContainer(cmpView.viewRef, 0, 0, childView.viewRef);
expect(tb.rootEl).toHaveText('');
var elr = elRef(rootView.viewRef, 1);
expect(rootView.hostElement).toHaveText('');
var fragment = rootView.fragments[1];
tb.renderer.attachFragmentAfterElement(elr, fragment);
expect(rootView.hostElement).toHaveText('hello');
tb.renderer.detachFragment(fragment);
expect(rootView.hostElement).toHaveText('');
async.done();
});
}));
it('should handle events', inject([AsyncTestCompleter, DomTestbed], (async, tb: DomTestbed) => {
tb.compileAll([
someComponent,
new ViewDefinition({
componentId: 'someComponent',
template: '<input (change)="doSomething()">',
directives: []
})
])
tb.compileAndMerge(someComponent,
[
new ViewDefinition({
componentId: 'someComponent',
template: '<input (change)="doSomething()">',
directives: []
})
])
.then((protoViewDtos) => {
var rootView = tb.createRootView(protoViewDtos[0]);
var cmpView = tb.createComponentView(rootView.viewRef, 0, protoViewDtos[1]);
var rootView = tb.createView(protoViewDtos[0]);
tb.triggerEvent(cmpView.viewRef, 0, 'change');
var eventEntry = cmpView.events[0];
tb.triggerEvent(elRef(rootView.viewRef, 1), 'change');
var eventEntry = rootView.events[0];
// bound element index
expect(eventEntry[0]).toEqual(0);
expect(eventEntry[0]).toEqual(1);
// event type
expect(eventEntry[1]).toEqual('change');
// 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();
});
}));
});
}
});
}

View File

@ -1,48 +1,57 @@
import {Inject, Injectable} from 'angular2/di';
import {isPresent} from 'angular2/src/facade/lang';
import {MapWrapper, ListWrapper, List, Map} from 'angular2/src/facade/collection';
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {DomRenderer, DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer';
import {DefaultDomCompiler} from 'angular2/src/render/dom/compiler/compiler';
import {DomView} from 'angular2/src/render/dom/view/view';
import {
RenderViewWithFragments,
RenderFragmentRef,
RenderViewRef,
ProtoViewDto,
ViewDefinition,
EventDispatcher,
RenderEventDispatcher,
DirectiveMetadata,
RenderElementRef
RenderElementRef,
RenderProtoViewMergeMapping,
RenderProtoViewRef
} from 'angular2/src/render/api';
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';
export class TestView {
rawView: DomView;
export class TestRootView {
viewRef: RenderViewRef;
fragments: RenderFragmentRef[];
hostElement: Element;
events: List<List<any>>;
constructor(viewRef: RenderViewRef) {
this.viewRef = viewRef;
this.rawView = resolveInternalDomView(viewRef);
constructor(viewWithFragments: RenderViewWithFragments) {
this.viewRef = viewWithFragments.viewRef;
this.fragments = viewWithFragments.fragmentRefs;
this.hostElement = <Element>resolveInternalDomFragment(this.fragments[0])[0];
this.events = [];
}
}
export class TestRenderElementRef implements RenderElementRef {
constructor(public renderView: RenderViewRef, public renderBoundElementIndex: number) {}
}
export function elRef(renderView: RenderViewRef, boundElementIndex: number) {
return new TestRenderElementRef(renderView, boundElementIndex);
}
class TestRenderElementRef implements RenderElementRef {
constructor(public renderView: RenderViewRef, public boundElementIndex: number) {}
}
export function rootNodes(view: RenderViewRef) {}
class LoggingEventDispatcher implements EventDispatcher {
class LoggingEventDispatcher implements RenderEventDispatcher {
log: List<List<any>>;
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]);
return true;
}
@ -67,71 +76,62 @@ export class DomTestbed {
DOM.appendChild(DOM.querySelector(document, 'body'), this.rootEl);
}
compileAll(directivesOrViewDefinitions:
List<DirectiveMetadata | ViewDefinition>): Promise<List<ProtoViewDto>> {
return PromiseWrapper.all(ListWrapper.map(directivesOrViewDefinitions, (entry) => {
if (entry instanceof DirectiveMetadata) {
return this.compiler.compileHost(entry);
} else {
return this.compiler.compile(entry);
}
}));
compile(host: DirectiveMetadata, componentViews: ViewDefinition[]): Promise<ProtoViewDto[]> {
var promises = [this.compiler.compileHost(host)];
componentViews.forEach(view => promises.push(this.compiler.compile(view)));
return PromiseWrapper.all(promises);
}
_createTestView(viewRef: RenderViewRef) {
var testView = new TestView(viewRef);
this.renderer.setEventDispatcher(viewRef, new LoggingEventDispatcher(testView.events));
merge(protoViews:
List<ProtoViewDto | RenderProtoViewRef>): Promise<RenderProtoViewMergeMapping[]> {
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;
}
createRootView(rootProtoView: ProtoViewDto): TestView {
var viewRef = this.renderer.createRootHostView(rootProtoView.render, '#root');
this.renderer.hydrateView(viewRef);
return this._createTestView(viewRef);
createView(protoView: RenderProtoViewMergeMapping): TestRootView {
var viewWithFragments = this.renderer.createView(protoView.mergedProtoViewRef, 0);
this.renderer.hydrateView(viewWithFragments.viewRef);
return this._createTestView(viewWithFragments);
}
createComponentView(parentViewRef: RenderViewRef, boundElementIndex: number,
componentProtoView: ProtoViewDto): TestView {
var componentViewRef = this.renderer.createView(componentProtoView.render);
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;
triggerEvent(elementRef: RenderElementRef, eventName: string) {
var element = resolveInternalDomView(elementRef.renderView)
.boundElements[elementRef.renderBoundElementIndex];
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;
}

View File

@ -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}`);
});
});
}

View File

@ -32,10 +32,8 @@ export function main() {
resetShadowDomCache();
});
it('should use the host element as shadow root', () => {
var host = el('<div><span>original content</span></div>');
expect(strategy.prepareShadowRoot(host)).toBe(host);
});
it('should report that this is not the native strategy',
() => { expect(strategy.hasNativeContentElement()).toBe(false); });
it('should scope styles', () => {
var styleElement = el('<style>.foo {} :host {}</style>');

View File

@ -34,10 +34,8 @@ export function main() {
resetShadowDomCache();
});
it('should use the host element as shadow root', () => {
var host = el('<div><span>original content</span></div>');
expect(strategy.prepareShadowRoot(host)).toBe(host);
});
it('should report that this is not the native strategy',
() => { expect(strategy.hasNativeContentElement()).toBe(false); });
it('should move the style element to the style host', () => {
var compileElement = el('<div><style>.one {}</style></div>');

View File

@ -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);
}

View File

@ -16,19 +16,13 @@ import {
NativeShadowDomStrategy
} from 'angular2/src/render/dom/shadow_dom/native_shadow_dom_strategy';
import {DOM} from 'angular2/src/dom/dom_adapter';
export function main() {
var strategy;
var strategy: NativeShadowDomStrategy;
describe('NativeShadowDomStrategy', () => {
beforeEach(() => { strategy = new NativeShadowDomStrategy(); });
if (DOM.supportsNativeShadowDOM()) {
it('should use the native shadow root', () => {
var host = el('<div><span>original content</span></div>');
expect(strategy.prepareShadowRoot(host)).toBe(DOM.getShadowRoot(host));
});
}
it('should report that this is the native strategy',
() => { expect(strategy.hasNativeContentElement()).toBe(true); });
});
}

View File

@ -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]
});

View File

@ -14,13 +14,15 @@ import {
import {ProtoViewBuilder} from 'angular2/src/render/dom/view/proto_view_builder';
import {ASTWithSource, AST} from 'angular2/change_detection';
import {PropertyBindingType, ViewType} from 'angular2/src/render/api';
import {DOM} from 'angular2/src/dom/dom_adapter';
export function main() {
function emptyExpr() { return new ASTWithSource(new AST(), 'empty', 'empty'); }
describe('ProtoViewBuilder', () => {
var builder;
beforeEach(() => { builder = new ProtoViewBuilder(el('<div/>'), ViewType.EMBEDDED); });
beforeEach(
() => { builder = new ProtoViewBuilder(DOM.createTemplate(''), ViewType.EMBEDDED); });
if (!IS_DARTIUM) {
describe('verification of properties', () => {

View File

@ -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'});

View File

@ -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');
});
});
});
}

View File

@ -19,9 +19,8 @@ import {isBlank} from 'angular2/src/facade/lang';
import {ListWrapper} from 'angular2/src/facade/collection';
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 {DomElement} from 'angular2/src/render/dom/view/element';
import {DOM} from 'angular2/src/dom/dom_adapter';
export function main() {
@ -30,54 +29,33 @@ export function main() {
if (isBlank(binders)) {
binders = [];
}
var rootEl = el('<div></div>');
return new DomProtoView({
element: rootEl,
elementBinders: binders,
transitiveContentTagCount: 0,
boundTextNodeCount: 0
});
var rootEl = DOM.createTemplate('<div></div>');
return DomProtoView.create(null, <Element>rootEl, [1], [], binders, null, null, null);
}
function createElementBinder() { return new DomElementBinder({textNodeIndices: []}); }
function createView(pv = null, boundElementCount = 0) {
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 boundElements = [];
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', () => {
var el, view;
beforeEach(() => {
view = createView(null, 1);
el = view.boundElements[0].element;
el = view.boundElements[0];
});
it('should update the property value', () => {
@ -91,7 +69,7 @@ export function main() {
var el, view;
beforeEach(() => {
view = createView(null, 1);
el = view.boundElements[0].element;
el = view.boundElements[0];
});
it('should update and remove an attribute', () => {
@ -111,7 +89,7 @@ export function main() {
var el, view;
beforeEach(() => {
view = createView(null, 1);
el = view.boundElements[0].element;
el = view.boundElements[0];
});
it('should set and remove a class', () => {
@ -135,7 +113,7 @@ export function main() {
var el, view;
beforeEach(() => {
view = createView(null, 1);
el = view.boundElements[0].element;
el = view.boundElements[0];
});
it('should set and remove styles', () => {

View File

@ -206,11 +206,9 @@ export function main() {
.then((_) => rtr.navigate('/page/1'))
.then((_) => {
rootTC.detectChanges();
expect(DOM.getAttribute(rootTC.componentViewChildren[1]
.componentViewChildren[0]
.children[0]
.nativeElement,
'href'))
expect(DOM.getAttribute(
rootTC.componentViewChildren[1].componentViewChildren[0].nativeElement,
'href'))
.toEqual('/page/2');
async.done();
});
@ -232,7 +230,6 @@ export function main() {
expect(DOM.getAttribute(rootTC.componentViewChildren[1]
.componentViewChildren[2]
.componentViewChildren[0]
.children[0]
.nativeElement,
'href'))
.toEqual('/book/1984/page/2');

View File

@ -1,2 +1,2 @@
<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>

View File

@ -4,5 +4,5 @@
<div class="md-checkbox-container">
<div class="md-checkbox-icon"></div>
</div>
<div class="md-checkbox-label"><content></content></div>
<div class="md-checkbox-label"><ng-content></ng-content></div>
</div>

View File

@ -64,7 +64,7 @@ export class MdDialog {
// 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).
// 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);
// TODO(jelbourn): Use hostProperties binding to set these once #1539 is fixed.
@ -109,7 +109,7 @@ export class MdDialog {
.then((componentRef) => {
// TODO(tbosch): clean this up when we have custom renderers
// (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.appendChild(DOM.query('body'), backdropElement);
return componentRef;

View File

@ -5,5 +5,5 @@
</style>
<div class="md-grid-list">
<content></content>
<ng-content></ng-content>
</div>

View File

@ -1,5 +1,5 @@
<style>@import "angular2_material/src/components/grid_list/grid-list.css";</style>
<figure>
<content></content>
<ng-content></ng-content>
</figure>

View File

@ -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.
.md-radio-label {
position: relative;

View File

@ -13,6 +13,6 @@
<!-- The label for radio control. -->
<div class="md-radio-label">
<content></content>
<ng-content></ng-content>
</div>
</label>

View File

@ -1,2 +1,2 @@
<style>@import "angular2_material/src/components/radio/radio-group.css";</style>
<content></content>
<ng-content></ng-content>

View File

@ -7,5 +7,5 @@
<div class="md-switch-thumb"></div>
</div>
</div>
<div class="md-switch-label"><content></content></div>
<div class="md-switch-label"><ng-content></ng-content></div>
</div>

View File

@ -9,6 +9,7 @@ describe('md-dialog', function() {
it('should open a dialog', function() {
var openButton = element(by.id('open'));
openButton.click();
browser.sleep(500);
expect(element(by.css('.md-dialog')).isPresent()).toEqual(true);
var dialog = element(by.css('.md-dialog'));

View File

@ -3,6 +3,6 @@
{{ visible ? '&blacktriangledown;' : '&blacktriangleright;' }} {{title}}
</div>
<div [hidden]="!visible" class="zippy__content">
<content></content>
<ng-content></ng-content>
</div>
</div>