From c68fa274440d4a35f6267cd34ee7f408a890f690 Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Wed, 6 May 2015 10:49:42 -0700 Subject: [PATCH] refactor(render): remove recursion from renderer The goal is to make implementing a renderer straight forward. BREAKING_CHANGE: - Renderer interface was redone / simplified. - `DirectDomRenderer` was replaced by `DomRenderer`. - `DirectDomRenderer.setImperativeComponentRootNodes` is replaced by the following 2 steps: 1. `ViewManager.getComponentView(elementRef) -> ViewRef` 2. `DomRenderer.setComponentViewRootNodes(viewRef, rootNodes)` - all `@View` annotations need to have a template, but the template may be empty. Previously views that had a `renderer` property did not have to have a `template`. - `dynamicComponentLoader.loadIntoNewLocation` does no more allow to pass an element, but requires a css selector. Special syntax: `:document` can be used as prefix to search globally on the document instead of in the provided parent view. Part of #1675 --- modules/angular2/angular2.js | 2 +- modules/angular2/src/core/application.js | 54 +-- .../angular2/src/core/application_tokens.js | 2 - .../angular2/src/core/compiler/compiler.js | 31 +- .../core/compiler/dynamic_component_loader.js | 6 +- .../src/core/compiler/element_injector.js | 2 +- .../angular2/src/core/compiler/element_ref.js | 5 +- modules/angular2/src/core/compiler/view.js | 4 +- .../src/core/compiler/view_manager.js | 157 ++++--- .../src/core/compiler/view_manager_utils.js | 10 +- modules/angular2/src/render/api.js | 169 +++---- .../src/render/dom/compiler/compiler.js | 20 - .../src/render/dom/direct_dom_renderer.js | 158 ------- .../angular2/src/render/dom/dom_renderer.js | 338 +++++++++++++ .../src/render/dom/shadow_dom/content_tag.js | 6 +- .../emulated_unscoped_shadow_dom_strategy.js | 9 +- .../src/render/dom/shadow_dom/light_dom.js | 18 +- .../shadow_dom/native_shadow_dom_strategy.js | 7 +- .../dom/shadow_dom/shadow_dom_strategy.js | 9 +- .../src/render/dom/shadow_dom/util.js | 6 - .../src/render/dom/view/element_binder.js | 9 - .../src/render/dom/view/proto_view.js | 42 +- .../src/render/dom/view/proto_view_builder.js | 17 +- modules/angular2/src/render/dom/view/view.js | 56 +-- .../src/render/dom/view/view_container.js | 93 +--- .../src/render/dom/view/view_factory.js | 164 ------- .../src/render/dom/view/view_hydrator.js | 190 -------- modules/angular2/src/test_lib/test_bed.js | 10 +- .../angular2/src/test_lib/test_injector.js | 22 +- modules/angular2/src/test_lib/utils.js | 3 +- .../angular2/test/core/application_spec.js | 17 +- .../test/core/compiler/compiler_spec.js | 19 - .../compiler/dynamic_component_loader_spec.js | 16 +- .../test/core/compiler/integration_spec.js | 11 +- .../test/core/compiler/view_manager_spec.js | 79 ++-- .../dom/compiler/compiler_common_tests.js | 8 +- .../direct_dom_renderer_integration_spec.js | 213 --------- .../dom/dom_renderer_integration_spec.js | 154 ++++++ .../angular2/test/render/dom/dom_testbed.js | 126 +++++ .../test/render/dom/integration_testbed.js | 195 -------- .../render/dom/shadow_dom/content_tag_spec.js | 6 +- ...mulated_scoped_shadow_dom_strategy_spec.js | 11 +- ...lated_unscoped_shadow_dom_strategy_spec.js | 11 +- .../render/dom/shadow_dom/light_dom_spec.js | 29 +- .../native_shadow_dom_strategy_spec.js | 12 +- .../shadow_dom_emulation_integration_spec.js | 443 +++++++++--------- .../test/render/dom/view/view_factory_spec.js | 183 -------- .../render/dom/view/view_hydrator_spec.js | 285 ----------- .../test/render/dom/view/view_spec.js | 16 - .../src/components/dialog/dialog.js | 15 +- modules/benchmarks/src/tree/tree_benchmark.js | 2 - 51 files changed, 1242 insertions(+), 2228 deletions(-) delete mode 100644 modules/angular2/src/render/dom/direct_dom_renderer.js create mode 100644 modules/angular2/src/render/dom/dom_renderer.js delete mode 100644 modules/angular2/src/render/dom/view/view_factory.js delete mode 100644 modules/angular2/src/render/dom/view/view_hydrator.js delete mode 100644 modules/angular2/test/render/dom/direct_dom_renderer_integration_spec.js create mode 100644 modules/angular2/test/render/dom/dom_renderer_integration_spec.js create mode 100644 modules/angular2/test/render/dom/dom_testbed.js delete mode 100644 modules/angular2/test/render/dom/integration_testbed.js delete mode 100644 modules/angular2/test/render/dom/view/view_factory_spec.js delete mode 100644 modules/angular2/test/render/dom/view/view_hydrator_spec.js diff --git a/modules/angular2/angular2.js b/modules/angular2/angular2.js index 2d5354143a..facf3da73a 100644 --- a/modules/angular2/angular2.js +++ b/modules/angular2/angular2.js @@ -5,4 +5,4 @@ export * from './directives'; export * from './forms'; export {Observable, EventEmitter} from 'angular2/src/facade/async'; export * from 'angular2/src/render/api'; -export {DirectDomRenderer} from 'angular2/src/render/dom/direct_dom_renderer'; +export {DomRenderer, DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer'; diff --git a/modules/angular2/src/core/application.js b/modules/angular2/src/core/application.js index 7375842838..591f005caf 100644 --- a/modules/angular2/src/core/application.js +++ b/modules/angular2/src/core/application.js @@ -33,17 +33,14 @@ import {AppViewManager} from 'angular2/src/core/compiler/view_manager'; import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils'; import {ProtoViewFactory} from 'angular2/src/core/compiler/proto_view_factory'; import {Renderer, RenderCompiler} from 'angular2/src/render/api'; -import {DirectDomRenderer} from 'angular2/src/render/dom/direct_dom_renderer'; -import * as rc from 'angular2/src/render/dom/compiler/compiler'; -import * as rvf from 'angular2/src/render/dom/view/view_factory'; -import * as rvh from 'angular2/src/render/dom/view/view_hydrator'; +import {DomRenderer, DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer'; +import {resolveInternalDomView} from 'angular2/src/render/dom/view/view'; +import {DefaultDomCompiler} from 'angular2/src/render/dom/compiler/compiler'; import {internalView} from 'angular2/src/core/compiler/view_ref'; import { appComponentRefToken, - appElementToken, - appComponentAnnotatedTypeToken, - appDocumentToken, + appComponentAnnotatedTypeToken } from './application_tokens'; var _rootInjector: Injector; @@ -56,28 +53,25 @@ var _rootBindings = [ function _injectorBindings(appComponentType): List { return [ - bind(appDocumentToken).toValue(DOM.defaultDoc()), + bind(DOCUMENT_TOKEN).toValue(DOM.defaultDoc()), bind(appComponentAnnotatedTypeToken).toFactory((reader) => { // TODO(rado): investigate whether to support bindings on root component. return reader.read(appComponentType); }, [DirectiveMetadataReader]), - bind(appElementToken).toFactory((appComponentAnnotatedType, appDocument) => { - var selector = appComponentAnnotatedType.annotation.selector; - var element = DOM.querySelector(appDocument, selector); - if (isBlank(element)) { - throw new BaseException(`The app selector "${selector}" did not match any elements`); - } - return element; - }, [appComponentAnnotatedTypeToken, appDocumentToken]), - bind(appComponentRefToken).toAsyncFactory((dynamicComponentLoader, injector, appElement, + bind(appComponentRefToken).toAsyncFactory((dynamicComponentLoader, injector, appComponentAnnotatedType, testability, registry) => { - // We need to do this here to ensure that we create Testability and - // it's ready on the window for users. - registry.registerApplication(appElement, testability); - return dynamicComponentLoader.loadIntoNewLocation(appComponentAnnotatedType.type, null, appElement, injector); - }, [DynamicComponentLoader, Injector, appElementToken, appComponentAnnotatedTypeToken, + var selector = appComponentAnnotatedType.annotation.selector; + return dynamicComponentLoader.loadIntoNewLocation(appComponentAnnotatedType.type, null, selector, injector).then( (componentRef) => { + var domView = resolveInternalDomView(componentRef.hostView.render); + // We need to do this here to ensure that we create Testability and + // it's ready on the window for users. + registry.registerApplication(domView.boundElements[0], testability); + + return componentRef; + }); + }, [DynamicComponentLoader, Injector, appComponentAnnotatedTypeToken, Testability, TestabilityRegistry]), bind(appComponentType).toFactory((ref) => ref.instance, @@ -89,18 +83,16 @@ function _injectorBindings(appComponentType): List { }, [VmTurnZone]), bind(ShadowDomStrategy).toFactory( (styleUrlResolver, doc) => new EmulatedUnscopedShadowDomStrategy(styleUrlResolver, doc.head), - [StyleUrlResolver, appDocumentToken]), - DirectDomRenderer, - bind(Renderer).toClass(DirectDomRenderer), - bind(RenderCompiler).toClass(rc.DefaultDomCompiler), + [StyleUrlResolver, DOCUMENT_TOKEN]), // TODO(tbosch): We need an explicit factory here, as // we are getting errors in dart2js with mirrors... - bind(rvf.ViewFactory).toFactory( - (capacity, eventManager, shadowDomStrategy) => new rvf.ViewFactory(capacity, eventManager, shadowDomStrategy), - [rvf.VIEW_POOL_CAPACITY, EventManager, ShadowDomStrategy] + bind(DomRenderer).toFactory( + (eventManager, shadowDomStrategy, doc) => new DomRenderer(eventManager, shadowDomStrategy, doc), + [EventManager, ShadowDomStrategy, DOCUMENT_TOKEN] ), - bind(rvf.VIEW_POOL_CAPACITY).toValue(10000), - rvh.RenderViewHydrator, + DefaultDomCompiler, + bind(Renderer).toAlias(DomRenderer), + bind(RenderCompiler).toAlias(DefaultDomCompiler), ProtoViewFactory, // TODO(tbosch): We need an explicit factory here, as // we are getting errors in dart2js with mirrors... diff --git a/modules/angular2/src/core/application_tokens.js b/modules/angular2/src/core/application_tokens.js index 01acce0f70..8921b5509b 100644 --- a/modules/angular2/src/core/application_tokens.js +++ b/modules/angular2/src/core/application_tokens.js @@ -1,6 +1,4 @@ import {OpaqueToken} from 'angular2/di'; export var appComponentRefToken:OpaqueToken = new OpaqueToken('ComponentRef'); -export var appElementToken:OpaqueToken = new OpaqueToken('AppElement'); export var appComponentAnnotatedTypeToken:OpaqueToken = new OpaqueToken('AppComponentAnnotatedType'); -export var appDocumentToken:OpaqueToken = new OpaqueToken('AppDocument'); diff --git a/modules/angular2/src/core/compiler/compiler.js b/modules/angular2/src/core/compiler/compiler.js index 16bc783d3b..3aaaaa38f1 100644 --- a/modules/angular2/src/core/compiler/compiler.js +++ b/modules/angular2/src/core/compiler/compiler.js @@ -133,21 +133,14 @@ export class Compiler { if (isBlank(template)) { return null; } - if (isPresent(template.renderer)) { - var directives = []; - pvPromise = this._render.createImperativeComponentProtoView(template.renderer).then( (renderPv) => { - return this._compileNestedProtoViews(null, componentBinding, renderPv, directives, true); - }); - } else { - var directives = ListWrapper.map( - this._flattenDirectives(template), - (directive) => this._bindDirective(directive) - ); - var renderTemplate = this._buildRenderTemplate(component, template, directives); - pvPromise = this._render.compile(renderTemplate).then( (renderPv) => { - return this._compileNestedProtoViews(null, componentBinding, renderPv, directives, true); - }); - } + var directives = ListWrapper.map( + this._flattenDirectives(template), + (directive) => this._bindDirective(directive) + ); + var renderTemplate = this._buildRenderTemplate(component, template, directives); + pvPromise = this._render.compile(renderTemplate).then( (renderPv) => { + return this._compileNestedProtoViews(null, componentBinding, renderPv, directives, true); + }); MapWrapper.set(this._compiling, component, pvPromise); return pvPromise; @@ -187,14 +180,6 @@ export class Compiler { }); var protoViewDone = (_) => { - var childComponentRenderPvRefs = []; - ListWrapper.forEach(protoView.elementBinders, (eb) => { - if (isPresent(eb.componentDirective)) { - var componentPv = eb.nestedProtoView; - ListWrapper.push(childComponentRenderPvRefs, isPresent(componentPv) ? componentPv.render : null); - } - }); - this._render.mergeChildComponentProtoViews(protoView.render, childComponentRenderPvRefs); return protoView; }; if (nestedPVPromises.length > 0) { diff --git a/modules/angular2/src/core/compiler/dynamic_component_loader.js b/modules/angular2/src/core/compiler/dynamic_component_loader.js index 0fa7c8aaa6..5d41fbadf9 100644 --- a/modules/angular2/src/core/compiler/dynamic_component_loader.js +++ b/modules/angular2/src/core/compiler/dynamic_component_loader.js @@ -62,14 +62,14 @@ export class DynamicComponentLoader { } /** - * Loads a component in the element specified by elementOrSelector. The loaded component receives + * Loads a component in the element specified by elementSelector. The loaded component receives * injection normally as a hosted view. */ - loadIntoNewLocation(typeOrBinding, parentComponentLocation:ElementRef, elementOrSelector:any, + loadIntoNewLocation(typeOrBinding, parentComponentLocation:ElementRef, elementSelector:string, injector:Injector = null):Promise { return this._compiler.compileInHost(this._getBinding(typeOrBinding)).then(hostProtoViewRef => { var hostViewRef = this._viewManager.createInPlaceHostView( - parentComponentLocation, elementOrSelector, hostProtoViewRef, injector); + parentComponentLocation, elementSelector, hostProtoViewRef, injector); var newLocation = new ElementRef(hostViewRef, 0); var component = this._viewManager.getComponent(newLocation); diff --git a/modules/angular2/src/core/compiler/element_injector.js b/modules/angular2/src/core/compiler/element_injector.js index 5c4c1d0a9e..7784326d65 100644 --- a/modules/angular2/src/core/compiler/element_injector.js +++ b/modules/angular2/src/core/compiler/element_injector.js @@ -894,7 +894,7 @@ export class ElementInjector extends TreeNode { _getPreBuiltObjectByKeyId(keyId:int) { var staticKeys = StaticKeys.instance(); - if (keyId === staticKeys.viewManagerId) return this._preBuiltObjects.viewManagerId; + if (keyId === staticKeys.viewManagerId) return this._preBuiltObjects.viewManager; //TODO add other objects as needed return _undefined; diff --git a/modules/angular2/src/core/compiler/element_ref.js b/modules/angular2/src/core/compiler/element_ref.js index 81f9a98d97..96141a92d4 100644 --- a/modules/angular2/src/core/compiler/element_ref.js +++ b/modules/angular2/src/core/compiler/element_ref.js @@ -1,7 +1,7 @@ import {DOM} from 'angular2/src/dom/dom_adapter'; import {normalizeBlank} from 'angular2/src/facade/lang'; import {ViewRef} from './view_ref'; -import {DirectDomViewRef} from 'angular2/src/render/dom/direct_dom_renderer'; +import {resolveInternalDomView} from 'angular2/src/render/dom/view/view'; /** * @exportedAs angular2/view @@ -23,8 +23,7 @@ export class ElementRef { // We need a more general way to read/write to the DOM element // via a proper abstraction in the render layer get domElement() { - var renderViewRef:DirectDomViewRef = this.parentView.render; - return renderViewRef.delegate.boundElements[this.boundElementIndex]; + return resolveInternalDomView(this.parentView.render).boundElements[this.boundElementIndex]; } /** diff --git a/modules/angular2/src/core/compiler/view.js b/modules/angular2/src/core/compiler/view.js index ee7deca0d1..5551b26055 100644 --- a/modules/angular2/src/core/compiler/view.js +++ b/modules/angular2/src/core/compiler/view.js @@ -32,7 +32,7 @@ export class AppView { componentChildViews: List; /// Host views that were added by an imperative view. /// This is a dynamically growing / shrinking array. - imperativeHostViews: List; + inPlaceHostViews: List; viewContainers: List; preBuiltObjects: List; proto: AppProtoView; @@ -64,7 +64,7 @@ export class AppView { this.context = null; this.locals = new Locals(null, MapWrapper.clone(protoLocals)); //TODO optimize this this.renderer = renderer; - this.imperativeHostViews = []; + this.inPlaceHostViews = []; } init(changeDetector:ChangeDetector, elementInjectors:List, rootElementInjectors:List, diff --git a/modules/angular2/src/core/compiler/view_manager.js b/modules/angular2/src/core/compiler/view_manager.js index bf7b05df0f..fc8eb119ae 100644 --- a/modules/angular2/src/core/compiler/view_manager.js +++ b/modules/angular2/src/core/compiler/view_manager.js @@ -1,12 +1,11 @@ import {Injector, Binding} from 'angular2/di'; import {Injectable} from 'angular2/src/di/annotations_impl'; -import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src/facade/collection'; import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang'; 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, RenderViewContainerRef} from 'angular2/src/render/api'; +import {Renderer, RenderViewRef} from 'angular2/src/render/api'; import {AppViewManagerUtils} from './view_manager_utils'; import {AppViewPool} from './view_pool'; @@ -27,6 +26,12 @@ export class AppViewManager { this._utils = utils; } + getComponentView(hostLocation:ElementRef):ViewRef { + var hostView = internalView(hostLocation.parentView); + var boundElementIndex = hostLocation.boundElementIndex; + return new ViewRef(hostView.componentChildViews[boundElementIndex]); + } + getViewContainer(location:ElementRef):ViewContainerRef { var hostView = internalView(location.parentView); return hostView.elementInjectors[location.boundElementIndex].getViewContainerRef(); @@ -47,20 +52,18 @@ export class AppViewManager { if (!binder.hasDynamicComponent()) { throw new BaseException(`There is no dynamic component directive at element ${boundElementIndex}`) } - - var componentView = this._createViewRecurse(componentProtoView); - var renderViewRefs = this._renderer.createDynamicComponentView(hostView.render, boundElementIndex, componentProtoView.render); - componentView.render = renderViewRefs[0]; + var componentView = this._createPooledView(componentProtoView); + this._renderer.attachComponentView(hostView.render, boundElementIndex, componentView.render); this._utils.attachComponentView(hostView, boundElementIndex, componentView); this._utils.hydrateDynamicComponentInElementInjector(hostView, boundElementIndex, componentBinding, injector); this._utils.hydrateComponentView(hostView, boundElementIndex); - this._viewHydrateRecurse(componentView, renderViewRefs, 1); + this._viewHydrateRecurse(componentView); return new ViewRef(componentView); } createInPlaceHostView(parentComponentLocation:ElementRef, - hostElementSelector, hostProtoViewRef:ProtoViewRef, injector:Injector):ViewRef { + hostElementSelector:string, hostProtoViewRef:ProtoViewRef, injector:Injector):ViewRef { var hostProtoView = internalProtoView(hostProtoViewRef); var parentComponentHostView = null; var parentComponentBoundElementIndex = null; @@ -70,27 +73,22 @@ export class AppViewManager { parentComponentBoundElementIndex = parentComponentLocation.boundElementIndex; parentRenderViewRef = parentComponentHostView.componentChildViews[parentComponentBoundElementIndex].render; } - var hostView = this._createViewRecurse(hostProtoView); - var renderViewRefs = this._renderer.createInPlaceHostView(parentRenderViewRef, hostElementSelector, hostProtoView.render); - hostView.render = renderViewRefs[0]; + var hostRenderView = this._renderer.createInPlaceHostView(parentRenderViewRef, hostElementSelector, hostProtoView.render); + var hostView = this._utils.createView(hostProtoView, hostRenderView, this, this._renderer); + this._renderer.setEventDispatcher(hostView.render, hostView); + this._createViewRecurse(hostView) this._utils.attachAndHydrateInPlaceHostView(parentComponentHostView, parentComponentBoundElementIndex, hostView, injector); - this._viewHydrateRecurse(hostView, renderViewRefs, 1); + this._viewHydrateRecurse(hostView); return new ViewRef(hostView); } destroyInPlaceHostView(parentComponentLocation:ElementRef, hostViewRef:ViewRef) { var hostView = internalView(hostViewRef); var parentView = null; - var parentRenderViewRef = null; if (isPresent(parentComponentLocation)) { parentView = internalView(parentComponentLocation.parentView).componentChildViews[parentComponentLocation.boundElementIndex]; - parentRenderViewRef = parentView.render; } - var hostViewRenderRef = hostView.render; - this._viewDehydrateRecurse(hostView); - this._utils.detachInPlaceHostView(parentView, hostView); - this._renderer.destroyInPlaceHostView(parentRenderViewRef, hostViewRenderRef); - this._destroyView(hostView); + this._destroyInPlaceHostView(parentView, hostView); } createViewInContainer(viewContainerLocation:ElementRef, @@ -99,25 +97,19 @@ export class AppViewManager { var parentView = internalView(viewContainerLocation.parentView); var boundElementIndex = viewContainerLocation.boundElementIndex; - var view = this._createViewRecurse(protoView); - var renderViewRefs = this._renderer.createViewInContainer(this._getRenderViewContainerRef(parentView, boundElementIndex), atIndex, view.proto.render); - view.render = renderViewRefs[0]; + var view = this._createPooledView(protoView); + this._renderer.attachViewInContainer(parentView.render, boundElementIndex, atIndex, view.render); this._utils.attachViewInContainer(parentView, boundElementIndex, atIndex, view); this._utils.hydrateViewInContainer(parentView, boundElementIndex, atIndex, injector); - this._viewHydrateRecurse(view, renderViewRefs, 1); + this._viewHydrateRecurse(view); return new ViewRef(view); } destroyViewInContainer(viewContainerLocation:ElementRef, atIndex:number) { var parentView = internalView(viewContainerLocation.parentView); var boundElementIndex = viewContainerLocation.boundElementIndex; - var viewContainer = parentView.viewContainers[boundElementIndex]; - var view = viewContainer.views[atIndex]; - this._viewDehydrateRecurse(view); - this._utils.detachViewInContainer(parentView, boundElementIndex, atIndex); - this._renderer.destroyViewInContainer(this._getRenderViewContainerRef(parentView, boundElementIndex), atIndex); - this._destroyView(view); + this._destroyViewInContainer(parentView, boundElementIndex, atIndex); } attachViewInContainer(viewContainerLocation:ElementRef, atIndex:number, viewRef:ViewRef):ViewRef { @@ -125,7 +117,7 @@ export class AppViewManager { var parentView = internalView(viewContainerLocation.parentView); var boundElementIndex = viewContainerLocation.boundElementIndex; this._utils.attachViewInContainer(parentView, boundElementIndex, atIndex, view); - this._renderer.insertViewIntoContainer(this._getRenderViewContainerRef(parentView, boundElementIndex), atIndex, view.render); + this._renderer.attachViewInContainer(parentView.render, boundElementIndex, atIndex, view.render); return viewRef; } @@ -135,86 +127,105 @@ export class AppViewManager { var viewContainer = parentView.viewContainers[boundElementIndex]; var view = viewContainer.views[atIndex]; this._utils.detachViewInContainer(parentView, boundElementIndex, atIndex); - this._renderer.detachViewFromContainer(this._getRenderViewContainerRef(parentView, boundElementIndex), atIndex); + this._renderer.detachViewInContainer(parentView.render, boundElementIndex, atIndex, view.render); return new ViewRef(view); } - _getRenderViewContainerRef(parentView:viewModule.AppView, boundElementIndex:number) { - return new RenderViewContainerRef(parentView.render, boundElementIndex); - } - - _createViewRecurse(protoView:viewModule.AppProtoView) { + _createPooledView(protoView:viewModule.AppProtoView):viewModule.AppView { var view = this._viewPool.getView(protoView); if (isBlank(view)) { - view = this._utils.createView(protoView, this, this._renderer); - var binders = protoView.elementBinders; - for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) { - var binder = binders[binderIdx]; - if (binder.hasStaticComponent()) { - var childView = this._createViewRecurse(binder.nestedProtoView); - this._utils.attachComponentView(view, binderIdx, childView); - } - } + view = this._utils.createView(protoView, this._renderer.createView(protoView.render), this, this._renderer); + this._renderer.setEventDispatcher(view.render, view); + this._createViewRecurse(view); } return view; } - _destroyView(view:viewModule.AppView) { + _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.render, binderIdx, childView.render); + this._utils.attachComponentView(view, binderIdx, childView); + } + } + } + + _destroyPooledView(view:viewModule.AppView) { + // TODO: if the pool is full, call renderer.destroyView as well! this._viewPool.returnView(view); } + _destroyViewInContainer(parentView, boundElementIndex, atIndex:number) { + var viewContainer = parentView.viewContainers[boundElementIndex]; + var view = viewContainer.views[atIndex]; + this._viewDehydrateRecurse(view, false); + this._utils.detachViewInContainer(parentView, boundElementIndex, atIndex); + this._renderer.detachViewInContainer(parentView.render, boundElementIndex, atIndex, view.render); + this._destroyPooledView(view); + } + + _destroyComponentView(hostView, boundElementIndex, componentView) { + this._viewDehydrateRecurse(componentView, false); + this._renderer.detachComponentView(hostView.render, boundElementIndex, componentView.render); + this._utils.detachComponentView(hostView, boundElementIndex); + this._destroyPooledView(componentView); + } + + _destroyInPlaceHostView(parentView, hostView) { + var parentRenderViewRef = null; + if (isPresent(parentView)) { + parentRenderViewRef = parentView.render; + } + this._viewDehydrateRecurse(hostView, true); + this._utils.detachInPlaceHostView(parentView, hostView); + this._renderer.destroyInPlaceHostView(parentRenderViewRef, hostView.render); + // Note: Don't put the inplace host view into the view pool + // as it is depending on the element for which it was created. + } + _viewHydrateRecurse( - view:viewModule.AppView, - renderComponentViewRefs:List, - renderComponentIndex:number):number { - this._renderer.setEventDispatcher(view.render, view); + 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()) { - var childView = view.componentChildViews[i]; - childView.render = renderComponentViewRefs[renderComponentIndex++]; this._utils.hydrateComponentView(view, i); - renderComponentIndex = this._viewHydrateRecurse( - view.componentChildViews[i], - renderComponentViewRefs, - renderComponentIndex + this._viewHydrateRecurse( + view.componentChildViews[i] ); } } - return renderComponentIndex; } - _viewDehydrateRecurse(view:viewModule.AppView) { + _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)) { - this._viewDehydrateRecurse(componentView); - if (binders[i].hasDynamicComponent()) { - this._utils.detachComponentView(view, i); - this._destroyView(componentView); + if (binders[i].hasDynamicComponent() || forceDestroyComponents) { + this._destroyComponentView(view, i, componentView); + } else { + this._viewDehydrateRecurse(componentView, false); } } var vc = view.viewContainers[i]; if (isPresent(vc)) { for (var j = vc.views.length - 1; j >= 0; j--) { - var childView = vc.views[j]; - this._viewDehydrateRecurse(childView); - this._utils.detachViewInContainer(view, i, j); - this._destroyView(childView); + this._destroyViewInContainer(view, i, j); } } } - // imperativeHostViews - for (var i = 0; i < view.imperativeHostViews.length; i++) { - var hostView = view.imperativeHostViews[i]; - this._viewDehydrateRecurse(hostView); - this._utils.detachInPlaceHostView(view, hostView); - this._destroyView(hostView); + // inPlaceHostViews + for (var i = view.inPlaceHostViews.length-1; i>=0; i--) { + var hostView = view.inPlaceHostViews[i]; + this._destroyInPlaceHostView(view, hostView); } - view.render = null; } } diff --git a/modules/angular2/src/core/compiler/view_manager_utils.js b/modules/angular2/src/core/compiler/view_manager_utils.js index 7e64b7af8d..4a203acd13 100644 --- a/modules/angular2/src/core/compiler/view_manager_utils.js +++ b/modules/angular2/src/core/compiler/view_manager_utils.js @@ -8,6 +8,7 @@ import * as avmModule from './view_manager'; import {Renderer} from 'angular2/src/render/api'; import {BindingPropagationConfig, Locals} from 'angular2/change_detection'; import {DirectiveMetadataReader} from './directive_metadata_reader'; +import {RenderViewRef} from 'angular2/src/render/api'; @Injectable() export class AppViewManagerUtils { @@ -27,8 +28,11 @@ export class AppViewManagerUtils { } } - createView(protoView:viewModule.AppProtoView, viewManager:avmModule.AppViewManager, renderer:Renderer): viewModule.AppView { + createView(protoView:viewModule.AppProtoView, renderView:RenderViewRef, 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 changeDetector = protoView.protoChangeDetector.instantiate(view); var binders = protoView.elementBinders; @@ -96,7 +100,7 @@ export class AppViewManagerUtils { hostElementInjector = parentComponentHostView.elementInjectors[parentComponentBoundElementIndex]; var parentView = parentComponentHostView.componentChildViews[parentComponentBoundElementIndex]; parentView.changeDetector.addChild(hostView.changeDetector); - ListWrapper.push(parentView.imperativeHostViews, hostView); + ListWrapper.push(parentView.inPlaceHostViews, hostView); } this._hydrateView(hostView, injector, hostElementInjector, new Object(), null); } @@ -105,7 +109,7 @@ export class AppViewManagerUtils { hostView:viewModule.AppView) { if (isPresent(parentView)) { parentView.changeDetector.removeChild(hostView.changeDetector); - ListWrapper.remove(parentView.imperativeHostViews, hostView); + ListWrapper.remove(parentView.inPlaceHostViews, hostView); } } diff --git a/modules/angular2/src/render/api.js b/modules/angular2/src/render/api.js index 92e9404124..9e41578cfd 100644 --- a/modules/angular2/src/render/api.js +++ b/modules/angular2/src/render/api.js @@ -134,18 +134,11 @@ export class DirectiveMetadata { } // An opaque reference to a DomProtoView -export class RenderProtoViewRef {} +export class RenderProtoViewRef { +} // An opaque reference to a DomView -export class RenderViewRef {} - -export class RenderViewContainerRef { - view:RenderViewRef; - elementIndex:number; - constructor(view:RenderViewRef, elementIndex: number) { - this.view = view; - this.elementIndex = elementIndex; - } +export class RenderViewRef { } export class ViewDefinition { @@ -168,113 +161,101 @@ export class RenderCompiler { */ compileHost(componentId):Promise { return null; } - /** - * Creats a ProtoViewDto for a component that will use an imperative View using the given - * renderer. - * Note: Rigth now, the renderer argument is ignored, but will be used in the future to define - * a custom handler. - */ - createImperativeComponentProtoView(rendererId):Promise { return null; } - /** * Compiles a single DomProtoView. Non recursive so that * we don't need to serialize all possible components over the wire, * but only the needed ones based on previous calls. */ compile(template:ViewDefinition):Promise { return null; } - - /** - * Sets the preset nested components, - * which will be instantiated when this protoView is instantiated. - * Note: We can't create new ProtoViewRefs here as we need to support cycles / recursive components. - * @param {List} protoViewRefs - * DomProtoView for every element with a component in this protoView or in a view container's protoView - */ - mergeChildComponentProtoViews(protoViewRef:RenderProtoViewRef, componentProtoViewRefs:List) { return null; } } export class Renderer { - /** - * Creates a view and inserts it into a ViewContainer. - * @param {RenderViewContainerRef} viewContainerRef - * @param {RenderProtoViewRef} protoViewRef A RenderProtoViewRef of type ProtoViewDto.HOST_VIEW_TYPE or ProtoViewDto.EMBEDDED_VIEW_TYPE - * @param {number} atIndex - * @return {List} the view and all of its nested child component views - */ - createViewInContainer(vcRef:RenderViewContainerRef, atIndex:number, protoViewRef:RenderProtoViewRef):List { return null; } - - /** - * Destroys the view in the given ViewContainer - */ - destroyViewInContainer(vcRef:RenderViewContainerRef, atIndex:number):void {} - - /** - * Inserts a detached view into a viewContainer. - */ - insertViewIntoContainer(vcRef:RenderViewContainerRef, atIndex:number, view:RenderViewRef):void {} - - /** - * Detaches a view from a container so that it can be inserted later on - */ - detachViewFromContainer(vcRef:RenderViewContainerRef, atIndex:number):void {} - - /** - * Creates a view and - * installs it as a shadow view for an element. - * - * Note: only allowed if there is a dynamic component directive at this place. - * @param {RenderViewRef} hostView - * @param {number} elementIndex - * @param {RenderProtoViewRef} componentProtoViewRef A RenderProtoViewRef of type ProtoViewDto.COMPONENT_VIEW_TYPE - * @return {List} the view and all of its nested child component views - */ - createDynamicComponentView(hostViewRef:RenderViewRef, elementIndex:number, componentProtoViewRef:RenderProtoViewRef):List { return null; } - - /** - * Destroys the component view at the given index - * - * Note: only allowed if there is a dynamic component directive at this place. - */ - destroyDynamicComponentView(hostViewRef:RenderViewRef, elementIndex:number):void {} - /** * Creates a host view that includes the given element. - * @param {RenderViewRef} parentViewRef (might be null) - * @param {any} hostElementSelector element or css selector for the host element - * @param {RenderProtoViewRef} hostProtoView a RenderProtoViewRef of type ProtoViewDto.HOST_VIEW_TYPE - * @return {List} the view and all of its nested child component views + * @param {RenderViewRef} parentHostViewRef (might be null) + * @param {any} hostElementSelector css selector for the host element + * @param {RenderProtoViewRef} hostProtoViewRef a RenderProtoViewRef of type ProtoViewDto.HOST_VIEW_TYPE + * @return {RenderViewRef} the created view */ - createInPlaceHostView(parentViewRef:RenderViewRef, hostElementSelector, hostProtoViewRef:RenderProtoViewRef):List { return null; } + createInPlaceHostView(parentHostViewRef:RenderViewRef, hostElementSelector:string, hostProtoViewRef:RenderProtoViewRef):RenderViewRef { + return null; + } /** * Destroys the given host view in the given parent view. */ - destroyInPlaceHostView(parentViewRef:RenderViewRef, hostViewRef:RenderViewRef):void {} + destroyInPlaceHostView(parentHostViewRef:RenderViewRef, hostViewRef:RenderViewRef) { + } /** - * Sets a property on an element. + * Creates a regular view out of the given ProtoView + */ + createView(protoViewRef:RenderProtoViewRef):RenderViewRef { + return null; + } + + /** + * Destroys the given view after it has been dehydrated and detached + */ + destroyView(viewRef:RenderViewRef) { + } + + /** + * Attaches a componentView into the given hostView at the given element + */ + attachComponentView(hostViewRef:RenderViewRef, elementIndex:number, componentViewRef:RenderViewRef) { + } + + /** + * Detaches a componentView into the given hostView at the given element + */ + detachComponentView(hostViewRef:RenderViewRef, boundElementIndex:number, componentViewRef:RenderViewRef) { + } + + /** + * Attaches a view into a ViewContainer (in the given parentView at the given element) at the given index. + */ + attachViewInContainer(parentViewRef:RenderViewRef, boundElementIndex:number, 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(parentViewRef:RenderViewRef, boundElementIndex:number, atIndex:number, viewRef:RenderViewRef) { + } + + /** + * Hydrates a view after it has been attached. Hydration/dehydration is used for reusing views inside of the view pool. + */ + hydrateView(hviewRef:RenderViewRef) { + } + + /** + * Dehydrates a view after it has been attached. Hydration/dehydration is used for reusing views inside of the view pool. + */ + dehydrateView(viewRef:RenderViewRef) { + } + + /** + * Sets a porperty on an element. * Note: This will fail if the property was not mentioned previously as a host property - * in the View. + * in the ProtoView */ - setElementProperty(view:RenderViewRef, elementIndex:number, propertyName:string, propertyValue:any):void {} + setElementProperty(viewRef:RenderViewRef, elementIndex:number, propertyName:string, propertyValue:any):void { + } + + /* + * Sets the value of a text node. + */ + setText(viewRef:RenderViewRef, textNodeIndex:number, text:string):void { + } /** - * This will set the value for a text node. - * Note: This needs to be separate from setElementProperty as we don't have ElementBinders - * for text nodes in the DomProtoView either. + * Sets the dispatcher for all events of the given view */ - setText(view:RenderViewRef, textNodeIndex:number, text:string):void {} - - /** - * Sets the dispatcher for all events that have been defined in the template or in directives - * in the given view. - */ - setEventDispatcher(viewRef:RenderViewRef, dispatcher:any/*EventDispatcher*/):void {} - - /** - * To be called at the end of the VmTurn so the API can buffer calls - */ - flush():void {} + setEventDispatcher(viewRef:RenderViewRef, dispatcher:any/*api.EventDispatcher*/):void { + } } diff --git a/modules/angular2/src/render/dom/compiler/compiler.js b/modules/angular2/src/render/dom/compiler/compiler.js index 127603b4d1..3822272a7f 100644 --- a/modules/angular2/src/render/dom/compiler/compiler.js +++ b/modules/angular2/src/render/dom/compiler/compiler.js @@ -2,7 +2,6 @@ import {Injectable} from 'angular2/src/di/annotations_impl'; import {PromiseWrapper, Promise} from 'angular2/src/facade/async'; import {BaseException, isPresent} from 'angular2/src/facade/lang'; -import {ListWrapper} from 'angular2/src/facade/collection'; import {DOM} from 'angular2/src/dom/dom_adapter'; import {ViewDefinition, ProtoViewDto, DirectiveMetadata, RenderCompiler, RenderProtoViewRef} from '../../api'; @@ -11,13 +10,6 @@ import {TemplateLoader} from 'angular2/src/render/dom/compiler/template_loader'; import {CompileStepFactory, DefaultStepFactory} from './compile_step_factory'; import {Parser} from 'angular2/change_detection'; import {ShadowDomStrategy} from '../shadow_dom/shadow_dom_strategy'; -import {ProtoViewBuilder} from '../view/proto_view_builder'; - -import {DirectDomProtoViewRef} from '../direct_dom_renderer'; - -function _resolveProtoView(protoViewRef:DirectDomProtoViewRef) { - return isPresent(protoViewRef) ? protoViewRef.delegate : null; -} /** * The compiler loads and translates the html templates of components into @@ -53,18 +45,6 @@ export class DomCompiler extends RenderCompiler { return this._compileTemplate(hostViewDef, element); } - createImperativeComponentProtoView(rendererId):Promise { - var protoViewBuilder = new ProtoViewBuilder(null); - protoViewBuilder.setImperativeRendererId(rendererId); - return PromiseWrapper.resolve(protoViewBuilder.build()); - } - - mergeChildComponentProtoViews(protoViewRef:RenderProtoViewRef, protoViewRefs:List) { - _resolveProtoView(protoViewRef).mergeChildComponentProtoViews( - ListWrapper.map(protoViewRefs, _resolveProtoView) - ); - } - _compileTemplate(viewDef: ViewDefinition, tplElement):Promise { var subTaskPromises = []; var pipeline = new CompilePipeline(this._stepFactory.createSteps(viewDef, subTaskPromises)); diff --git a/modules/angular2/src/render/dom/direct_dom_renderer.js b/modules/angular2/src/render/dom/direct_dom_renderer.js deleted file mode 100644 index 25c70cb589..0000000000 --- a/modules/angular2/src/render/dom/direct_dom_renderer.js +++ /dev/null @@ -1,158 +0,0 @@ -import {Injectable} from 'angular2/src/di/annotations_impl'; - -import {List, ListWrapper} from 'angular2/src/facade/collection'; -import {isBlank, isPresent, BaseException} from 'angular2/src/facade/lang'; - -import * as api from '../api'; -import {DomView} from './view/view'; -import {DomProtoView} from './view/proto_view'; -import {ViewFactory} from './view/view_factory'; -import {RenderViewHydrator} from './view/view_hydrator'; -import {ShadowDomStrategy} from './shadow_dom/shadow_dom_strategy'; -import {ViewContainer} from './view/view_container'; - -function _resolveViewContainer(vc:api.RenderViewContainerRef) { - return _resolveView(vc.view).getOrCreateViewContainer(vc.elementIndex); -} - -function _resolveView(viewRef:DirectDomViewRef) { - return isPresent(viewRef) ? viewRef.delegate : null; -} - -function _resolveProtoView(protoViewRef:DirectDomProtoViewRef) { - return isPresent(protoViewRef) ? protoViewRef.delegate : null; -} - -function _wrapView(view:DomView) { - return new DirectDomViewRef(view); -} - -function _collectComponentChildViewRefs(view, target = null) { - if (isBlank(target)) { - target = []; - } - ListWrapper.push(target, _wrapView(view)); - ListWrapper.forEach(view.componentChildViews, (view) => { - if (isPresent(view)) { - _collectComponentChildViewRefs(view, target); - } - }); - return target; -} - - - -// public so that the compiler can use it. -export class DirectDomProtoViewRef extends api.RenderProtoViewRef { - delegate:DomProtoView; - - constructor(delegate:DomProtoView) { - super(); - this.delegate = delegate; - } -} - -export class DirectDomViewRef extends api.RenderViewRef { - delegate:DomView; - - constructor(delegate:DomView) { - super(); - this.delegate = delegate; - } -} - -@Injectable() -export class DirectDomRenderer extends api.Renderer { - _viewFactory: ViewFactory; - _viewHydrator: RenderViewHydrator; - _shadowDomStrategy: ShadowDomStrategy; - - constructor( - viewFactory: ViewFactory, viewHydrator: RenderViewHydrator, shadowDomStrategy: ShadowDomStrategy) { - super(); - this._viewFactory = viewFactory; - this._viewHydrator = viewHydrator; - this._shadowDomStrategy = shadowDomStrategy; - } - - createViewInContainer(vcRef:api.RenderViewContainerRef, atIndex:number, protoViewRef:api.RenderProtoViewRef):List { - var view = this._viewFactory.getView(_resolveProtoView(protoViewRef)); - var vc = _resolveViewContainer(vcRef); - this._viewHydrator.hydrateViewInViewContainer(vc, view); - vc.insert(view, atIndex); - return _collectComponentChildViewRefs(view); - } - - destroyViewInContainer(vcRef:api.RenderViewContainerRef, atIndex:number):void { - var vc = _resolveViewContainer(vcRef); - var view = vc.detach(atIndex); - this._viewHydrator.dehydrateViewInViewContainer(vc, view); - this._viewFactory.returnView(view); - } - - insertViewIntoContainer(vcRef:api.RenderViewContainerRef, atIndex=-1, viewRef:api.RenderViewRef):void { - _resolveViewContainer(vcRef).insert(_resolveView(viewRef), atIndex); - } - - detachViewFromContainer(vcRef:api.RenderViewContainerRef, atIndex:number):void { - _resolveViewContainer(vcRef).detach(atIndex); - } - - createDynamicComponentView(hostViewRef:api.RenderViewRef, elementIndex:number, componentViewRef:api.RenderProtoViewRef):List { - var hostView = _resolveView(hostViewRef); - var componentView = this._viewFactory.getView(_resolveProtoView(componentViewRef)); - this._viewHydrator.hydrateDynamicComponentView(hostView, elementIndex, componentView); - return _collectComponentChildViewRefs(componentView); - } - - destroyDynamicComponentView(hostViewRef:api.RenderViewRef, elementIndex:number):void { - throw new BaseException('Not supported yet'); - // Something along these lines: - // var hostView = _resolveView(hostViewRef); - // var componentView = hostView.childComponentViews[elementIndex]; - // this._viewHydrator.dehydrateDynamicComponentView(hostView, componentView); - } - - createInPlaceHostView(parentViewRef:api.RenderViewRef, hostElementSelector, hostProtoViewRef:api.RenderProtoViewRef):List { - var parentView = _resolveView(parentViewRef); - var hostView = this._viewFactory.createInPlaceHostView(hostElementSelector, _resolveProtoView(hostProtoViewRef)); - this._viewHydrator.hydrateInPlaceHostView(parentView, hostView); - return _collectComponentChildViewRefs(hostView); - } - - /** - * Destroys the given host view in the given parent view. - */ - destroyInPlaceHostView(parentViewRef:api.RenderViewRef, hostViewRef:api.RenderViewRef):void { - var parentView = _resolveView(parentViewRef); - var hostView = _resolveView(hostViewRef); - this._viewHydrator.dehydrateInPlaceHostView(parentView, hostView); - } - - setImperativeComponentRootNodes(parentViewRef:api.RenderViewRef, elementIndex:number, nodes:List):void { - var parentView = _resolveView(parentViewRef); - var hostElement = parentView.boundElements[elementIndex]; - var componentView = parentView.componentChildViews[elementIndex]; - if (isBlank(componentView)) { - throw new BaseException(`There is no componentChildView at index ${elementIndex}`); - } - if (isBlank(componentView.proto.imperativeRendererId)) { - throw new BaseException(`This component view has no imperative renderer`); - } - ViewContainer.removeViewNodes(componentView); - componentView.rootNodes = nodes; - this._shadowDomStrategy.attachTemplate(hostElement, componentView); - } - - setElementProperty(viewRef:api.RenderViewRef, elementIndex:number, propertyName:string, propertyValue:any):void { - _resolveView(viewRef).setElementProperty(elementIndex, propertyName, propertyValue); - } - - setText(viewRef:api.RenderViewRef, textNodeIndex:number, text:string):void { - _resolveView(viewRef).setText(textNodeIndex, text); - } - - setEventDispatcher(viewRef:api.RenderViewRef, dispatcher:any/*api.EventDispatcher*/):void { - _resolveView(viewRef).setEventDispatcher(dispatcher); - } -} diff --git a/modules/angular2/src/render/dom/dom_renderer.js b/modules/angular2/src/render/dom/dom_renderer.js new file mode 100644 index 0000000000..921b88a142 --- /dev/null +++ b/modules/angular2/src/render/dom/dom_renderer.js @@ -0,0 +1,338 @@ +import {Inject, Injectable} from 'angular2/src/di/annotations_impl'; +import {int, isPresent, isBlank, BaseException, RegExpWrapper} from 'angular2/src/facade/lang'; +import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src/facade/collection'; + +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 {DomViewContainer} from './view/view_container'; +import {NG_BINDING_CLASS_SELECTOR, NG_BINDING_CLASS} from './util'; + +import {Renderer, RenderProtoViewRef, RenderViewRef} from '../api'; + +// TODO(tbosch): use an OpaqueToken here once our transpiler supports +// const expressions! +export const DOCUMENT_TOKEN = 'DocumentToken'; + +var _DOCUMENT_SELECTOR_REGEX = RegExpWrapper.create('\\:document(.+)'); + +@Injectable() +export class DomRenderer extends Renderer { + _eventManager:EventManager; + _shadowDomStrategy:ShadowDomStrategy; + _document; + + constructor(eventManager:EventManager, shadowDomStrategy:ShadowDomStrategy, @Inject(DOCUMENT_TOKEN) document) { + super(); + this._eventManager = eventManager; + this._shadowDomStrategy = shadowDomStrategy; + this._document = document; + } + + createInPlaceHostView(parentHostViewRef:RenderViewRef, hostElementSelector:string, hostProtoViewRef:RenderProtoViewRef):RenderViewRef { + var containerNode; + var documentSelectorMatch = RegExpWrapper.firstMatch(_DOCUMENT_SELECTOR_REGEX, hostElementSelector); + if (isPresent(documentSelectorMatch)) { + containerNode = this._document; + hostElementSelector = documentSelectorMatch[1]; + } else if (isPresent(parentHostViewRef)) { + var parentHostView = resolveInternalDomView(parentHostViewRef); + containerNode = parentHostView.shadowRoot; + } else { + containerNode = this._document; + } + var element = DOM.querySelector(containerNode, hostElementSelector); + if (isBlank(element)) { + throw new BaseException(`The selector "${hostElementSelector}" did not match any elements`); + } + var hostProtoView = resolveInternalDomProtoView(hostProtoViewRef); + return new DomViewRef(this._createView(hostProtoView, element)); + } + + destroyInPlaceHostView(parentHostViewRef:RenderViewRef, hostViewRef:RenderViewRef) { + var hostView = resolveInternalDomView(hostViewRef); + this._removeViewNodes(hostView); + } + + createView(protoViewRef:RenderProtoViewRef):RenderViewRef { + var protoView = resolveInternalDomProtoView(protoViewRef); + return new DomViewRef(this._createView(protoView, null)); + } + + destroyView(view:RenderViewRef) { + // noop for now + } + + attachComponentView(hostViewRef:RenderViewRef, elementIndex:number, componentViewRef:RenderViewRef) { + var hostView = resolveInternalDomView(hostViewRef); + var componentView = resolveInternalDomView(componentViewRef); + var element = hostView.boundElements[elementIndex]; + var lightDom = hostView.lightDoms[elementIndex]; + if (isPresent(lightDom)) { + lightDom.attachShadowDomView(componentView); + } + var shadowRoot = this._shadowDomStrategy.prepareShadowRoot(element); + this._moveViewNodesIntoParent(shadowRoot, componentView); + componentView.hostLightDom = lightDom; + componentView.shadowRoot = shadowRoot; + } + + setComponentViewRootNodes(componentViewRef:RenderViewRef, rootNodes:List) { + var componentView = resolveInternalDomView(componentViewRef); + this._removeViewNodes(componentView); + componentView.rootNodes = rootNodes; + this._moveViewNodesIntoParent(componentView.shadowRoot, componentView); + } + + detachComponentView(hostViewRef:RenderViewRef, boundElementIndex:number, componentViewRef:RenderViewRef) { + var hostView = resolveInternalDomView(hostViewRef); + var componentView = resolveInternalDomView(componentViewRef); + this._removeViewNodes(componentView); + var lightDom = hostView.lightDoms[boundElementIndex]; + if (isPresent(lightDom)) { + lightDom.detachShadowDomView(); + } + componentView.hostLightDom = null; + componentView.shadowRoot = null; + } + + attachViewInContainer(parentViewRef:RenderViewRef, boundElementIndex:number, atIndex:number, viewRef:RenderViewRef) { + var parentView = resolveInternalDomView(parentViewRef); + var view = resolveInternalDomView(viewRef); + var viewContainer = this._getOrCreateViewContainer(parentView, boundElementIndex); + ListWrapper.insert(viewContainer.views, atIndex, view); + view.hostLightDom = parentView.hostLightDom; + + var directParentLightDom = parentView.getDirectParentLightDom(boundElementIndex); + if (isBlank(directParentLightDom)) { + var siblingToInsertAfter; + if (atIndex == 0) { + siblingToInsertAfter = parentView.boundElements[boundElementIndex]; + } 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(parentViewRef:RenderViewRef, boundElementIndex:number, atIndex:number, viewRef:RenderViewRef) { + var parentView = resolveInternalDomView(parentViewRef); + var view = resolveInternalDomView(viewRef); + var viewContainer = parentView.viewContainers[boundElementIndex]; + var detachedView = viewContainer.views[atIndex]; + ListWrapper.removeAt(viewContainer.views, atIndex); + var directParentLightDom = parentView.getDirectParentLightDom(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(); + } + } + + hydrateView(viewRef:RenderViewRef) { + var view = resolveInternalDomView(viewRef); + if (view.hydrated) throw new BaseException('The view is already hydrated.'); + view.hydrated = true; + + for (var i = 0; i < view.lightDoms.length; ++i) { + var lightDom = view.lightDoms[i]; + if (isPresent(lightDom)) { + lightDom.redistribute(); + } + } + + //add global events + view.eventHandlerRemovers = ListWrapper.create(); + var binders = view.proto.elementBinders; + for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) { + var binder = binders[binderIdx]; + if (isPresent(binder.globalEvents)) { + for (var i = 0; i < binder.globalEvents.length; i++) { + var globalEvent = binder.globalEvents[i]; + var remover = this._createGlobalEventListener(view, binderIdx, globalEvent.name, globalEvent.target, globalEvent.fullName); + ListWrapper.push(view.eventHandlerRemovers, remover); + } + } + } + if (isPresent(view.hostLightDom)) { + view.hostLightDom.redistribute(); + } + } + + dehydrateView(viewRef:RenderViewRef) { + var view = resolveInternalDomView(viewRef); + + //remove global events + for (var i = 0; i < view.eventHandlerRemovers.length; i++) { + view.eventHandlerRemovers[i](); + } + + view.eventHandlerRemovers = null; + view.hydrated = false; + } + + setElementProperty(viewRef:RenderViewRef, elementIndex:number, propertyName:string, propertyValue:any):void { + var view = resolveInternalDomView(viewRef); + view.setElementProperty(elementIndex, propertyName, propertyValue); + } + + setText(viewRef:RenderViewRef, textNodeIndex:number, text:string):void { + var view = resolveInternalDomView(viewRef); + DOM.setText(view.boundTextNodes[textNodeIndex], text); + } + + setEventDispatcher(viewRef:RenderViewRef, dispatcher:any/*api.EventDispatcher*/):void { + var view = resolveInternalDomView(viewRef); + view.eventDispatcher = dispatcher; + } + + _createView(protoView:DomProtoView, inplaceElement): DomView { + var rootElementClone = isPresent(inplaceElement) ? inplaceElement : DOM.importIntoDoc(protoView.element); + var elementsWithBindingsDynamic; + if (protoView.isTemplateElement) { + elementsWithBindingsDynamic = DOM.querySelectorAll(DOM.content(rootElementClone), NG_BINDING_CLASS_SELECTOR); + } else { + elementsWithBindingsDynamic = DOM.getElementsByClassName(rootElementClone, NG_BINDING_CLASS); + } + + var elementsWithBindings = ListWrapper.createFixedSize(elementsWithBindingsDynamic.length); + for (var binderIdx = 0; binderIdx < elementsWithBindingsDynamic.length; ++binderIdx) { + elementsWithBindings[binderIdx] = elementsWithBindingsDynamic[binderIdx]; + } + + var viewRootNodes; + if (protoView.isTemplateElement) { + var childNode = DOM.firstChild(DOM.content(rootElementClone)); + viewRootNodes = []; // TODO(perf): Should be fixed size, since we could pre-compute in in DomProtoView + // Note: An explicit loop is the fastest way to convert a DOM array into a JS array! + while(childNode != null) { + ListWrapper.push(viewRootNodes, childNode); + childNode = DOM.nextSibling(childNode); + } + } else { + viewRootNodes = [rootElementClone]; + } + var binders = protoView.elementBinders; + var boundTextNodes = []; + var boundElements = ListWrapper.createFixedSize(binders.length); + var contentTags = ListWrapper.createFixedSize(binders.length); + + for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) { + var binder = binders[binderIdx]; + var element; + if (binderIdx === 0 && protoView.rootBindingOffset === 1) { + element = rootElementClone; + } else { + element = elementsWithBindings[binderIdx - protoView.rootBindingOffset]; + } + boundElements[binderIdx] = element; + + // boundTextNodes + var childNodes = DOM.childNodes(DOM.templateAwareRoot(element)); + var textNodeIndices = binder.textNodeIndices; + for (var i = 0; i { + 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 vc = parentView.viewContainers[boundElementIndex]; + if (isBlank(vc)) { + vc = new DomViewContainer(); + parentView.viewContainers[boundElementIndex] = vc; + } + return vc; + } + + _createGlobalEventListener(view, elementIndex, eventName, eventTarget, fullName): Function { + return this._eventManager.addGlobalEventListener(eventTarget, eventName, (event) => { + view.dispatchEvent(elementIndex, fullName, event); + }); + } + +} diff --git a/modules/angular2/src/render/dom/shadow_dom/content_tag.js b/modules/angular2/src/render/dom/shadow_dom/content_tag.js index ee140ebd73..2621aa818c 100644 --- a/modules/angular2/src/render/dom/shadow_dom/content_tag.js +++ b/modules/angular2/src/render/dom/shadow_dom/content_tag.js @@ -74,16 +74,12 @@ export class Content { this._strategy = null; } - hydrate(destinationLightDom:ldModule.LightDom) { + init(destinationLightDom:ldModule.LightDom) { this._strategy = isPresent(destinationLightDom) ? new IntermediateContent(destinationLightDom) : new RenderedContent(this.contentStartElement); } - dehydrate() { - this._strategy = null; - } - nodes():List { return this._strategy.nodes; } diff --git a/modules/angular2/src/render/dom/shadow_dom/emulated_unscoped_shadow_dom_strategy.js b/modules/angular2/src/render/dom/shadow_dom/emulated_unscoped_shadow_dom_strategy.js index 82c2194951..e9d8b35834 100644 --- a/modules/angular2/src/render/dom/shadow_dom/emulated_unscoped_shadow_dom_strategy.js +++ b/modules/angular2/src/render/dom/shadow_dom/emulated_unscoped_shadow_dom_strategy.js @@ -7,7 +7,6 @@ import * as viewModule from '../view/view'; import {LightDom} from './light_dom'; import {ShadowDomStrategy} from './shadow_dom_strategy'; import {StyleUrlResolver} from './style_url_resolver'; -import {moveViewNodesIntoParent} from './util'; import {insertSharedStyleText} from './util'; /** @@ -33,12 +32,12 @@ export class EmulatedUnscopedShadowDomStrategy extends ShadowDomStrategy { return false; } - attachTemplate(el, view:viewModule.DomView) { - moveViewNodesIntoParent(el, view); + prepareShadowRoot(el) { + return el; } - constructLightDom(lightDomView:viewModule.DomView, shadowDomView:viewModule.DomView, el): LightDom { - return new LightDom(lightDomView, shadowDomView, el); + constructLightDom(lightDomView:viewModule.DomView, el): LightDom { + return new LightDom(lightDomView, el); } processStyleElement(hostComponentId:string, templateUrl:string, styleEl):Promise { diff --git a/modules/angular2/src/render/dom/shadow_dom/light_dom.js b/modules/angular2/src/render/dom/shadow_dom/light_dom.js index f6d5a40198..228eefe9a2 100644 --- a/modules/angular2/src/render/dom/shadow_dom/light_dom.js +++ b/modules/angular2/src/render/dom/shadow_dom/light_dom.js @@ -28,12 +28,20 @@ export class LightDom { nodes:List; roots:List<_Root>; - constructor(lightDomView:viewModule.DomView, shadowDomView:viewModule.DomView, element) { + constructor(lightDomView:viewModule.DomView, element) { this.lightDomView = lightDomView; - this.shadowDomView = shadowDomView; this.nodes = DOM.childNodesAsList(element); this.roots = null; + this.shadowDomView = null; + } + + attachShadowDomView(shadowDomView:viewModule.DomView) { + this.shadowDomView = shadowDomView; + } + + detachShadowDomView() { + this.shadowDomView = null; } redistribute() { @@ -41,7 +49,11 @@ export class LightDom { } contentTags(): List { - return this._collectAllContentTags(this.shadowDomView, []); + if (isPresent(this.shadowDomView)) { + return this._collectAllContentTags(this.shadowDomView, []); + } else { + return []; + } } // Collects the Content directives from the view and all its child views diff --git a/modules/angular2/src/render/dom/shadow_dom/native_shadow_dom_strategy.js b/modules/angular2/src/render/dom/shadow_dom/native_shadow_dom_strategy.js index 5d2907c5f5..6e0d26cbbe 100644 --- a/modules/angular2/src/render/dom/shadow_dom/native_shadow_dom_strategy.js +++ b/modules/angular2/src/render/dom/shadow_dom/native_shadow_dom_strategy.js @@ -2,11 +2,8 @@ import {Promise} from 'angular2/src/facade/async'; import {DOM} from 'angular2/src/dom/dom_adapter'; -import * as viewModule from '../view/view'; - import {StyleUrlResolver} from './style_url_resolver'; import {ShadowDomStrategy} from './shadow_dom_strategy'; -import {moveViewNodesIntoParent} from './util'; /** * This strategies uses the native Shadow DOM support. @@ -22,8 +19,8 @@ export class NativeShadowDomStrategy extends ShadowDomStrategy { this.styleUrlResolver = styleUrlResolver; } - attachTemplate(el, view:viewModule.DomView){ - moveViewNodesIntoParent(DOM.createShadowRoot(el), view); + prepareShadowRoot(el) { + return DOM.createShadowRoot(el); } processStyleElement(hostComponentId:string, templateUrl:string, styleEl):Promise { diff --git a/modules/angular2/src/render/dom/shadow_dom/shadow_dom_strategy.js b/modules/angular2/src/render/dom/shadow_dom/shadow_dom_strategy.js index a379473e46..959a81284e 100644 --- a/modules/angular2/src/render/dom/shadow_dom/shadow_dom_strategy.js +++ b/modules/angular2/src/render/dom/shadow_dom/shadow_dom_strategy.js @@ -9,9 +9,14 @@ export class ShadowDomStrategy { return true; } - attachTemplate(el, view:viewModule.DomView) {} + /** + * Prepares and returns the shadow root for the given element. + */ + prepareShadowRoot(el):any { + return null; + } - constructLightDom(lightDomView:viewModule.DomView, shadowDomView:viewModule.DomView, el): LightDom { + constructLightDom(lightDomView:viewModule.DomView, el): LightDom { return null; } diff --git a/modules/angular2/src/render/dom/shadow_dom/util.js b/modules/angular2/src/render/dom/shadow_dom/util.js index a34d9e13e2..708a0c8aa5 100644 --- a/modules/angular2/src/render/dom/shadow_dom/util.js +++ b/modules/angular2/src/render/dom/shadow_dom/util.js @@ -5,12 +5,6 @@ import {DOM} from 'angular2/src/dom/dom_adapter'; import {ShadowCss} from './shadow_css'; -export function moveViewNodesIntoParent(parent, view) { - for (var i = 0; i < view.rootNodes.length; ++i) { - DOM.appendChild(parent, view.rootNodes[i]); - } -} - var _componentUIDs: Map = MapWrapper.create(); var _nextComponentUID: int = 0; var _sharedStyleTexts: Map = MapWrapper.create(); diff --git a/modules/angular2/src/render/dom/view/element_binder.js b/modules/angular2/src/render/dom/view/element_binder.js index 87b67ba4c2..d07a5b0c13 100644 --- a/modules/angular2/src/render/dom/view/element_binder.js +++ b/modules/angular2/src/render/dom/view/element_binder.js @@ -1,4 +1,3 @@ -import {isBlank, isPresent} from 'angular2/src/facade/lang'; import {AST} from 'angular2/change_detection'; import {SetterFn} from 'angular2/src/reflection/types'; import {List, ListWrapper} from 'angular2/src/facade/collection'; @@ -39,14 +38,6 @@ export class ElementBinder { this.distanceToParent = distanceToParent; this.propertySetters = propertySetters; } - - hasStaticComponent() { - return isPresent(this.componentId) && isPresent(this.nestedProtoView); - } - - hasDynamicComponent() { - return isPresent(this.componentId) && isBlank(this.nestedProtoView); - } } export class Event { diff --git a/modules/angular2/src/render/dom/view/proto_view.js b/modules/angular2/src/render/dom/view/proto_view.js index 882c56c054..324940c33e 100644 --- a/modules/angular2/src/render/dom/view/proto_view.js +++ b/modules/angular2/src/render/dom/view/proto_view.js @@ -1,43 +1,39 @@ import {isPresent} from 'angular2/src/facade/lang'; import {DOM} from 'angular2/src/dom/dom_adapter'; -import {List, Map, ListWrapper, MapWrapper} from 'angular2/src/facade/collection'; +import {List} from 'angular2/src/facade/collection'; import {ElementBinder} from './element_binder'; import {NG_BINDING_CLASS} from '../util'; +import {RenderProtoViewRef} from '../../api'; + +export function resolveInternalDomProtoView(protoViewRef:RenderProtoViewRef) { + var domProtoViewRef:DomProtoViewRef = protoViewRef; + return domProtoViewRef._protoView; +} + +export class DomProtoViewRef extends RenderProtoViewRef { + _protoView:DomProtoView; + constructor(protoView:DomProtoView) { + super(); + this._protoView = protoView; + } +} + export class DomProtoView { element; elementBinders:List; isTemplateElement:boolean; rootBindingOffset:int; - imperativeRendererId:string; constructor({ elementBinders, - element, - imperativeRendererId + element }) { this.element = element; this.elementBinders = elementBinders; - this.imperativeRendererId = imperativeRendererId; - if (isPresent(imperativeRendererId)) { - this.rootBindingOffset = 0; - this.isTemplateElement = false; - } else { - this.isTemplateElement = DOM.isTemplateElement(this.element); - this.rootBindingOffset = (isPresent(this.element) && DOM.hasClass(this.element, NG_BINDING_CLASS)) ? 1 : 0; - } - } - - mergeChildComponentProtoViews(componentProtoViews:List) { - var componentProtoViewIndex = 0; - for (var i=0; i; elements:List; - imperativeRendererId:string; constructor(rootElement) { this.rootElement = rootElement; this.elements = []; this.variableBindings = MapWrapper.create(); - this.imperativeRendererId = null; - } - - setImperativeRendererId(id:string):ProtoViewBuilder { - this.imperativeRendererId = id; - return this; } bindElement(element, description = null):ElementBinderBuilder { @@ -93,7 +85,7 @@ export class ProtoViewBuilder { contentTagSelector: ebb.contentTagSelector, parentIndex: parentIndex, distanceToParent: ebb.distanceToParent, - nestedProtoView: isPresent(nestedProtoView) ? nestedProtoView.render.delegate : null, + nestedProtoView: isPresent(nestedProtoView) ? resolveInternalDomProtoView(nestedProtoView.render) : null, componentId: ebb.componentId, eventLocals: new LiteralArray(ebb.eventBuilder.buildEventLocals()), localEvents: ebb.eventBuilder.buildLocalEvents(), @@ -102,10 +94,9 @@ export class ProtoViewBuilder { })); }); return new api.ProtoViewDto({ - render: new directDomRenderer.DirectDomProtoViewRef(new DomProtoView({ + render: new DomProtoViewRef(new DomProtoView({ element: this.rootElement, - elementBinders: renderElementBinders, - imperativeRendererId: this.imperativeRendererId + elementBinders: renderElementBinders })), elementBinders: apiElementBinders, variableBindings: this.variableBindings diff --git a/modules/angular2/src/render/dom/view/view.js b/modules/angular2/src/render/dom/view/view.js index 1f8061d134..bba576cd3e 100644 --- a/modules/angular2/src/render/dom/view/view.js +++ b/modules/angular2/src/render/dom/view/view.js @@ -2,13 +2,30 @@ import {DOM} from 'angular2/src/dom/dom_adapter'; import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src/facade/collection'; import {int, isPresent, isBlank, BaseException} from 'angular2/src/facade/lang'; -import {ViewContainer} from './view_container'; +import {DomViewContainer} from './view_container'; import {DomProtoView} from './proto_view'; import {LightDom} from '../shadow_dom/light_dom'; import {Content} from '../shadow_dom/content_tag'; +import {RenderViewRef} from '../../api'; + +// TODO(tbosch): enable this again! // import {EventDispatcher} from '../../api'; +export function resolveInternalDomView(viewRef:RenderViewRef) { + var domViewRef:DomViewRef = viewRef; + return domViewRef._view; +} + +export class DomViewRef extends RenderViewRef { + _view:DomView; + constructor(view:DomView) { + super(); + this._view = view; + } +} + + const NG_BINDING_CLASS = 'ng-binding'; /** @@ -22,18 +39,15 @@ export class DomView { rootNodes:List; // TODO(tbosch): move componentChildViews, viewContainers, contentTags, lightDoms into // a single array with records inside - componentChildViews: List; - viewContainers: List; + viewContainers: List; contentTags: List; lightDoms: List; hostLightDom: LightDom; + shadowRoot; proto: DomProtoView; hydrated: boolean; - _eventDispatcher: any/*EventDispatcher*/; + eventDispatcher: any/*EventDispatcher*/; eventHandlerRemovers: List; - /// Host views that were added by an imperative view. - /// This is a dynamically growing / shrinking array. - imperativeHostViews: List; constructor( proto:DomProtoView, rootNodes:List, @@ -45,12 +59,11 @@ export class DomView { this.viewContainers = ListWrapper.createFixedSize(boundElements.length); this.contentTags = contentTags; this.lightDoms = ListWrapper.createFixedSize(boundElements.length); - ListWrapper.fill(this.lightDoms, null); - this.componentChildViews = ListWrapper.createFixedSize(boundElements.length); this.hostLightDom = null; this.hydrated = false; this.eventHandlerRemovers = []; - this.imperativeHostViews = []; + this.eventDispatcher = null; + this.shadowRoot = null; } getDirectParentLightDom(boundElementIndex:number) { @@ -62,15 +75,6 @@ export class DomView { return destLightDom; } - getOrCreateViewContainer(binderIndex) { - var vc = this.viewContainers[binderIndex]; - if (isBlank(vc)) { - vc = new ViewContainer(this, binderIndex); - this.viewContainers[binderIndex] = vc; - } - return vc; - } - setElementProperty(elementIndex:number, propertyName:string, value:any) { var setter = MapWrapper.get(this.proto.elementBinders[elementIndex].propertySetters, propertyName); setter(this.boundElements[elementIndex], value); @@ -80,24 +84,16 @@ export class DomView { DOM.setText(this.boundTextNodes[textIndex], value); } - getViewContainer(index:number):ViewContainer { - return this.viewContainers[index]; - } - - setEventDispatcher(dispatcher:any/*EventDispatcher*/) { - this._eventDispatcher = dispatcher; - } - dispatchEvent(elementIndex, eventName, event): boolean { var allowDefaultBehavior = true; - if (isPresent(this._eventDispatcher)) { + if (isPresent(this.eventDispatcher)) { var evalLocals = MapWrapper.create(); MapWrapper.set(evalLocals, '$event', event); // TODO(tbosch): reenable this when we are parsing element properties // out of action expressions // var localValues = this.proto.elementBinders[elementIndex].eventLocals.eval(null, new Locals(null, evalLocals)); - // this._eventDispatcher.dispatchEvent(elementIndex, eventName, localValues); - allowDefaultBehavior = this._eventDispatcher.dispatchEvent(elementIndex, eventName, evalLocals); + // this.eventDispatcher.dispatchEvent(elementIndex, eventName, localValues); + allowDefaultBehavior = this.eventDispatcher.dispatchEvent(elementIndex, eventName, evalLocals); if (!allowDefaultBehavior) { event.preventDefault(); } diff --git a/modules/angular2/src/render/dom/view/view_container.js b/modules/angular2/src/render/dom/view/view_container.js index 18b5384089..e89afa9689 100644 --- a/modules/angular2/src/render/dom/view/view_container.js +++ b/modules/angular2/src/render/dom/view/view_container.js @@ -1,90 +1,15 @@ -import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang'; import {ListWrapper, MapWrapper, List} from 'angular2/src/facade/collection'; -import {DOM} from 'angular2/src/dom/dom_adapter'; import * as viewModule from './view'; -export class ViewContainer { - parentView: viewModule.DomView; - boundElementIndex: number; +export class DomViewContainer { views: List; - constructor(parentView: viewModule.DomView, boundElementIndex: number) { - this.parentView = parentView; - this.boundElementIndex = boundElementIndex; + constructor() { // The order in this list matches the DOM order. this.views = []; } - get(index: number): viewModule.DomView { - return this.views[index]; - } - - size() { - return this.views.length; - } - - _siblingToInsertAfter(index: number) { - if (index == 0) return this.parentView.boundElements[this.boundElementIndex]; - return ListWrapper.last(this.views[index - 1].rootNodes); - } - - _checkHydrated() { - if (!this.parentView.hydrated) throw new BaseException( - 'Cannot change dehydrated ViewContainer'); - } - - _getDirectParentLightDom() { - return this.parentView.getDirectParentLightDom(this.boundElementIndex); - } - - clear() { - this._checkHydrated(); - for (var i=this.views.length-1; i>=0; i--) { - this.detach(i); - } - if (isPresent(this._getDirectParentLightDom())) { - this._getDirectParentLightDom().redistribute(); - } - } - - insert(view, atIndex=-1): viewModule.DomView { - this._checkHydrated(); - if (atIndex == -1) atIndex = this.views.length; - ListWrapper.insert(this.views, atIndex, view); - - if (isBlank(this._getDirectParentLightDom())) { - ViewContainer.moveViewNodesAfterSibling(this._siblingToInsertAfter(atIndex), view); - } else { - this._getDirectParentLightDom().redistribute(); - } - // new content tags might have appeared, we need to redistribute. - if (isPresent(this.parentView.hostLightDom)) { - this.parentView.hostLightDom.redistribute(); - } - return view; - } - - /** - * The method can be used together with insert to implement a view move, i.e. - * moving the dom nodes while the directives in the view stay intact. - */ - detach(atIndex:number) { - this._checkHydrated(); - var detachedView = this.get(atIndex); - ListWrapper.removeAt(this.views, atIndex); - if (isBlank(this._getDirectParentLightDom())) { - ViewContainer.removeViewNodes(detachedView); - } else { - this._getDirectParentLightDom().redistribute(); - } - // content tags might have disappeared we need to do redistribution. - if (isPresent(this.parentView.hostLightDom)) { - this.parentView.hostLightDom.redistribute(); - } - return detachedView; - } - contentTagContainers() { return this.views; } @@ -97,18 +22,4 @@ export class ViewContainer { return r; } - static moveViewNodesAfterSibling(sibling, view) { - for (var i = view.rootNodes.length - 1; i >= 0; --i) { - DOM.insertAfter(sibling, view.rootNodes[i]); - } - } - - static removeViewNodes(view) { - var len = view.rootNodes.length; - if (len == 0) return; - var parent = view.rootNodes[0].parentNode; - for (var i = len - 1; i >= 0; --i) { - DOM.removeChild(parent, view.rootNodes[i]); - } - } } diff --git a/modules/angular2/src/render/dom/view/view_factory.js b/modules/angular2/src/render/dom/view/view_factory.js deleted file mode 100644 index 3021ef438a..0000000000 --- a/modules/angular2/src/render/dom/view/view_factory.js +++ /dev/null @@ -1,164 +0,0 @@ -import {Inject, Injectable} from 'angular2/src/di/annotations_impl'; -import {int, isPresent, isBlank, BaseException} from 'angular2/src/facade/lang'; -import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src/facade/collection'; - -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 'angular2/src/render/dom/events/event_manager'; - -import * as pvModule from './proto_view'; -import * as viewModule from './view'; -import {NG_BINDING_CLASS_SELECTOR, NG_BINDING_CLASS} from '../util'; - -// TODO(tbosch): Make this an OpaqueToken as soon as our transpiler supports this! -export const VIEW_POOL_CAPACITY = 'render.ViewFactory.viewPoolCapacity'; - -@Injectable() -export class ViewFactory { - _poolCapacityPerProtoView:number; - _pooledViewsPerProtoView:Map>; - _eventManager:EventManager; - _shadowDomStrategy:ShadowDomStrategy; - - constructor(@Inject(VIEW_POOL_CAPACITY) poolCapacityPerProtoView, - eventManager:EventManager, shadowDomStrategy:ShadowDomStrategy) { - this._poolCapacityPerProtoView = poolCapacityPerProtoView; - this._pooledViewsPerProtoView = MapWrapper.create(); - this._eventManager = eventManager; - this._shadowDomStrategy = shadowDomStrategy; - } - - createInPlaceHostView(hostElementSelector, hostProtoView:pvModule.DomProtoView):viewModule.DomView { - return this._createView(hostProtoView, hostElementSelector); - } - - getView(protoView:pvModule.DomProtoView):viewModule.DomView { - var pooledViews = MapWrapper.get(this._pooledViewsPerProtoView, protoView); - if (isPresent(pooledViews) && pooledViews.length > 0) { - return ListWrapper.removeLast(pooledViews); - } - return this._createView(protoView, null); - } - - returnView(view:viewModule.DomView) { - if (view.hydrated) { - throw new BaseException('View is still hydrated'); - } - var protoView = view.proto; - var pooledViews = MapWrapper.get(this._pooledViewsPerProtoView, protoView); - if (isBlank(pooledViews)) { - pooledViews = []; - MapWrapper.set(this._pooledViewsPerProtoView, protoView, pooledViews); - } - if (pooledViews.length < this._poolCapacityPerProtoView) { - ListWrapper.push(pooledViews, view); - } - } - - _createView(protoView:pvModule.DomProtoView, inplaceElement): viewModule.DomView { - if (isPresent(protoView.imperativeRendererId)) { - return new viewModule.DomView( - protoView, [], [], [], [] - ); - } - - var rootElementClone = isPresent(inplaceElement) ? inplaceElement : DOM.importIntoDoc(protoView.element); - var elementsWithBindingsDynamic; - if (protoView.isTemplateElement) { - elementsWithBindingsDynamic = DOM.querySelectorAll(DOM.content(rootElementClone), NG_BINDING_CLASS_SELECTOR); - } else { - elementsWithBindingsDynamic = DOM.getElementsByClassName(rootElementClone, NG_BINDING_CLASS); - } - - var elementsWithBindings = ListWrapper.createFixedSize(elementsWithBindingsDynamic.length); - for (var binderIdx = 0; binderIdx < elementsWithBindingsDynamic.length; ++binderIdx) { - elementsWithBindings[binderIdx] = elementsWithBindingsDynamic[binderIdx]; - } - - var viewRootNodes; - if (protoView.isTemplateElement) { - var childNode = DOM.firstChild(DOM.content(rootElementClone)); - viewRootNodes = []; // TODO(perf): Should be fixed size, since we could pre-compute in in pvModule.DomProtoView - // Note: An explicit loop is the fastest way to convert a DOM array into a JS array! - while(childNode != null) { - ListWrapper.push(viewRootNodes, childNode); - childNode = DOM.nextSibling(childNode); - } - } else { - viewRootNodes = [rootElementClone]; - } - var binders = protoView.elementBinders; - var boundTextNodes = []; - var boundElements = ListWrapper.createFixedSize(binders.length); - var contentTags = ListWrapper.createFixedSize(binders.length); - - for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) { - var binder = binders[binderIdx]; - var element; - if (binderIdx === 0 && protoView.rootBindingOffset === 1) { - element = rootElementClone; - } else { - element = elementsWithBindings[binderIdx - protoView.rootBindingOffset]; - } - boundElements[binderIdx] = element; - - // boundTextNodes - var childNodes = DOM.childNodes(DOM.templateAwareRoot(element)); - var textNodeIndices = binder.textNodeIndices; - for (var i = 0; i { - view.dispatchEvent(elementIndex, eventName, event); - }); - } - - // This method is used by the ViewFactory and the ViewHydrator - // TODO(tbosch): change shadow dom emulation so that LightDom - // instances don't need to be recreated by instead hydrated/dehydrated - static setComponentView(shadowDomStrategy:ShadowDomStrategy, hostView:viewModule.DomView, elementIndex:number, componentView:viewModule.DomView) { - var element = hostView.boundElements[elementIndex]; - var lightDom = shadowDomStrategy.constructLightDom(hostView, componentView, element); - shadowDomStrategy.attachTemplate(element, componentView); - hostView.lightDoms[elementIndex] = lightDom; - hostView.componentChildViews[elementIndex] = componentView; - } -} diff --git a/modules/angular2/src/render/dom/view/view_hydrator.js b/modules/angular2/src/render/dom/view/view_hydrator.js deleted file mode 100644 index 869641373c..0000000000 --- a/modules/angular2/src/render/dom/view/view_hydrator.js +++ /dev/null @@ -1,190 +0,0 @@ -import {Injectable} from 'angular2/src/di/annotations_impl'; -import {int, isPresent, isBlank, BaseException} from 'angular2/src/facade/lang'; -import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src/facade/collection'; - -import * as ldModule from '../shadow_dom/light_dom'; -import {EventManager} from '../events/event_manager'; -import {ViewFactory} from './view_factory'; -import * as vcModule from './view_container'; -import * as viewModule from './view'; -import {ShadowDomStrategy} from '../shadow_dom/shadow_dom_strategy'; - -/** - * A dehydrated view is a state of the view that allows it to be moved around - * the view tree. - * - * A dehydrated view has the following properties: - * - * - all viewcontainers are empty. - * - * A call to hydrate/dehydrate is called whenever a view is attached/detached, - * but it does not do the attach/detach itself. - */ -@Injectable() -export class RenderViewHydrator { - _eventManager:EventManager; - _viewFactory:ViewFactory; - _shadowDomStrategy:ShadowDomStrategy; - - constructor(eventManager:EventManager, viewFactory:ViewFactory, shadowDomStrategy:ShadowDomStrategy) { - this._eventManager = eventManager; - this._viewFactory = viewFactory; - this._shadowDomStrategy = shadowDomStrategy; - } - - hydrateDynamicComponentView(hostView:viewModule.DomView, boundElementIndex:number, componentView:viewModule.DomView) { - ViewFactory.setComponentView(this._shadowDomStrategy, hostView, boundElementIndex, componentView); - var lightDom = hostView.lightDoms[boundElementIndex]; - this._viewHydrateRecurse(componentView, lightDom); - if (isPresent(lightDom)) { - lightDom.redistribute(); - } - } - - dehydrateDynamicComponentView(parentView:viewModule.DomView, boundElementIndex:number) { - throw new BaseException('Not supported yet'); - // Something along these lines: - // var componentView = parentView.componentChildViews[boundElementIndex]; - // vcModule.ViewContainer.removeViewNodes(componentView); - // parentView.componentChildViews[boundElementIndex] = null; - // parentView.lightDoms[boundElementIndex] = null; - // this._viewDehydrateRecurse(componentView); - } - - hydrateInPlaceHostView(parentView:viewModule.DomView, hostView:viewModule.DomView) { - if (isPresent(parentView)) { - ListWrapper.push(parentView.imperativeHostViews, hostView); - } - this._viewHydrateRecurse(hostView, null); - } - - dehydrateInPlaceHostView(parentView:viewModule.DomView, hostView:viewModule.DomView) { - if (isPresent(parentView)) { - ListWrapper.remove(parentView.imperativeHostViews, hostView); - } - vcModule.ViewContainer.removeViewNodes(hostView); - hostView.rootNodes = []; - this._viewDehydrateRecurse(hostView); - } - - hydrateViewInViewContainer(viewContainer:vcModule.ViewContainer, view:viewModule.DomView) { - this._viewHydrateRecurse(view, viewContainer.parentView.hostLightDom); - } - - dehydrateViewInViewContainer(viewContainer:vcModule.ViewContainer, view:viewModule.DomView) { - this._viewDehydrateRecurse(view); - } - - _viewHydrateRecurse(view, hostLightDom: ldModule.LightDom) { - if (view.hydrated) throw new BaseException('The view is already hydrated.'); - view.hydrated = true; - view.hostLightDom = hostLightDom; - - // content tags - for (var i = 0; i < view.contentTags.length; i++) { - var destLightDom = view.getDirectParentLightDom(i); - var ct = view.contentTags[i]; - if (isPresent(ct)) { - ct.hydrate(destLightDom); - } - } - - // componentChildViews - for (var i = 0; i < view.componentChildViews.length; i++) { - var cv = view.componentChildViews[i]; - if (isPresent(cv)) { - this._viewHydrateRecurse(cv, view.lightDoms[i]); - } - } - - for (var i = 0; i < view.lightDoms.length; ++i) { - var lightDom = view.lightDoms[i]; - if (isPresent(lightDom)) { - lightDom.redistribute(); - } - } - - //add global events - view.eventHandlerRemovers = ListWrapper.create(); - var binders = view.proto.elementBinders; - for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) { - var binder = binders[binderIdx]; - if (isPresent(binder.globalEvents)) { - for (var i = 0; i < binder.globalEvents.length; i++) { - var globalEvent = binder.globalEvents[i]; - var remover = this._createGlobalEventListener(view, binderIdx, globalEvent.name, globalEvent.target, globalEvent.fullName); - ListWrapper.push(view.eventHandlerRemovers, remover); - } - } - } - } - - _createGlobalEventListener(view, elementIndex, eventName, eventTarget, fullName): Function { - return this._eventManager.addGlobalEventListener(eventTarget, eventName, (event) => { - view.dispatchEvent(elementIndex, fullName, event); - }); - } - - _viewDehydrateRecurse(view) { - // Note: preserve the opposite order of the hydration process. - - // componentChildViews - for (var i = 0; i < view.componentChildViews.length; i++) { - var cv = view.componentChildViews[i]; - if (isPresent(cv)) { - this._viewDehydrateRecurse(cv); - if (view.proto.elementBinders[i].hasDynamicComponent()) { - vcModule.ViewContainer.removeViewNodes(cv); - this._viewFactory.returnView(cv); - view.lightDoms[i] = null; - view.componentChildViews[i] = null; - } - } - } - - // imperativeHostViews - for (var i = 0; i < view.imperativeHostViews.length; i++) { - var hostView = view.imperativeHostViews[i]; - this._viewDehydrateRecurse(hostView); - vcModule.ViewContainer.removeViewNodes(hostView); - hostView.rootNodes = []; - this._viewFactory.returnView(hostView); - } - view.imperativeHostViews = []; - - - // viewContainers and content tags - if (isPresent(view.viewContainers)) { - for (var i = 0; i < view.viewContainers.length; i++) { - var vc = view.viewContainers[i]; - if (isPresent(vc)) { - this._viewContainerDehydrateRecurse(vc); - } - var ct = view.contentTags[i]; - if (isPresent(ct)) { - ct.dehydrate(); - } - } - } - - //remove global events - for (var i = 0; i < view.eventHandlerRemovers.length; i++) { - view.eventHandlerRemovers[i](); - } - - view.hostLightDom = null; - view.eventHandlerRemovers = null; - view.setEventDispatcher(null); - view.hydrated = false; - } - - _viewContainerDehydrateRecurse(viewContainer) { - for (var i=0; i'); + var doc = this._injector.get(DOCUMENT_TOKEN); + var rootEl = el('
'); + DOM.appendChild(doc.body, rootEl); + var componentBinding = bind(component).toValue(context); - return this._injector.get(DynamicComponentLoader).loadIntoNewLocation(componentBinding, null, rootEl, this._injector).then((hostComponentRef) => { + return this._injector.get(DynamicComponentLoader).loadIntoNewLocation(componentBinding, null, '#root', this._injector).then((hostComponentRef) => { return new ViewProxy(hostComponentRef); }); } diff --git a/modules/angular2/src/test_lib/test_injector.js b/modules/angular2/src/test_lib/test_injector.js index cedd03bfc8..bd9f584424 100644 --- a/modules/angular2/src/test_lib/test_injector.js +++ b/modules/angular2/src/test_lib/test_injector.js @@ -20,8 +20,6 @@ import {VmTurnZone} from 'angular2/src/core/zone/vm_turn_zone'; import {DOM} from 'angular2/src/dom/dom_adapter'; -import {appDocumentToken} from 'angular2/src/core/application_tokens'; - import {EventManager, DomEventsPlugin} from 'angular2/src/render/dom/events/event_manager'; import {MockTemplateResolver} from 'angular2/src/mock/template_resolver_mock'; @@ -40,10 +38,8 @@ import {AppViewManager} from 'angular2/src/core/compiler/view_manager'; import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils'; import {ProtoViewFactory} from 'angular2/src/core/compiler/proto_view_factory'; import {RenderCompiler, Renderer} from 'angular2/src/render/api'; -import {DirectDomRenderer} from 'angular2/src/render/dom/direct_dom_renderer'; -import * as rc from 'angular2/src/render/dom/compiler/compiler'; -import * as rvf from 'angular2/src/render/dom/view/view_factory'; -import * as rvh from 'angular2/src/render/dom/view/view_hydrator'; +import {DomRenderer, DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer'; +import {DefaultDomCompiler} from 'angular2/src/render/dom/compiler/compiler'; /** * Returns the root injector bindings. @@ -76,16 +72,14 @@ function _getAppBindings() { } return [ - bind(appDocumentToken).toValue(appDoc), + bind(DOCUMENT_TOKEN).toValue(appDoc), bind(ShadowDomStrategy).toFactory( (styleUrlResolver, doc) => new EmulatedUnscopedShadowDomStrategy(styleUrlResolver, doc.head), - [StyleUrlResolver, appDocumentToken]), - bind(DirectDomRenderer).toClass(DirectDomRenderer), - bind(Renderer).toClass(DirectDomRenderer), - bind(RenderCompiler).toClass(rc.DefaultDomCompiler), - rvf.ViewFactory, - rvh.RenderViewHydrator, - bind(rvf.VIEW_POOL_CAPACITY).toValue(500), + [StyleUrlResolver, DOCUMENT_TOKEN]), + DomRenderer, + DefaultDomCompiler, + bind(Renderer).toAlias(DomRenderer), + bind(RenderCompiler).toAlias(DefaultDomCompiler), ProtoViewFactory, AppViewPool, AppViewManager, diff --git a/modules/angular2/src/test_lib/utils.js b/modules/angular2/src/test_lib/utils.js index 4c859e99e9..2ec39a98ca 100644 --- a/modules/angular2/src/test_lib/utils.js +++ b/modules/angular2/src/test_lib/utils.js @@ -1,6 +1,7 @@ import {List, ListWrapper} from 'angular2/src/facade/collection'; import {DOM} from 'angular2/src/dom/dom_adapter'; import {isPresent} from 'angular2/src/facade/lang'; +import {resolveInternalDomView} from 'angular2/src/render/dom/view/view'; export class Log { _result:List; @@ -25,7 +26,7 @@ export class Log { } export function viewRootNodes(view):List { - return view.render.delegate.rootNodes; + return resolveInternalDomView(view.render).rootNodes; } export function queryView(view, selector:string) { diff --git a/modules/angular2/test/core/application_spec.js b/modules/angular2/test/core/application_spec.js index 7ebf90996b..95aa654ecb 100644 --- a/modules/angular2/test/core/application_spec.js +++ b/modules/angular2/test/core/application_spec.js @@ -11,7 +11,7 @@ import { xit, } from 'angular2/test_lib'; import {bootstrap} from 'angular2/src/core/application'; -import {appDocumentToken, appElementToken} from 'angular2/src/core/application_tokens'; +import {appComponentAnnotatedTypeToken} from 'angular2/src/core/application_tokens'; import {Component, Directive} from 'angular2/src/core/annotations_impl/annotations'; import {DOM} from 'angular2/src/dom/dom_adapter'; import {ListWrapper} from 'angular2/src/facade/collection'; @@ -21,6 +21,7 @@ import {Inject} from 'angular2/src/di/annotations_impl'; import {View} from 'angular2/src/core/annotations_impl/view'; import {LifeCycle} from 'angular2/src/core/life_cycle/life_cycle'; import {Testability, TestabilityRegistry} from 'angular2/src/core/testability/testability'; +import {DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer'; @Component({selector: 'hello-app'}) @View({template: '{{greeting}} world!'}) @@ -84,7 +85,7 @@ export function main() { DOM.appendChild(fakeDoc.body, el2); DOM.appendChild(el, lightDom); DOM.setText(lightDom, 'loading'); - testBindings = [bind(appDocumentToken).toValue(fakeDoc)]; + testBindings = [bind(DOCUMENT_TOKEN).toValue(fakeDoc)]; }); describe('bootstrap factory method', () => { @@ -100,7 +101,7 @@ export function main() { var refPromise = bootstrap(HelloRootCmp, [], (e,t) => {throw e;}); PromiseWrapper.then(refPromise, null, (reason) => { expect(reason.message).toContain( - 'The app selector "hello-app" did not match any elements'); + 'The selector "hello-app" did not match any elements'); async.done(); }); })); @@ -113,7 +114,7 @@ export function main() { it('should resolve an injector promise and contain bindings', inject([AsyncTestCompleter], (async) => { var refPromise = bootstrap(HelloRootCmp, testBindings); refPromise.then((ref) => { - expect(ref.injector.get(appElementToken)).toBe(el); + expect(ref.injector.get(appComponentAnnotatedTypeToken).type).toBe(HelloRootCmp); async.done(); }); })); @@ -129,7 +130,7 @@ export function main() { it('should display hello world', inject([AsyncTestCompleter], (async) => { var refPromise = bootstrap(HelloRootCmp, testBindings); refPromise.then((ref) => { - expect(ref.injector.get(appElementToken)).toHaveText('hello world!'); + expect(el).toHaveText('hello world!'); async.done(); }); })); @@ -138,8 +139,8 @@ export function main() { var refPromise1 = bootstrap(HelloRootCmp, testBindings); var refPromise2 = bootstrap(HelloRootCmp2, testBindings); PromiseWrapper.all([refPromise1, refPromise2]).then((refs) => { - expect(refs[0].injector.get(appElementToken)).toHaveText('hello world!'); - expect(refs[1].injector.get(appElementToken)).toHaveText('hello world, again!'); + expect(el).toHaveText('hello world!'); + expect(el2).toHaveText('hello world, again!'); async.done(); }); })); @@ -168,7 +169,7 @@ export function main() { it("should support shadow dom content tag", inject([AsyncTestCompleter], (async) => { var refPromise = bootstrap(HelloRootCmpContent, testBindings); refPromise.then((ref) => { - expect(ref.injector.get(appElementToken)).toHaveText('before: loading after: done'); + expect(el).toHaveText('before: loading after: done'); async.done(); }); })); diff --git a/modules/angular2/test/core/compiler/compiler_spec.js b/modules/angular2/test/core/compiler/compiler_spec.js index d12f3d8206..75db7ed605 100644 --- a/modules/angular2/test/core/compiler/compiler_spec.js +++ b/modules/angular2/test/core/compiler/compiler_spec.js @@ -391,25 +391,6 @@ export function main() { }); })); - it('should create imperative proto views', inject([AsyncTestCompleter], (async) => { - renderCompiler.spy('createImperativeComponentProtoView').andCallFake( (rendererId) => { - return PromiseWrapper.resolve( - createRenderProtoView([]) - ); - }); - tplResolver.setView(MainComponent, new View({renderer: 'some-renderer'})); - var mainProtoView = createProtoView(); - var compiler = createCompiler( - [], - [mainProtoView] - ); - compiler.compile(MainComponent).then( (protoViewRef) => { - expect(internalProtoView(protoViewRef)).toBe(mainProtoView); - expect(renderCompiler.spy('createImperativeComponentProtoView')).toHaveBeenCalledWith('some-renderer'); - async.done(); - }); - })); - it('should throw for non component types', () => { var compiler = createCompiler([], []); expect( diff --git a/modules/angular2/test/core/compiler/dynamic_component_loader_spec.js b/modules/angular2/test/core/compiler/dynamic_component_loader_spec.js index 57280d3439..db2c6a333a 100644 --- a/modules/angular2/test/core/compiler/dynamic_component_loader_spec.js +++ b/modules/angular2/test/core/compiler/dynamic_component_loader_spec.js @@ -21,9 +21,9 @@ import {View} from 'angular2/src/core/annotations_impl/view'; import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader'; import {ElementRef} from 'angular2/src/core/compiler/element_ref'; import {If} from 'angular2/src/directives/if'; -import {DirectDomRenderer} from 'angular2/src/render/dom/direct_dom_renderer'; +import {DomRenderer} from 'angular2/src/render/dom/dom_renderer'; import {DOM} from 'angular2/src/dom/dom_adapter'; - +import {AppViewManager} from 'angular2/src/core/compiler/view_manager'; export function main() { describe('DynamicComponentLoader', function () { @@ -200,15 +200,17 @@ export function main() { selector: 'imp-ng-cmp' }) @View({ - renderer: 'imp-ng-cmp-renderer' + renderer: 'imp-ng-cmp-renderer', + template: '' }) class ImperativeViewComponentUsingNgComponent { done; - constructor(self:ElementRef, dynamicComponentLoader:DynamicComponentLoader, renderer:DirectDomRenderer) { - var div = el('
'); - renderer.setImperativeComponentRootNodes(self.parentView.render, self.boundElementIndex, [div]); - this.done = dynamicComponentLoader.loadIntoNewLocation(ChildComp, self, div, null); + constructor(self:ElementRef, dynamicComponentLoader:DynamicComponentLoader, viewManager:AppViewManager, renderer:DomRenderer) { + var div = el('
'); + var shadowViewRef = viewManager.getComponentView(self); + renderer.setComponentViewRootNodes(shadowViewRef.render, [div]); + this.done = dynamicComponentLoader.loadIntoNewLocation(ChildComp, self, '#impHost', null); } } diff --git a/modules/angular2/test/core/compiler/integration_spec.js b/modules/angular2/test/core/compiler/integration_spec.js index 27a5ba1ce6..49577a5ded 100644 --- a/modules/angular2/test/core/compiler/integration_spec.js +++ b/modules/angular2/test/core/compiler/integration_spec.js @@ -37,7 +37,8 @@ import {ProtoViewRef} from 'angular2/src/core/compiler/view_ref'; import {Compiler} from 'angular2/src/core/compiler/compiler'; import {ElementRef} from 'angular2/src/core/compiler/element_ref'; -import {DirectDomRenderer} from 'angular2/src/render/dom/direct_dom_renderer'; +import {DomRenderer} from 'angular2/src/render/dom/dom_renderer'; +import {AppViewManager} from 'angular2/src/core/compiler/view_manager'; export function main() { describe('integration tests', function() { @@ -863,13 +864,15 @@ export function main() { selector: 'simple-imp-cmp' }) @View({ - renderer: 'simple-imp-cmp-renderer' + renderer: 'simple-imp-cmp-renderer', + template: '' }) class SimpleImperativeViewComponent { done; - constructor(self:ElementRef, renderer:DirectDomRenderer) { - renderer.setImperativeComponentRootNodes(self.parentView.render, self.boundElementIndex, [el('hello imp view')]); + constructor(self:ElementRef, viewManager:AppViewManager, renderer:DomRenderer) { + var shadowViewRef = viewManager.getComponentView(self); + renderer.setComponentViewRootNodes(shadowViewRef.render, [el('hello imp view')]); } } diff --git a/modules/angular2/test/core/compiler/view_manager_spec.js b/modules/angular2/test/core/compiler/view_manager_spec.js index 014509c1f4..1e890ae41e 100644 --- a/modules/angular2/test/core/compiler/view_manager_spec.js +++ b/modules/angular2/test/core/compiler/view_manager_spec.js @@ -94,11 +94,15 @@ export function main() { }, {}); } - function createView(pv=null) { + function createView(pv=null, renderViewRef=null) { if (isBlank(pv)) { pv = createProtoView(); } + if (isBlank(renderViewRef)) { + renderViewRef = new RenderViewRef(); + } var view = new AppView(renderer, pv, MapWrapper.create()); + view.render = renderViewRef; var elementInjectors = ListWrapper.createFixedSize(pv.elementBinders.length); for (var i=0; i { - var view = createView(proto); + utils.spy('createView').andCallFake( (proto, renderViewRef, _a, _b) => { + var view = createView(proto, renderViewRef); ListWrapper.push(createdViews, view); return view; }); @@ -137,23 +141,15 @@ export function main() { } ListWrapper.insert(viewContainer.views, atIndex, childView); }); - var createRenderViewRefs = function(renderPvRef) { - var res = []; - for (var i=0; i { - return createRenderViewRefs(childPvRef); + renderer.spy('createInPlaceHostView').andCallFake( (_a, _b, _c) => { + var rv = new RenderViewRef(); + ListWrapper.push(createdRenderViews, rv); + return rv; }); - renderer.spy('createInPlaceHostView').andCallFake( (_a, _b, childPvRef) => { - return createRenderViewRefs(childPvRef); - }); - renderer.spy('createViewInContainer').andCallFake( (_a, _b, childPvRef) => { - return createRenderViewRefs(childPvRef); + renderer.spy('createView').andCallFake( (_a) => { + var rv = new RenderViewRef(); + ListWrapper.push(createdRenderViews, rv); + return rv; }); }); @@ -165,7 +161,6 @@ export function main() { hostView = createView(createProtoView( [createComponentElBinder(null)] )); - hostView.render = new RenderViewRef(); componentProtoView = createProtoView(); }); @@ -186,11 +181,13 @@ export function main() { internalView(manager.createDynamicComponentView(elementRef(wrapView(hostView), 0), wrapPv(componentProtoView), null, null)) ).toBe(createdView); expect(utils.spy('createView')).not.toHaveBeenCalled(); + expect(renderer.spy('createView')).not.toHaveBeenCalled(); }); it('should attach the view', () => { manager.createDynamicComponentView(elementRef(wrapView(hostView), 0), wrapPv(componentProtoView), null, null) expect(utils.spy('attachComponentView')).toHaveBeenCalledWith(hostView, 0, createdViews[0]); + expect(renderer.spy('attachComponentView')).toHaveBeenCalledWith(hostView.render, 0, createdViews[0].render); }); it('should hydrate the dynamic component', () => { @@ -203,11 +200,12 @@ export function main() { it('should hydrate the view', () => { manager.createDynamicComponentView(elementRef(wrapView(hostView), 0), wrapPv(componentProtoView), null, null); expect(utils.spy('hydrateComponentView')).toHaveBeenCalledWith(hostView, 0); + expect(renderer.spy('hydrateView')).toHaveBeenCalledWith(createdViews[0].render); }); it('should create and set the render view', () => { manager.createDynamicComponentView(elementRef(wrapView(hostView), 0), wrapPv(componentProtoView), null, null); - expect(renderer.spy('createDynamicComponentView')).toHaveBeenCalledWith(hostView.render, 0, componentProtoView.render); + expect(renderer.spy('createView')).toHaveBeenCalledWith(componentProtoView.render); expect(createdViews[0].render).toBe(createdRenderViews[0]); }); @@ -256,7 +254,6 @@ export function main() { hostView = createView(createProtoView( [createComponentElBinder(null)] )); - hostView.render = new RenderViewRef(); nestedProtoView = createProtoView(); componentProtoView = createProtoView([ createComponentElBinder(nestedProtoView) @@ -272,6 +269,7 @@ export function main() { it('should hydrate the view', () => { manager.createDynamicComponentView(elementRef(wrapView(hostView), 0), wrapPv(componentProtoView), null, null); expect(utils.spy('hydrateComponentView')).toHaveBeenCalledWith(createdViews[0], 0); + expect(renderer.spy('hydrateView')).toHaveBeenCalledWith(createdViews[0].render); }); it('should set the render view', () => { @@ -309,7 +307,6 @@ export function main() { )); parentView = createView(); utils.attachComponentView(parentHostView, 0, parentView); - parentView.render = new RenderViewRef(); hostProtoView = createProtoView( [createComponentElBinder(null)] ); @@ -326,6 +323,7 @@ export function main() { var injector = new Injector([], null, false); manager.createInPlaceHostView(elementRef(wrapView(parentHostView), 0), null, wrapPv(hostProtoView), injector); expect(utils.spy('attachAndHydrateInPlaceHostView')).toHaveBeenCalledWith(parentHostView, 0, createdViews[0], injector); + expect(renderer.spy('hydrateView')).toHaveBeenCalledWith(createdViews[0].render); }); it('should create and set the render view', () => { @@ -354,7 +352,6 @@ export function main() { )); parentView = createView(); utils.attachComponentView(parentHostView, 0, parentView); - parentView.render = new RenderViewRef(); hostProtoView = createProtoView( [createComponentElBinder(null)] ); @@ -362,25 +359,25 @@ export function main() { hostRenderViewRef = hostView.render; }); - it('should dehydrate', () => { + it('should detach', () => { manager.destroyInPlaceHostView(elementRef(wrapView(parentHostView), 0), wrapView(hostView)); expect(utils.spy('detachInPlaceHostView')).toHaveBeenCalledWith(parentView, hostView); }); - it('should detach', () => { + it('should dehydrate', () => { manager.destroyInPlaceHostView(elementRef(wrapView(parentHostView), 0), wrapView(hostView)); expect(utils.spy('dehydrateView')).toHaveBeenCalledWith(hostView); + expect(renderer.spy('dehydrateView')).toHaveBeenCalledWith(hostView.render); }); it('should destroy and clear the render view', () => { manager.destroyInPlaceHostView(elementRef(wrapView(parentHostView), 0), wrapView(hostView)); expect(renderer.spy('destroyInPlaceHostView')).toHaveBeenCalledWith(parentView.render, hostRenderViewRef); - expect(hostView.render).toBe(null); }); - it('should return the view to the pool', () => { + it('should not return the view to the pool', () => { manager.destroyInPlaceHostView(elementRef(wrapView(parentHostView), 0), wrapView(hostView)); - expect(viewPool.spy('returnView')).toHaveBeenCalledWith(hostView); + expect(viewPool.spy('returnView')).not.toHaveBeenCalled(); }); }); @@ -398,7 +395,6 @@ export function main() { parentView = createView(createProtoView( [createEmptyElBinder()] )); - parentView.render = new RenderViewRef(); childProtoView = createProtoView(); }); @@ -417,18 +413,19 @@ export function main() { it('should attach the view', () => { manager.createViewInContainer(elementRef(wrapView(parentView), 0), 0, wrapPv(childProtoView), null) expect(utils.spy('attachViewInContainer')).toHaveBeenCalledWith(parentView, 0, 0, createdViews[0]); + expect(renderer.spy('attachViewInContainer')).toHaveBeenCalledWith(parentView.render, 0, 0, createdViews[0].render); }); it('should hydrate the view', () => { var injector = new Injector([], null, false); manager.createViewInContainer(elementRef(wrapView(parentView), 0), 0, wrapPv(childProtoView), injector); expect(utils.spy('hydrateViewInContainer')).toHaveBeenCalledWith(parentView, 0, 0, injector); + expect(renderer.spy('hydrateView')).toHaveBeenCalledWith(createdViews[0].render); }); it('should create and set the render view', () => { manager.createViewInContainer(elementRef(wrapView(parentView), 0), 0, wrapPv(childProtoView), null); - expect(renderer.spy('createViewInContainer')).toHaveBeenCalledWith( - new RenderViewContainerRef(parentView.render, 0), 0, childProtoView.render); + expect(renderer.spy('createView')).toHaveBeenCalledWith(childProtoView.render); expect(createdViews[0].render).toBe(createdRenderViews[0]); }); @@ -449,7 +446,6 @@ export function main() { parentView = createView(createProtoView( [createEmptyElBinder()] )); - parentView.render = new RenderViewRef(); childProtoView = createProtoView(); childView = internalView(manager.createViewInContainer(elementRef(wrapView(parentView), 0), 0, wrapPv(childProtoView), null)); }); @@ -457,17 +453,13 @@ export function main() { it('should dehydrate', () => { manager.destroyViewInContainer(elementRef(wrapView(parentView), 0), 0); expect(utils.spy('dehydrateView')).toHaveBeenCalledWith(parentView.viewContainers[0].views[0]); + expect(renderer.spy('dehydrateView')).toHaveBeenCalledWith(childView.render); }); it('should detach', () => { manager.destroyViewInContainer(elementRef(wrapView(parentView), 0), 0); expect(utils.spy('detachViewInContainer')).toHaveBeenCalledWith(parentView, 0, 0); - }); - - it('should destroy and clear the render view', () => { - manager.destroyViewInContainer(elementRef(wrapView(parentView), 0), 0); - expect(renderer.spy('destroyViewInContainer')).toHaveBeenCalledWith(new RenderViewContainerRef(parentView.render, 0), 0); - expect(childView.render).toBe(null); + expect(renderer.spy('detachViewInContainer')).toHaveBeenCalledWith(parentView.render, 0, 0, childView.render); }); it('should return the view to the pool', () => { @@ -482,7 +474,6 @@ export function main() { parentView = createView(createProtoView( [createEmptyElBinder()] )); - parentView.render = new RenderViewRef(); childProtoView = createProtoView(); childView = internalView(manager.createViewInContainer(elementRef(wrapView(parentView), 0), 0, wrapPv(childProtoView), null)); }); @@ -490,17 +481,13 @@ export function main() { it('should dehydrate', () => { manager.destroyInPlaceHostView(null, wrapView(parentView)); expect(utils.spy('dehydrateView')).toHaveBeenCalledWith(parentView.viewContainers[0].views[0]); + expect(renderer.spy('dehydrateView')).toHaveBeenCalledWith(childView.render); }); it('should detach', () => { manager.destroyInPlaceHostView(null, wrapView(parentView)); expect(utils.spy('detachViewInContainer')).toHaveBeenCalledWith(parentView, 0, 0); - }); - - it('should not destroy but clear the render view', () => { - manager.destroyInPlaceHostView(null, wrapView(parentView)); - expect(renderer.spy('destroyViewInContainer')).not.toHaveBeenCalled(); - expect(childView.render).toBe(null); + expect(renderer.spy('detachViewInContainer')).toHaveBeenCalledWith(parentView.render, 0, 0, childView.render); }); it('should return the view to the pool', () => { diff --git a/modules/angular2/test/render/dom/compiler/compiler_common_tests.js b/modules/angular2/test/render/dom/compiler/compiler_common_tests.js index 18ad6744d4..27d4eb29a4 100644 --- a/modules/angular2/test/render/dom/compiler/compiler_common_tests.js +++ b/modules/angular2/test/render/dom/compiler/compiler_common_tests.js @@ -26,6 +26,8 @@ import {TemplateLoader} from 'angular2/src/render/dom/compiler/template_loader'; import {UrlResolver} from 'angular2/src/services/url_resolver'; +import {resolveInternalDomProtoView} from 'angular2/src/render/dom/view/proto_view'; + export function runCompilerCommonTests() { describe('DomCompiler', function() { var mockStepFactory; @@ -61,7 +63,7 @@ export function runCompilerCommonTests() { var dirMetadata = new DirectiveMetadata({id: 'id', selector: 'CUSTOM', type: DirectiveMetadata.COMPONENT_TYPE}); compiler.compileHost(dirMetadata).then( (protoView) => { - expect(DOM.tagName(protoView.render.delegate.element)).toEqual('CUSTOM') + expect(DOM.tagName(resolveInternalDomProtoView(protoView.render).element)).toEqual('CUSTOM') expect(mockStepFactory.viewDef.directives).toEqual([dirMetadata]); expect(protoView.variableBindings).toEqual(MapWrapper.createFromStringMap({ 'a': 'b' @@ -76,7 +78,7 @@ export function runCompilerCommonTests() { componentId: 'someId', template: 'inline component' })).then( (protoView) => { - expect(DOM.getInnerHTML(protoView.render.delegate.element)).toEqual('inline component'); + expect(DOM.getInnerHTML(resolveInternalDomProtoView(protoView.render).element)).toEqual('inline component'); async.done(); }); })); @@ -90,7 +92,7 @@ export function runCompilerCommonTests() { componentId: 'someId', absUrl: 'someUrl' })).then( (protoView) => { - expect(DOM.getInnerHTML(protoView.render.delegate.element)).toEqual('url component'); + expect(DOM.getInnerHTML(resolveInternalDomProtoView(protoView.render).element)).toEqual('url component'); async.done(); }); })); diff --git a/modules/angular2/test/render/dom/direct_dom_renderer_integration_spec.js b/modules/angular2/test/render/dom/direct_dom_renderer_integration_spec.js deleted file mode 100644 index 68c9255f26..0000000000 --- a/modules/angular2/test/render/dom/direct_dom_renderer_integration_spec.js +++ /dev/null @@ -1,213 +0,0 @@ -import { - AsyncTestCompleter, - beforeEach, - ddescribe, - describe, - el, - elementText, - expect, - iit, - inject, - it, - xit, - SpyObject, -} from 'angular2/test_lib'; - -import {DOM} from 'angular2/src/dom/dom_adapter'; - -import {ProtoViewDto, ViewDefinition, RenderViewContainerRef, EventDispatcher, DirectiveMetadata} from 'angular2/src/render/api'; - -import {IntegrationTestbed, LoggingEventDispatcher, FakeEvent} from './integration_testbed'; - -export function main() { - describe('DirectDomRenderer integration', () => { - var testbed, renderer, renderCompiler, eventPlugin, compileRoot, rootEl; - - beforeEach(() => { - rootEl = el('
'); - }); - - function createRenderer({urlData, viewCacheCapacity, shadowDomStrategy, templates}={}) { - testbed = new IntegrationTestbed({ - urlData: urlData, - viewCacheCapacity: viewCacheCapacity, - shadowDomStrategy: shadowDomStrategy, - templates: templates - }); - renderer = testbed.renderer; - renderCompiler = testbed.renderCompiler; - eventPlugin = testbed.eventPlugin; - compileRoot = (componentId) => testbed.compileRoot(componentId); - } - - it('should create host views while using the given elements in place', inject([AsyncTestCompleter], (async) => { - createRenderer(); - renderCompiler.compileHost(someComponent).then( (rootProtoView) => { - expect(rootProtoView.elementBinders[0].directives[0].directiveIndex).toBe(0); - var viewRefs = renderer.createInPlaceHostView(null, rootEl, rootProtoView.render); - expect(viewRefs.length).toBe(1); - expect(viewRefs[0].delegate.rootNodes[0]).toEqual(rootEl); - async.done(); - }); - })); - - it('should create imperative proto views', inject([AsyncTestCompleter], (async) => { - createRenderer(); - renderCompiler.createImperativeComponentProtoView('someRenderId').then( (rootProtoView) => { - expect(rootProtoView.elementBinders).toEqual([]); - - expect(rootProtoView.render.delegate.imperativeRendererId).toBe('someRenderId'); - async.done(); - }); - })); - - it('should add a static component', inject([AsyncTestCompleter], (async) => { - createRenderer(); - renderCompiler.compileHost(someComponent).then( (rootProtoView) => { - var template = new ViewDefinition({ - componentId: 'someComponent', - template: 'hello', - directives: [] - }); - renderCompiler.compile(template).then( (pv) => { - renderCompiler.mergeChildComponentProtoViews(rootProtoView.render, [pv.render]); - renderer.createInPlaceHostView(null, rootEl, rootProtoView.render); - expect(rootEl).toHaveText('hello'); - async.done(); - }); - }); - })); - - it('should add a a dynamic component', inject([AsyncTestCompleter], (async) => { - createRenderer(); - renderCompiler.compileHost(someComponent).then( (rootProtoView) => { - var template = new ViewDefinition({ - componentId: 'someComponent', - template: 'hello', - directives: [] - }); - renderCompiler.compile(template).then( (pv) => { - var rootViewRef = renderer.createInPlaceHostView(null, rootEl, rootProtoView.render)[0]; - renderer.createDynamicComponentView(rootViewRef, 0, pv.render)[0]; - expect(rootEl).toHaveText('hello'); - async.done(); - }); - }); - })); - - it('should update text nodes', inject([AsyncTestCompleter], (async) => { - createRenderer({ - templates: [new ViewDefinition({ - componentId: 'someComponent', - template: '{{a}}', - directives: [] - })] - }); - compileRoot(someComponent).then( (rootProtoView) => { - var viewRefs = renderer.createInPlaceHostView(null, rootEl, rootProtoView.render); - renderer.setText(viewRefs[1], 0, 'hello'); - expect(rootEl).toHaveText('hello'); - async.done(); - }); - })); - - it('should update element properties', inject([AsyncTestCompleter], (async) => { - createRenderer({ - templates: [new ViewDefinition({ - componentId: 'someComponent', - template: '', - directives: [] - })] - }); - compileRoot(someComponent).then( (rootProtoView) => { - var viewRefs = renderer.createInPlaceHostView(null, rootEl, rootProtoView.render); - renderer.setElementProperty(viewRefs[1], 0, 'value', 'hello'); - expect(DOM.childNodes(rootEl)[0].value).toEqual('hello'); - async.done(); - }); - })); - - it('should add and remove views to and from containers', inject([AsyncTestCompleter], (async) => { - createRenderer({ - templates: [new ViewDefinition({ - componentId: 'someComponent', - template: '', - directives: [] - })] - }); - compileRoot(someComponent).then( (rootProtoView) => { - var viewRef = renderer.createInPlaceHostView(null, rootEl, rootProtoView.render)[1]; - var vcProtoViewRef = rootProtoView.elementBinders[0] - .nestedProtoView.elementBinders[0].nestedProtoView.render; - var vcRef = new RenderViewContainerRef(viewRef, 0); - expect(rootEl).toHaveText(''); - var childViewRef = renderer.createViewInContainer(vcRef, 0, vcProtoViewRef)[0]; - expect(rootEl).toHaveText('hello'); - renderer.detachViewFromContainer(vcRef, 0); - expect(rootEl).toHaveText(''); - renderer.insertViewIntoContainer(vcRef, 0, childViewRef); - expect(rootEl).toHaveText('hello'); - renderer.destroyViewInContainer(vcRef, 0); - expect(rootEl).toHaveText(''); - - async.done(); - }); - })); - - it('should cache views', inject([AsyncTestCompleter], (async) => { - createRenderer({ - templates: [new ViewDefinition({ - componentId: 'someComponent', - template: '', - directives: [] - })], - viewCacheCapacity: 2 - }); - compileRoot(someComponent).then( (rootProtoView) => { - var viewRef = renderer.createInPlaceHostView(null, rootEl, rootProtoView.render)[1]; - var vcProtoViewRef = rootProtoView.elementBinders[0] - .nestedProtoView.elementBinders[0].nestedProtoView.render; - var vcRef = new RenderViewContainerRef(viewRef, 0); - - var viewRef1 = renderer.createViewInContainer(vcRef, 0, vcProtoViewRef)[0]; - renderer.destroyViewInContainer(vcRef, 0); - var viewRef2 = renderer.createViewInContainer(vcRef, 0, vcProtoViewRef)[0]; - var viewRef3 = renderer.createViewInContainer(vcRef, 0, vcProtoViewRef)[0]; - expect(viewRef2.delegate).toBe(viewRef1.delegate); - expect(viewRef3.delegate).not.toBe(viewRef1.delegate); - - async.done(); - }); - })); - - // TODO(tbosch): This is not working yet as we commented out - // the event expression processing... - xit('should handle events', inject([AsyncTestCompleter], (async) => { - createRenderer({ - templates: [new ViewDefinition({ - componentId: 'someComponent', - template: '', - directives: [] - })] - }); - compileRoot(someComponent).then( (rootProtoView) => { - var viewRef = renderer.createInPlaceHostView(null, rootEl, rootProtoView.render)[1]; - var dispatcher = new LoggingEventDispatcher(); - renderer.setEventDispatcher(viewRef, dispatcher); - var inputEl = DOM.childNodes(rootEl)[0]; - inputEl.value = 'hello'; - eventPlugin.dispatchEvent('change', new FakeEvent(inputEl)); - expect(dispatcher.log).toEqual([[0, 'change', ['hello']]]); - async.done(); - }); - - })); - - }); -} - -var someComponent = new DirectiveMetadata({ - id: 'someComponent', - type: DirectiveMetadata.COMPONENT_TYPE, - selector: 'some-comp' -}); diff --git a/modules/angular2/test/render/dom/dom_renderer_integration_spec.js b/modules/angular2/test/render/dom/dom_renderer_integration_spec.js new file mode 100644 index 0000000000..667469955f --- /dev/null +++ b/modules/angular2/test/render/dom/dom_renderer_integration_spec.js @@ -0,0 +1,154 @@ +import { + AsyncTestCompleter, + beforeEach, + ddescribe, + describe, + el, + elementText, + expect, + iit, + inject, + it, + xit, + beforeEachBindings, + SpyObject, +} from 'angular2/test_lib'; + +import {MapWrapper} from 'angular2/src/facade/collection'; +import {DOM} from 'angular2/src/dom/dom_adapter'; + +import {DomTestbed} from './dom_testbed'; + +import {ViewDefinition, DirectiveMetadata, RenderViewRef} from 'angular2/src/render/api'; + +export function main() { + describe('DomRenderer integration', () => { + beforeEachBindings(() => [ + DomTestbed + ]); + + it('should create and destroy host views while using the given elements in place', + inject([AsyncTestCompleter, DomTestbed], (async, tb) => { + tb.compileAll([someComponent]).then( (protoViewDtos) => { + var view = tb.createRootView(protoViewDtos[0]); + expect(tb.rootEl.parentNode).toBeTruthy(); + expect(view.rawView.rootNodes[0]).toEqual(tb.rootEl); + + tb.renderer.destroyInPlaceHostView(null, 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) => { + tb.compileAll([someComponent, + new ViewDefinition({ + componentId: 'someComponent', + template: '{{a}}', + directives: [] + }) + ]).then( (protoViewDtos) => { + var rootView = tb.createRootView(protoViewDtos[0]); + var cmpView = tb.createComponentView(rootView.viewRef, 0, protoViewDtos[1]); + + tb.renderer.setText(cmpView.viewRef, 0, 'hello'); + expect(tb.rootEl).toHaveText('hello'); + async.done(); + }); + })); + + it('should update element properties', + inject([AsyncTestCompleter, DomTestbed], (async, tb) => { + tb.compileAll([someComponent, + new ViewDefinition({ + componentId: 'someComponent', + template: 'asdf', + directives: [] + }) + ]).then( (protoViewDtos) => { + var rootView = tb.createRootView(protoViewDtos[0]); + var cmpView = tb.createComponentView(rootView.viewRef, 0, protoViewDtos[1]); + + tb.renderer.setElementProperty(cmpView.viewRef, 0, 'value', 'hello'); + expect(DOM.childNodes(tb.rootEl)[0].value).toEqual('hello'); + 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: '', + directives: [] + }) + ]).then( (protoViewDtos) => { + var rootView = tb.createRootView(protoViewDtos[0]); + var cmpView = tb.createComponentView(rootView.viewRef, 0, protoViewDtos[1]); + + 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(''); + + async.done(); + }); + })); + + it('should handle events', + inject([AsyncTestCompleter, DomTestbed], (async, tb) => { + tb.compileAll([someComponent, + new ViewDefinition({ + componentId: 'someComponent', + template: '', + directives: [] + }) + ]).then( (protoViewDtos) => { + var rootView = tb.createRootView(protoViewDtos[0]); + var cmpView = tb.createComponentView(rootView.viewRef, 0, protoViewDtos[1]); + + tb.triggerEvent(cmpView.viewRef, 0, 'change'); + var eventEntry = cmpView.events[0]; + // bound element index + expect(eventEntry[0]).toEqual(0); + // event type + expect(eventEntry[1]).toEqual('change'); + // actual event + expect(MapWrapper.get(eventEntry[2], '$event').type).toEqual('change'); + async.done(); + }); + + })); + + }); +} + +var someComponent = new DirectiveMetadata({ + id: 'someComponent', + type: DirectiveMetadata.COMPONENT_TYPE, + selector: 'some-comp' +}); diff --git a/modules/angular2/test/render/dom/dom_testbed.js b/modules/angular2/test/render/dom/dom_testbed.js new file mode 100644 index 0000000000..e687931908 --- /dev/null +++ b/modules/angular2/test/render/dom/dom_testbed.js @@ -0,0 +1,126 @@ +import {Inject, Injectable} from 'angular2/src/di/annotations_impl'; +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 {RenderViewRef, ProtoViewDto, ViewDefinition, EventDispatcher, DirectiveMetadata} from 'angular2/src/render/api'; +import {resolveInternalDomView} from 'angular2/src/render/dom/view/view'; +import {el, dispatchEvent} from 'angular2/test_lib'; + +export class TestView extends EventDispatcher { + rawView:DomView; + viewRef:RenderViewRef; + events:List; + + constructor(viewRef:RenderViewRef) { + super(); + this.viewRef = viewRef; + this.rawView = resolveInternalDomView(viewRef); + this.events = []; + } +} + + +class LoggingEventDispatcher extends EventDispatcher { + log:List; + + constructor(log:List) { + super(); + this.log = log; + } + + dispatchEvent( + elementIndex:number, eventName:string, locals:Map + ) { + ListWrapper.push(this.log, [elementIndex, eventName, locals]); + return true; + } +} + + +@Injectable() +export class DomTestbed { + renderer:DomRenderer; + compiler:DefaultDomCompiler; + rootEl; + + constructor(renderer:DomRenderer, compiler:DefaultDomCompiler, @Inject(DOCUMENT_TOKEN) document) { + this.renderer = renderer; + this.compiler = compiler; + this.rootEl = el('
'); + var oldRoots = DOM.querySelectorAll(document, '#root'); + for (var i=0; i> { + return PromiseWrapper.all( + ListWrapper.map(directivesOrViewDefinitions, (entry) => { + if (entry instanceof DirectiveMetadata) { + return this.compiler.compileHost(entry); + } else { + return this.compiler.compile(entry); + } + }) + ); + } + + _createTestView(viewRef:RenderViewRef) { + var testView = new TestView(viewRef); + this.renderer.setEventDispatcher(viewRef, new LoggingEventDispatcher(testView.events)); + return testView; + } + + createRootView(rootProtoView:ProtoViewDto):TestView { + var viewRef = this.renderer.createInPlaceHostView(null, '#root', rootProtoView.render); + this.renderer.hydrateView(viewRef); + return this._createTestView(viewRef); + } + + createComponentView(parentViewRef:RenderViewRef, boundElementIndex:number, componentProtoView:ProtoViewDto):TestView { + var componentViewRef = this.renderer.createView(componentProtoView.render); + this.renderer.attachComponentView(parentViewRef, boundElementIndex, componentViewRef); + this.renderer.hydrateView(componentViewRef); + return this._createTestView(componentViewRef); + } + + createRootViews(protoViews:List):List { + var views = []; + var lastView = this.createRootView(protoViews[0]); + ListWrapper.push(views, lastView); + for (var i=1; i; - - constructor({urlData, viewCacheCapacity, shadowDomStrategy, templates}) { - this._templates = MapWrapper.create(); - if (isPresent(templates)) { - ListWrapper.forEach(templates, (template) => { - MapWrapper.set(this._templates, template.componentId, template); - }); - } - var parser = new Parser(new Lexer()); - var urlResolver = new UrlResolver(); - if (isBlank(shadowDomStrategy)) { - shadowDomStrategy = new EmulatedUnscopedShadowDomStrategy(new StyleUrlResolver(urlResolver), null); - } - this.renderCompiler = new DefaultDomCompiler(parser, shadowDomStrategy, new FakeTemplateLoader(urlResolver, urlData)); - - if (isBlank(viewCacheCapacity)) { - viewCacheCapacity = 0; - } - if (isBlank(urlData)) { - urlData = MapWrapper.create(); - } - this.eventPlugin = new FakeEventManagerPlugin(); - var eventManager = new EventManager([this.eventPlugin], new FakeVmTurnZone()); - var viewFactory = new ViewFactory(viewCacheCapacity, eventManager, shadowDomStrategy); - var viewHydrator = new RenderViewHydrator(eventManager, viewFactory, shadowDomStrategy); - this.renderer = new DirectDomRenderer(viewFactory, viewHydrator, shadowDomStrategy); - } - - compileRoot(componentMetadata):Promise { - return this.renderCompiler.compileHost(componentMetadata).then( (rootProtoView) => { - return this._compileNestedProtoViews(rootProtoView, [componentMetadata]); - }); - } - - compile(componentId):Promise { - var childTemplate = MapWrapper.get(this._templates, componentId); - if (isBlank(childTemplate)) { - throw new BaseException(`No template for component ${componentId}`); - } - return this.renderCompiler.compile(childTemplate).then( (protoView) => { - return this._compileNestedProtoViews(protoView, childTemplate.directives); - }); - } - - _compileNestedProtoViews(protoView, directives):Promise { - var childComponentRenderPvRefs = []; - var nestedPVPromises = []; - ListWrapper.forEach(protoView.elementBinders, (elementBinder) => { - var nestedComponentId = null; - ListWrapper.forEach(elementBinder.directives, (db) => { - var directiveMeta = directives[db.directiveIndex]; - if (directiveMeta.type === DirectiveMetadata.COMPONENT_TYPE) { - nestedComponentId = directiveMeta.id; - } - }); - var nestedCall; - if (isPresent(nestedComponentId)) { - var childTemplate = MapWrapper.get(this._templates, nestedComponentId); - if (isBlank(childTemplate)) { - // dynamic component - ListWrapper.push(childComponentRenderPvRefs, null); - } else { - nestedCall = this.compile(nestedComponentId); - } - } else if (isPresent(elementBinder.nestedProtoView)) { - nestedCall = this._compileNestedProtoViews(elementBinder.nestedProtoView, directives); - } - if (isPresent(nestedCall)) { - ListWrapper.push( - nestedPVPromises, - nestedCall.then( (nestedPv) => { - elementBinder.nestedProtoView = nestedPv; - if (isPresent(nestedComponentId)) { - ListWrapper.push(childComponentRenderPvRefs, nestedPv.render); - } - }) - ); - } - }); - if (nestedPVPromises.length > 0) { - return PromiseWrapper.all(nestedPVPromises).then((_) => { - this.renderCompiler.mergeChildComponentProtoViews(protoView.render, childComponentRenderPvRefs); - return protoView; - }); - } else { - return PromiseWrapper.resolve(protoView); - } - } - -} - - -class FakeTemplateLoader extends TemplateLoader { - _urlData: Map; - - constructor(urlResolver, urlData) { - super(null, urlResolver); - this._urlData = urlData; - } - - load(template: ViewDefinition) { - if (isPresent(template.template)) { - return PromiseWrapper.resolve(DOM.createTemplate(template.template)); - } - - if (isPresent(template.absUrl)) { - var content = this._urlData[template.absUrl]; - if (isPresent(content)) { - return PromiseWrapper.resolve(DOM.createTemplate(content)); - } - } - - return PromiseWrapper.reject('Load failed'); - } -} - -export class FakeVmTurnZone extends VmTurnZone { - constructor() { - super({enableLongStackTrace: false}); - } - - run(fn) { - fn(); - } - - runOutsideAngular(fn) { - fn(); - } -} - -export class FakeEventManagerPlugin extends EventManagerPlugin { - _eventHandlers: Map; - - constructor() { - super(); - this._eventHandlers = MapWrapper.create(); - } - - dispatchEvent(eventName, event) { - MapWrapper.get(this._eventHandlers, eventName)(event); - } - - supports(eventName: string): boolean { - return true; - } - - addEventListener(element, eventName: string, handler: Function, shouldSupportBubble: boolean) { - MapWrapper.set(this._eventHandlers, eventName, handler); - return () => {MapWrapper.delete(this._eventHandlers, eventName);} - } -} - -export class LoggingEventDispatcher extends EventDispatcher { - log:List; - constructor() { - super(); - this.log = []; - } - dispatchEvent( - elementIndex:number, eventName:string, locals:Map - ) { - ListWrapper.push(this.log, [elementIndex, eventName, locals]); - } -} - -export class FakeEvent { - target; - constructor(target) { - this.target = target; - } -} diff --git a/modules/angular2/test/render/dom/shadow_dom/content_tag_spec.js b/modules/angular2/test/render/dom/shadow_dom/content_tag_spec.js index 07b089f4ec..ae4165720c 100644 --- a/modules/angular2/test/render/dom/shadow_dom/content_tag_spec.js +++ b/modules/angular2/test/render/dom/shadow_dom/content_tag_spec.js @@ -23,7 +23,7 @@ export function main() { it("should insert the nodes", () => { var c = new Content(content, ''); - c.hydrate(null); + c.init(null); c.insert([el(""), el("")]) expect(DOM.getInnerHTML(parent)).toEqual(`${_scriptStart}${_scriptEnd}`); @@ -31,7 +31,7 @@ export function main() { it("should remove the nodes from the previous insertion", () => { var c = new Content(content, ''); - c.hydrate(null); + c.init(null); c.insert([el("")]); c.insert([el("")]); @@ -40,7 +40,7 @@ export function main() { it("should insert empty list", () => { var c = new Content(content, ''); - c.hydrate(null); + c.init(null); c.insert([el("")]); c.insert([]); diff --git a/modules/angular2/test/render/dom/shadow_dom/emulated_scoped_shadow_dom_strategy_spec.js b/modules/angular2/test/render/dom/shadow_dom/emulated_scoped_shadow_dom_strategy_spec.js index 7ba706bba3..4bcca815f5 100644 --- a/modules/angular2/test/render/dom/shadow_dom/emulated_scoped_shadow_dom_strategy_spec.js +++ b/modules/angular2/test/render/dom/shadow_dom/emulated_scoped_shadow_dom_strategy_spec.js @@ -28,7 +28,6 @@ import { import {UrlResolver} from 'angular2/src/services/url_resolver'; import {StyleUrlResolver} from 'angular2/src/render/dom/shadow_dom/style_url_resolver'; import {StyleInliner} from 'angular2/src/render/dom/shadow_dom/style_inliner'; -import {DomView} from 'angular2/src/render/dom/view/view'; export function main() { describe('EmulatedScopedShadowDomStrategy', () => { @@ -44,15 +43,9 @@ export function main() { resetShadowDomCache(); }); - it('should attach the view nodes as child of the host element', () => { + it('should use the host element as shadow root', () => { var host = el('
original content
'); - var originalChild = DOM.childNodes(host)[0]; - var nodes = el('
view
'); - var view = new DomView(null, [nodes], [], [], []); - - strategy.attachTemplate(host, view); - expect(DOM.childNodes(host)[0]).toBe(originalChild); - expect(DOM.childNodes(host)[1]).toBe(nodes); + expect(strategy.prepareShadowRoot(host)).toBe(host); }); it('should rewrite style urls', () => { diff --git a/modules/angular2/test/render/dom/shadow_dom/emulated_unscoped_shadow_dom_strategy_spec.js b/modules/angular2/test/render/dom/shadow_dom/emulated_unscoped_shadow_dom_strategy_spec.js index 80fe547bda..eb537ca182 100644 --- a/modules/angular2/test/render/dom/shadow_dom/emulated_unscoped_shadow_dom_strategy_spec.js +++ b/modules/angular2/test/render/dom/shadow_dom/emulated_unscoped_shadow_dom_strategy_spec.js @@ -23,7 +23,6 @@ import { } from 'angular2/src/render/dom/shadow_dom/util'; import {UrlResolver} from 'angular2/src/services/url_resolver'; import {StyleUrlResolver} from 'angular2/src/render/dom/shadow_dom/style_url_resolver'; -import {DomView} from 'angular2/src/render/dom/view/view'; export function main() { var strategy; @@ -39,15 +38,9 @@ export function main() { resetShadowDomCache(); }); - it('should attach the view nodes as child of the host element', () => { + it('should use the host element as shadow root', () => { var host = el('
original content
'); - var originalChild = DOM.childNodes(host)[0]; - var nodes = el('
view
'); - var view = new DomView(null, [nodes], [], [], []); - - strategy.attachTemplate(host, view); - expect(DOM.childNodes(host)[0]).toBe(originalChild); - expect(DOM.childNodes(host)[1]).toBe(nodes); + expect(strategy.prepareShadowRoot(host)).toBe(host); }); it('should rewrite style urls', () => { diff --git a/modules/angular2/test/render/dom/shadow_dom/light_dom_spec.js b/modules/angular2/test/render/dom/shadow_dom/light_dom_spec.js index e87fbd6cd6..4b72583329 100644 --- a/modules/angular2/test/render/dom/shadow_dom/light_dom_spec.js +++ b/modules/angular2/test/render/dom/shadow_dom/light_dom_spec.js @@ -5,7 +5,7 @@ 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 {ViewContainer} from 'angular2/src/render/dom/view/view_container'; +import {DomViewContainer} from 'angular2/src/render/dom/view/view_container'; @proxy @IMPLEMENTS(DomView) @@ -44,7 +44,7 @@ class FakeView { } @proxy -@IMPLEMENTS(ViewContainer) +@IMPLEMENTS(DomViewContainer) class FakeViewContainer { _nodes; _contentTagContainers; @@ -96,6 +96,11 @@ class FakeContentTag { } } +function createLightDom(hostView, shadowView, el) { + var lightDom = new LightDom(hostView, el); + lightDom.attachShadowDomView(shadowView); + return lightDom; +} export function main() { describe('LightDom', function() { @@ -110,7 +115,7 @@ export function main() { var tag = new FakeContentTag(el('')); var shadowDomView = new FakeView([tag]); - var lightDom = new LightDom(lightDomView, shadowDomView, + var lightDom = createLightDom(lightDomView, shadowDomView, el("
")); expect(lightDom.contentTags()).toEqual([tag]); @@ -122,7 +127,7 @@ export function main() { new FakeView([tag]) ]); var shadowDomView = new FakeView([vc]); - var lightDom = new LightDom(lightDomView, shadowDomView, + var lightDom = createLightDom(lightDomView, shadowDomView, el("
")); expect(lightDom.contentTags()).toEqual([tag]); @@ -132,13 +137,13 @@ export function main() { describe("expandedDomNodes", () => { it("should contain root nodes", () => { var lightDomEl = el("
") - var lightDom = new LightDom(lightDomView, new FakeView(), lightDomEl); + var lightDom = createLightDom(lightDomView, new FakeView(), lightDomEl); expect(toHtml(lightDom.expandedDomNodes())).toEqual([""]); }); it("should include view container nodes", () => { var lightDomEl = el("
"); - var lightDom = new LightDom( + var lightDom = createLightDom( new FakeView([ new FakeViewContainer( DOM.firstChild(lightDomEl), // template element @@ -153,7 +158,7 @@ export function main() { it("should include content nodes", () => { var lightDomEl = el("
"); - var lightDom = new LightDom( + var lightDom = createLightDom( new FakeView([ new FakeContentTag( DOM.firstChild(lightDomEl), // content element @@ -172,7 +177,7 @@ export function main() { var lightDomView = new FakeView(); - var lightDom = new LightDom( + var lightDom = createLightDom( lightDomView, new FakeView(), lightDomEl); @@ -188,7 +193,7 @@ export function main() { var lightDomEl = el("") - var lightDom = new LightDom(lightDomView, new FakeView([ + var lightDom = createLightDom(lightDomView, new FakeView([ contentA, contentB ]), lightDomEl); @@ -205,7 +210,7 @@ export function main() { var lightDomEl = el("") - var lightDom = new LightDom(lightDomView, new FakeView([ + var lightDom = createLightDom(lightDomView, new FakeView([ wildcard, contentB ]), lightDomEl); @@ -219,7 +224,7 @@ export function main() { it("should remove all nodes if there are no content tags", () => { var lightDomEl = el("") - var lightDom = new LightDom(lightDomView, new FakeView([]), lightDomEl); + var lightDom = createLightDom(lightDomView, new FakeView([]), lightDomEl); lightDom.redistribute(); @@ -230,7 +235,7 @@ export function main() { var lightDomEl = el(""); var bNode = DOM.childNodes(lightDomEl)[1]; - var lightDom = new LightDom(lightDomView, new FakeView([ + var lightDom = createLightDom(lightDomView, new FakeView([ new FakeContentTag(null, "a") ]), lightDomEl); diff --git a/modules/angular2/test/render/dom/shadow_dom/native_shadow_dom_strategy_spec.js b/modules/angular2/test/render/dom/shadow_dom/native_shadow_dom_strategy_spec.js index e43a551e46..daa3fe8e03 100644 --- a/modules/angular2/test/render/dom/shadow_dom/native_shadow_dom_strategy_spec.js +++ b/modules/angular2/test/render/dom/shadow_dom/native_shadow_dom_strategy_spec.js @@ -17,9 +17,7 @@ import { } from 'angular2/src/render/dom/shadow_dom/native_shadow_dom_strategy'; import {UrlResolver} from 'angular2/src/services/url_resolver'; import {StyleUrlResolver} from 'angular2/src/render/dom/shadow_dom/style_url_resolver'; -import {DomView} from 'angular2/src/render/dom/view/view'; -import {isPresent, isBlank} from 'angular2/src/facade/lang'; import {DOM} from 'angular2/src/dom/dom_adapter'; export function main() { @@ -32,15 +30,9 @@ export function main() { strategy = new NativeShadowDomStrategy(styleUrlResolver); }); - it('should attach the view nodes to the shadow root', () => { + it('should use the native shadow root', () => { var host = el('
original content
'); - var nodes = el('
view
'); - var view = new DomView(null, [nodes], [], [], []); - - strategy.attachTemplate(host, view); - var shadowRoot = DOM.getShadowRoot(host); - expect(isPresent(shadowRoot)).toBeTruthy(); - expect(shadowRoot).toHaveText('view'); + expect(strategy.prepareShadowRoot(host)).toBe(DOM.getShadowRoot(host)); }); it('should rewrite style urls', () => { diff --git a/modules/angular2/test/render/dom/shadow_dom_emulation_integration_spec.js b/modules/angular2/test/render/dom/shadow_dom_emulation_integration_spec.js index a22e07f7a1..2195edf2ac 100644 --- a/modules/angular2/test/render/dom/shadow_dom_emulation_integration_spec.js +++ b/modules/angular2/test/render/dom/shadow_dom_emulation_integration_spec.js @@ -10,128 +10,118 @@ import { 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 { - ProtoViewDto, ViewDefinition, RenderViewContainerRef, DirectiveMetadata + 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 {UrlResolver} from 'angular2/src/services/url_resolver'; import {StyleUrlResolver} from 'angular2/src/render/dom/shadow_dom/style_url_resolver'; import {StyleInliner} from 'angular2/src/render/dom/shadow_dom/style_inliner'; -import {IntegrationTestbed} from './integration_testbed'; +import {DomTestbed} from './dom_testbed'; export function main() { describe('ShadowDom integration tests', function() { - var urlResolver, styleUrlResolver, styleInliner; var strategies = { - "scoped" : () => new EmulatedScopedShadowDomStrategy(styleInliner, styleUrlResolver, null), - "unscoped" : () => new EmulatedUnscopedShadowDomStrategy(styleUrlResolver, null) + "scoped" : bind(ShadowDomStrategy).toFactory( + (styleInliner, styleUrlResolver) => new EmulatedScopedShadowDomStrategy(styleInliner, styleUrlResolver, null), + [StyleInliner, StyleUrlResolver]), + "unscoped" : bind(ShadowDomStrategy).toFactory( + (styleUrlResolver) => new EmulatedUnscopedShadowDomStrategy(styleUrlResolver, null), + [StyleUrlResolver]) } if (DOM.supportsNativeShadowDOM()) { - StringMapWrapper.set(strategies, "native", () => new NativeShadowDomStrategy(styleUrlResolver)); + StringMapWrapper.set(strategies, "native", bind(ShadowDomStrategy).toFactory( + (styleUrlResolver) => new NativeShadowDomStrategy(styleUrlResolver), + [StyleUrlResolver]) + ); } - beforeEach( () => { - urlResolver = new UrlResolver(); - styleUrlResolver = new StyleUrlResolver(urlResolver); - styleInliner = new StyleInliner(null, styleUrlResolver, urlResolver); - }); - - StringMapWrapper.forEach(strategies, - (strategyFactory, name) => { + (strategyBinding, name) => { + + beforeEachBindings( () => { + return [strategyBinding, DomTestbed]; + }); describe(`${name} shadow dom strategy`, () => { - var testbed, renderer, rootEl, compile, compileRoot; - - function createRenderer({templates, viewCacheCapacity}) { - testbed = new IntegrationTestbed({ - shadowDomStrategy: strategyFactory(), - templates: ListWrapper.concat(templates, componentTemplates), - viewCacheCapacity: viewCacheCapacity - }); - renderer = testbed.renderer; - compileRoot = (rootEl) => testbed.compileRoot(rootEl); - compile = (componentId) => testbed.compile(componentId); - } - - beforeEach( () => { - rootEl = el('
'); - }); - - it('should support simple components', inject([AsyncTestCompleter], (async) => { - createRenderer({ - templates: [new ViewDefinition({ + it('should support simple components', inject([AsyncTestCompleter, DomTestbed], (async, tb) => { + tb.compileAll([ + mainDir, + new ViewDefinition({ componentId: 'main', template: '' + '
A
' + '
', directives: [simple] - })] - }); - compileRoot(mainDir).then( (pv) => { - renderer.createInPlaceHostView(null, rootEl, pv.render); + }), + simpleTemplate + ]).then( (protoViews) => { + tb.createRootViews(protoViews); - expect(rootEl).toHaveText('SIMPLE(A)'); + expect(tb.rootEl).toHaveText('SIMPLE(A)'); async.done(); }); })); - it('should not show the light dom event if there is not content tag', inject([AsyncTestCompleter], (async) => { - createRenderer({ - templates: [new ViewDefinition({ + 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: '' + '
A
' + '
', directives: [empty] - })] - }); - compileRoot(mainDir).then( (pv) => { - renderer.createInPlaceHostView(null, rootEl, pv.render); + }), + emptyTemplate + ]).then( (protoViews) => { + tb.createRootViews(protoViews); - expect(rootEl).toHaveText(''); + expect(tb.rootEl).toHaveText(''); async.done(); }); })); - it('should support dynamic components', inject([AsyncTestCompleter], (async) => { - createRenderer({ - templates: [new ViewDefinition({ + it('should support dynamic components', inject([AsyncTestCompleter, DomTestbed], (async, tb) => { + tb.compileAll([ + mainDir, + new ViewDefinition({ componentId: 'main', template: '' + '
A
' + '
', directives: [dynamicComponent] - })] - }); - compileRoot(mainDir).then( (rootPv) => { - compile('simple').then( (simplePv) => { - var views = renderer.createInPlaceHostView(null, rootEl, rootPv.render); - renderer.createDynamicComponentView(views[1], 0, simplePv.render); + }), + simpleTemplate + ]).then( (protoViews) => { + var views = tb.createRootViews(ListWrapper.slice(protoViews, 0, 2)); + tb.createComponentView(views[1].viewRef, 0, protoViews[2]); - expect(rootEl).toHaveText('SIMPLE(A)'); + expect(tb.rootEl).toHaveText('SIMPLE(A)'); - async.done(); - }); + async.done(); }); })); - it('should support multiple content tags', inject([AsyncTestCompleter], (async) => { - createRenderer({ - templates: [new ViewDefinition({ + it('should support multiple content tags', inject([AsyncTestCompleter, DomTestbed], (async, tb) => { + tb.compileAll([ + mainDir, + new ViewDefinition({ componentId: 'main', template: '' + '
B
' + @@ -139,120 +129,126 @@ export function main() { '
A
' + '
', directives: [multipleContentTagsComponent] - })] - }); - compileRoot(mainDir).then( (pv) => { - renderer.createInPlaceHostView(null, rootEl, pv.render); + }), + multipleContentTagsTemplate + ]).then( (protoViews) => { + tb.createRootViews(protoViews); - expect(rootEl).toHaveText('(A, BC)'); + expect(tb.rootEl).toHaveText('(A, BC)'); async.done(); }); })); - it('should redistribute only direct children', inject([AsyncTestCompleter], (async) => { - createRenderer({ - templates: [new ViewDefinition({ + it('should redistribute only direct children', inject([AsyncTestCompleter, DomTestbed], (async, tb) => { + tb.compileAll([ + mainDir, + new ViewDefinition({ componentId: 'main', template: '' + '
B
A
' + '
C
' + '
', directives: [multipleContentTagsComponent] - })] - }); - compileRoot(mainDir).then( (pv) => { - renderer.createInPlaceHostView(null, rootEl, pv.render); + }), + multipleContentTagsTemplate + ]).then( (protoViews) => { + tb.createRootViews(protoViews); - expect(rootEl).toHaveText('(, BAC)'); + expect(tb.rootEl).toHaveText('(, BAC)'); async.done(); }); })); - it("should redistribute direct child viewcontainers when the light dom changes", inject([AsyncTestCompleter], (async) => { - createRenderer({ - templates: [new ViewDefinition({ + it("should redistribute direct child viewcontainers when the light dom changes", + inject([AsyncTestCompleter, DomTestbed], (async, tb) => { + tb.compileAll([ + mainDir, + new ViewDefinition({ componentId: 'main', template: '' + '
A
' + '
B
' + '
', directives: [multipleContentTagsComponent, manualViewportDirective] - })] - }); - compileRoot(mainDir).then( (pv) => { - var viewRefs = renderer.createInPlaceHostView(null, rootEl, pv.render); - var vcRef = new RenderViewContainerRef(viewRefs[1], 1); - var vcProtoViewRef = pv.elementBinders[0].nestedProtoView - .elementBinders[1].nestedProtoView.render; - expect(rootEl).toHaveText('(, B)'); + }), + multipleContentTagsTemplate + ]).then( (protoViews) => { + var views = tb.createRootViews(protoViews); + var childProtoView = protoViews[1].elementBinders[1].nestedProtoView; + expect(tb.rootEl).toHaveText('(, B)'); - renderer.createViewInContainer(vcRef, 0, vcProtoViewRef)[0]; + var childView = tb.createViewInContainer(views[1].viewRef, 1, 0, childProtoView); - expect(rootEl).toHaveText('(, AB)'); + expect(tb.rootEl).toHaveText('(, AB)'); - renderer.destroyViewInContainer(vcRef, 0); + tb.destroyViewInContainer(views[1].viewRef, 1, 0, childView.viewRef); - expect(rootEl).toHaveText('(, B)'); + expect(tb.rootEl).toHaveText('(, B)'); async.done(); }); })); - it("should redistribute when the light dom changes", inject([AsyncTestCompleter], (async) => { - createRenderer({ - templates: [new ViewDefinition({ + it("should redistribute when the light dom changes", + inject([AsyncTestCompleter, DomTestbed], (async, tb) => { + tb.compileAll([ + mainDir, + new ViewDefinition({ componentId: 'main', template: '' + '
A
' + '
B
' + '
', directives: [multipleContentTagsComponent, manualViewportDirective] - })] - }); - compileRoot(mainDir).then( (pv) => { - var viewRefs = renderer.createInPlaceHostView(null, rootEl, pv.render); - var vcRef = new RenderViewContainerRef(viewRefs[1], 1); - var vcProtoViewRef = pv.elementBinders[0].nestedProtoView - .elementBinders[1].nestedProtoView.render; - expect(rootEl).toHaveText('(, B)'); + }), + multipleContentTagsTemplate + ]).then( (protoViews) => { + var views = tb.createRootViews(protoViews); + var childProtoView = protoViews[1].elementBinders[1].nestedProtoView; - renderer.createViewInContainer(vcRef, 0, vcProtoViewRef)[0]; + expect(tb.rootEl).toHaveText('(, B)'); - expect(rootEl).toHaveText('(A, B)'); + var childView = tb.createViewInContainer(views[1].viewRef, 1, 0, childProtoView); - renderer.destroyViewInContainer(vcRef, 0); + expect(tb.rootEl).toHaveText('(A, B)'); - expect(rootEl).toHaveText('(, B)'); + tb.destroyViewInContainer(views[1].viewRef, 1, 0, childView.viewRef); + + expect(tb.rootEl).toHaveText('(, B)'); async.done(); }); })); - it("should support nested components", inject([AsyncTestCompleter], (async) => { - createRenderer({ - templates: [new ViewDefinition({ + it("should support nested components", inject([AsyncTestCompleter, DomTestbed], (async, tb) => { + tb.compileAll([ + mainDir, + new ViewDefinition({ componentId: 'main', template: '' + '
A
' + '
B
' + '
', directives: [outerWithIndirectNestedComponent] - })] - }); - compileRoot(mainDir).then( (pv) => { - renderer.createInPlaceHostView(null, rootEl, pv.render); + }), + outerWithIndirectNestedTemplate, + simpleTemplate + ]).then( (protoViews) => { + tb.createRootViews(protoViews); - expect(rootEl).toHaveText('OUTER(SIMPLE(AB))'); + expect(tb.rootEl).toHaveText('OUTER(SIMPLE(AB))'); async.done(); }); })); - it("should support nesting with content being direct child of a nested component", inject([AsyncTestCompleter], (async) => { - createRenderer({ - templates: [new ViewDefinition({ + 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: '' + '
A
' + @@ -260,25 +256,27 @@ export function main() { '
C
' + '
', directives: [outerComponent, manualViewportDirective] - })] - }); - compileRoot(mainDir).then( (pv) => { - var viewRefs = renderer.createInPlaceHostView(null, rootEl, pv.render); - var vcRef = new RenderViewContainerRef(viewRefs[1], 1); - var vcProtoViewRef = pv.elementBinders[0].nestedProtoView - .elementBinders[1].nestedProtoView.render; - expect(rootEl).toHaveText('OUTER(INNER(INNERINNER(,BC)))'); + }), + outerTemplate, + innerTemplate, + innerInnerTemplate + ]).then( (protoViews) => { + var views = tb.createRootViews(protoViews); + var childProtoView = protoViews[1].elementBinders[1].nestedProtoView; - renderer.createViewInContainer(vcRef, 0, vcProtoViewRef)[0]; + expect(tb.rootEl).toHaveText('OUTER(INNER(INNERINNER(,BC)))'); - expect(rootEl).toHaveText('OUTER(INNER(INNERINNER(A,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], (async) => { - createRenderer({ - templates: [new ViewDefinition({ + it('should redistribute when the shadow dom changes', inject([AsyncTestCompleter, DomTestbed], (async, tb) => { + tb.compileAll([ + mainDir, + new ViewDefinition({ componentId: 'main', template: '' + '
A
' + @@ -286,64 +284,68 @@ export function main() { '
C
' + '
', directives: [conditionalContentComponent] - })] - }); - compileRoot(mainDir).then( (pv) => { - var viewRefs = renderer.createInPlaceHostView(null, rootEl, pv.render); - var vcRef = new RenderViewContainerRef(viewRefs[2], 0); - var vcProtoViewRef = pv.elementBinders[0].nestedProtoView - .elementBinders[0].nestedProtoView - .elementBinders[0].nestedProtoView.render; + }), + conditionalContentTemplate + ]).then( (protoViews) => { + var views = tb.createRootViews(protoViews); + var childProtoView = protoViews[2].elementBinders[0].nestedProtoView; - expect(rootEl).toHaveText('(, ABC)'); + expect(tb.rootEl).toHaveText('(, ABC)'); - renderer.createViewInContainer(vcRef, 0, vcProtoViewRef)[0]; + var childView = tb.createViewInContainer(views[2].viewRef, 0, 0, childProtoView); - expect(rootEl).toHaveText('(A, BC)'); + expect(tb.rootEl).toHaveText('(A, BC)'); - renderer.destroyViewInContainer(vcRef, 0); + tb.destroyViewInContainer(views[2].viewRef, 0, 0, childView.viewRef); - expect(rootEl).toHaveText('(, ABC)'); + expect(tb.rootEl).toHaveText('(, ABC)'); async.done(); }); })); - it("should support tabs with view caching", inject([AsyncTestCompleter], (async) => { - createRenderer({ - templates: [new ViewDefinition({ + it("should support tabs with view caching", inject([AsyncTestCompleter, DomTestbed], (async, tb) => { + tb.compileAll([ + mainDir, + new ViewDefinition({ componentId: 'main', template: '(0'+ '1'+ '2)', directives: [tabComponent] - })], - viewCacheCapacity: 5 - }); - compileRoot(mainDir).then( (pv) => { - var viewRefs = renderer.createInPlaceHostView(null, rootEl, pv.render); - var vcRef0 = new RenderViewContainerRef(viewRefs[2], 0); - var vcRef1 = new RenderViewContainerRef(viewRefs[3], 0); - var vcRef2 = new RenderViewContainerRef(viewRefs[4], 0); - var mainPv = pv.elementBinders[0].nestedProtoView; - var pvRef = mainPv.elementBinders[0].nestedProtoView.elementBinders[0].nestedProtoView.render; + }), + tabTemplate + ]).then( (protoViews) => { + var views = tb.createRootViews(ListWrapper.slice(protoViews, 0, 2)); + var tabProtoView = protoViews[2]; + var tabChildProtoView = tabProtoView.elementBinders[0].nestedProtoView; - expect(rootEl).toHaveText('()'); + 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); - renderer.createViewInContainer(vcRef0, 0, pvRef); + expect(tb.rootEl).toHaveText('()'); - expect(rootEl).toHaveText('(TAB(0))'); + var tabChildView = tb.createViewInContainer(tab1View.viewRef, 0, 0, tabChildProtoView); - renderer.destroyViewInContainer(vcRef0, 0); - renderer.createViewInContainer(vcRef1, 0, pvRef); + expect(tb.rootEl).toHaveText('(TAB(0))'); - expect(rootEl).toHaveText('(TAB(1))'); + tb.renderer.dehydrateView(tabChildView.viewRef); + tb.renderer.detachViewInContainer(tab1View.viewRef, 0, 0, tabChildView.viewRef); - renderer.destroyViewInContainer(vcRef1, 0); - renderer.createViewInContainer(vcRef2, 0, pvRef); + tb.renderer.attachViewInContainer(tab2View.viewRef, 0, 0, tabChildView.viewRef); + tb.renderer.hydrateView(tabChildView.viewRef); - expect(rootEl).toHaveText('(TAB(2))'); + expect(tb.rootEl).toHaveText('(TAB(1))'); + + tb.renderer.dehydrateView(tabChildView.viewRef); + tb.renderer.detachViewInContainer(tab2View.viewRef, 0, 0, tabChildView.viewRef); + + tb.renderer.attachViewInContainer(tab3View.viewRef, 0, 0, tabChildView.viewRef); + tb.renderer.hydrateView(tabChildView.viewRef); + + expect(tb.rootEl).toHaveText('(TAB(2))'); async.done(); }); @@ -444,67 +446,62 @@ var autoViewportDirective = new DirectiveMetadata({ type: DirectiveMetadata.DIRECTIVE_TYPE }); -var tabGroupComponent = new DirectiveMetadata({ - selector: 'tab-group', - id: 'tab-group', - type: DirectiveMetadata.COMPONENT_TYPE -}); - var tabComponent = new DirectiveMetadata({ selector: 'tab', id: 'tab', type: DirectiveMetadata.COMPONENT_TYPE }); -var componentTemplates = [ - new ViewDefinition({ - componentId: 'simple', - template: 'SIMPLE()', - directives: [] - }), - new ViewDefinition({ - componentId: 'empty', - template: '', - directives: [] - }), - new ViewDefinition({ - componentId: 'multiple-content-tags', - template: '(, )', - directives: [] - }), - new ViewDefinition({ - componentId: 'outer-with-indirect-nested', - template: 'OUTER(
)', - directives: [simple] - }), - new ViewDefinition({ - componentId: 'outer', - template: 'OUTER()', - directives: [innerComponent] - }), - new ViewDefinition({ - componentId: 'inner', - template: 'INNER()', - directives: [innerInnerComponent] - }), - new ViewDefinition({ - componentId: 'innerinner', - template: 'INNERINNER(,)', - directives: [] - }), - new ViewDefinition({ - componentId: 'conditional-content', - template: '
(
, )
', - directives: [autoViewportDirective] - }), - new ViewDefinition({ - componentId: 'tab-group', - template: 'GROUP()', - directives: [] - }), - new ViewDefinition({ - componentId: 'tab', - template: '
TAB()
', - directives: [autoViewportDirective] - }) -]; +var simpleTemplate = new ViewDefinition({ + componentId: 'simple', + template: 'SIMPLE()', + directives: [] +}); + +var emptyTemplate = new ViewDefinition({ + componentId: 'empty', + template: '', + directives: [] +}); + +var multipleContentTagsTemplate = new ViewDefinition({ + componentId: 'multiple-content-tags', + template: '(, )', + directives: [] +}); + +var outerWithIndirectNestedTemplate = new ViewDefinition({ + componentId: 'outer-with-indirect-nested', + template: 'OUTER(
)', + directives: [simple] +}); + +var outerTemplate = new ViewDefinition({ + componentId: 'outer', + template: 'OUTER()', + directives: [innerComponent] +}); + +var innerTemplate = new ViewDefinition({ + componentId: 'inner', + template: 'INNER()', + directives: [innerInnerComponent] +}); + +var innerInnerTemplate = new ViewDefinition({ + componentId: 'innerinner', + template: 'INNERINNER(,)', + directives: [] +}); + +var conditionalContentTemplate = new ViewDefinition({ + componentId: 'conditional-content', + template: '
(
, )
', + directives: [autoViewportDirective] +}); + +var tabTemplate = new ViewDefinition({ + componentId: 'tab', + template: '
TAB()
', + directives: [autoViewportDirective] +}); diff --git a/modules/angular2/test/render/dom/view/view_factory_spec.js b/modules/angular2/test/render/dom/view/view_factory_spec.js deleted file mode 100644 index 13cc14ad59..0000000000 --- a/modules/angular2/test/render/dom/view/view_factory_spec.js +++ /dev/null @@ -1,183 +0,0 @@ -import { - AsyncTestCompleter, - beforeEach, - ddescribe, - xdescribe, - describe, - el, - dispatchEvent, - expect, - iit, - inject, - beforeEachBindings, - it, - xit, - SpyObject, proxy -} from 'angular2/test_lib'; -import {IMPLEMENTS, isBlank} from 'angular2/src/facade/lang'; -import {ListWrapper} from 'angular2/src/facade/collection'; -import {ViewFactory} from 'angular2/src/render/dom/view/view_factory'; -import {DomProtoView} from 'angular2/src/render/dom/view/proto_view'; -import {DomView} from 'angular2/src/render/dom/view/view'; -import {ElementBinder} from 'angular2/src/render/dom/view/element_binder'; -import {ShadowDomStrategy} from 'angular2/src/render/dom/shadow_dom/shadow_dom_strategy'; -import {LightDom} from 'angular2/src/render/dom/shadow_dom/light_dom' -import {EventManager} from 'angular2/src/render/dom/events/event_manager'; - -export function main() { - describe('RenderViewFactory', () => { - var eventManager; - var shadowDomStrategy; - - function createViewFactory({capacity}):ViewFactory { - return new ViewFactory(capacity, eventManager, shadowDomStrategy); - } - - function createProtoView(rootEl=null, binders=null) { - if (isBlank(rootEl)) { - rootEl = el('
'); - } - if (isBlank(binders)) { - binders = []; - } - return new DomProtoView({ - element: rootEl, - elementBinders: binders - }); - } - - function createComponentElBinder(componentId, nestedProtoView = null) { - var binder = new ElementBinder({ - componentId: componentId, - textNodeIndices: [] - }); - binder.nestedProtoView = nestedProtoView; - return binder; - } - - - beforeEach( () => { - eventManager = new SpyEventManager(); - shadowDomStrategy = new SpyShadowDomStrategy(); - }); - - it('should create views without cache', () => { - var pv = createProtoView(); - var vf = createViewFactory({ - capacity: 0 - }); - expect(vf.getView(pv) instanceof DomView).toBe(true); - }); - - describe('caching', () => { - - it('should support multiple RenderProtoViews', () => { - var pv1 = createProtoView(); - var pv2 = createProtoView(); - var vf = createViewFactory({ capacity: 2 }); - var view1 = vf.getView(pv1); - var view2 = vf.getView(pv2); - vf.returnView(view1); - vf.returnView(view2); - - expect(vf.getView(pv1)).toBe(view1); - expect(vf.getView(pv2)).toBe(view2); - }); - - it('should reuse the newest view that has been returned', () => { - var pv = createProtoView(); - var vf = createViewFactory({ capacity: 2 }); - var view1 = vf.getView(pv); - var view2 = vf.getView(pv); - vf.returnView(view1); - vf.returnView(view2); - - expect(vf.getView(pv)).toBe(view2); - }); - - it('should not add views when the capacity has been reached', () => { - var pv = createProtoView(); - var vf = createViewFactory({ capacity: 2 }); - var view1 = vf.getView(pv); - var view2 = vf.getView(pv); - var view3 = vf.getView(pv); - vf.returnView(view1); - vf.returnView(view2); - vf.returnView(view3); - - expect(vf.getView(pv)).toBe(view2); - expect(vf.getView(pv)).toBe(view1); - }); - - }); - - describe('child components', () => { - - var vf, log; - - beforeEach(() => { - vf = createViewFactory({capacity: 1}); - log = []; - shadowDomStrategy.spy('attachTemplate').andCallFake( (el, view) => { - ListWrapper.push(log, ['attachTemplate', el, view]); - }); - shadowDomStrategy.spy('constructLightDom').andCallFake( (lightDomView, shadowDomView, el) => { - ListWrapper.push(log, ['constructLightDom', lightDomView, shadowDomView, el]); - return new SpyLightDom(); - }); - }); - - it('should create static child component views', () => { - var hostPv = createProtoView(el('
'), [ - createComponentElBinder( - 'someComponent', - createProtoView() - ) - ]); - var hostView = vf.getView(hostPv); - var shadowView = hostView.componentChildViews[0]; - expect(shadowView).toBeTruthy(); - expect(hostView.lightDoms[0]).toBeTruthy(); - expect(log[0]).toEqual(['constructLightDom', hostView, shadowView, hostView.boundElements[0]]); - expect(log[1]).toEqual(['attachTemplate', hostView.boundElements[0], shadowView]); - }); - - it('should not create dynamic child component views', () => { - var hostPv = createProtoView(el('
'), [ - createComponentElBinder( - 'someComponent', - null - ) - ]); - var hostView = vf.getView(hostPv); - var shadowView = hostView.componentChildViews[0]; - expect(shadowView).toBeFalsy(); - expect(hostView.lightDoms[0]).toBeFalsy(); - expect(log).toEqual([]); - }); - - }); - - }); -} - -@proxy -@IMPLEMENTS(EventManager) -class SpyEventManager extends SpyObject { - constructor(){super(EventManager);} - noSuchMethod(m){return super.noSuchMethod(m)} -} - -@proxy -@IMPLEMENTS(ShadowDomStrategy) -class SpyShadowDomStrategy extends SpyObject { - constructor(){super(ShadowDomStrategy);} - noSuchMethod(m){return super.noSuchMethod(m)} -} - -@proxy -@IMPLEMENTS(LightDom) -class SpyLightDom extends SpyObject { - constructor(){super(LightDom);} - noSuchMethod(m){return super.noSuchMethod(m)} -} diff --git a/modules/angular2/test/render/dom/view/view_hydrator_spec.js b/modules/angular2/test/render/dom/view/view_hydrator_spec.js deleted file mode 100644 index a1ab5c57b7..0000000000 --- a/modules/angular2/test/render/dom/view/view_hydrator_spec.js +++ /dev/null @@ -1,285 +0,0 @@ -import { - AsyncTestCompleter, - beforeEach, - ddescribe, - xdescribe, - describe, - el, - dispatchEvent, - expect, - iit, - inject, - beforeEachBindings, - it, - xit, - SpyObject, proxy -} from 'angular2/test_lib'; -import {IMPLEMENTS, isBlank, isPresent} from 'angular2/src/facade/lang'; - -import {DomProtoView} from 'angular2/src/render/dom/view/proto_view'; -import {ElementBinder} from 'angular2/src/render/dom/view/element_binder'; -import {DomView} from 'angular2/src/render/dom/view/view'; -import {ShadowDomStrategy} from 'angular2/src/render/dom/shadow_dom/shadow_dom_strategy'; -import {LightDom} from 'angular2/src/render/dom/shadow_dom/light_dom'; -import {EventManager} from 'angular2/src/render/dom/events/event_manager'; -import {DOM} from 'angular2/src/dom/dom_adapter'; -import {ViewFactory} from 'angular2/src/render/dom/view/view_factory'; -import {RenderViewHydrator} from 'angular2/src/render/dom/view/view_hydrator'; - -export function main() { - - describe('RenderViewHydrator', () => { - var shadowDomStrategy; - var eventManager; - var viewFactory; - var viewHydrator; - - function createProtoView({rootEl, binders}={}) { - if (isBlank(rootEl)) { - rootEl = el('
'); - } - if (isBlank(binders)) { - binders = []; - } - return new DomProtoView({ - element: rootEl, - elementBinders: binders - }); - } - - function createComponentElBinder(componentId, nestedProtoView = null) { - var binder = new ElementBinder({ - componentId: componentId, - textNodeIndices: [] - }); - binder.nestedProtoView = nestedProtoView; - return binder; - } - - function createHostProtoView(nestedProtoView) { - return createProtoView({ - binders: [ - createComponentElBinder( - 'someComponent', - nestedProtoView - ) - ] - }); - } - - function createEmptyView() { - var root = el('
'); - return new DomView(createProtoView(), [DOM.childNodes(root)[0]], - [], [], []); - } - - function createHostView(pv, shadowDomView) { - var view = new DomView(pv, [el('
')], - [], [el('
')], [null]); - ViewFactory.setComponentView(shadowDomStrategy, view, 0, shadowDomView); - return view; - } - - function hydrate(view) { - viewHydrator.hydrateInPlaceHostView(null, view); - } - - function dehydrate(view) { - viewHydrator.dehydrateInPlaceHostView(null, view); - } - - beforeEach( () => { - eventManager = new SpyEventManager(); - shadowDomStrategy = new SpyShadowDomStrategy(); - shadowDomStrategy.spy('constructLightDom').andCallFake( (lightDomView, shadowDomView, el) => { - return new SpyLightDom(); - }); - viewFactory = new SpyViewFactory(); - viewHydrator = new RenderViewHydrator(eventManager, viewFactory, shadowDomStrategy); - }); - - describe('hydrateDynamicComponentView', () => { - - it('should redistribute', () => { - var shadowView = createEmptyView(); - var hostPv = createHostProtoView(createProtoView()); - var hostView = createHostView(hostPv, shadowView); - viewHydrator.hydrateDynamicComponentView(hostView, 0, shadowView); - var lightDomSpy:SpyLightDom = hostView.lightDoms[0]; - expect(lightDomSpy.spy('redistribute')).toHaveBeenCalled(); - }); - - }); - - describe('hydrateInPlaceHostView', () => { - - function createInPlaceHostView() { - var hostPv = createHostProtoView(createProtoView()); - var shadowView = createEmptyView(); - return createHostView(hostPv, shadowView); - } - - it('should hydrate the view', () => { - var hostView = createInPlaceHostView(); - viewHydrator.hydrateInPlaceHostView(null, hostView); - - expect(hostView.hydrated).toBe(true); - }); - - it('should store the view in the parent view', () => { - var parentView = createEmptyView(); - var hostView = createInPlaceHostView(); - - viewHydrator.hydrateInPlaceHostView(parentView, hostView); - - expect(parentView.imperativeHostViews).toEqual([hostView]); - - }); - - }); - - describe('dehydrateInPlaceHostView', () => { - - function createAndHydrateInPlaceHostView(parentView) { - var hostPv = createHostProtoView(createProtoView()); - var shadowView = createEmptyView(); - var hostView = createHostView(hostPv, shadowView); - viewHydrator.hydrateInPlaceHostView(parentView, hostView); - return hostView; - } - - it('should clear the host view', () => { - var parentView = createEmptyView(); - var hostView = createAndHydrateInPlaceHostView(parentView); - - var rootNodes = hostView.rootNodes; - expect(rootNodes[0].parentNode).toBeTruthy(); - - viewHydrator.dehydrateInPlaceHostView(parentView, hostView); - - expect(parentView.imperativeHostViews).toEqual([]); - expect(rootNodes[0].parentNode).toBeFalsy(); - expect(hostView.rootNodes).toEqual([]); - }); - - }); - - describe('hydrate... shared functionality', () => { - - it('should hydrate existing child components', () => { - var hostPv = createHostProtoView(createProtoView()); - var shadowView = createEmptyView(); - createHostView(hostPv, shadowView); - - hydrate(shadowView); - - expect(shadowView.hydrated).toBe(true); - }); - - }); - - describe('dehydrate... shared functionality', () => { - var hostView; - - function createAndHydrate(nestedProtoView, shadowView, imperativeHostView = null) { - var hostPv = createHostProtoView(nestedProtoView); - hostView = createHostView(hostPv, shadowView); - if (isPresent(imperativeHostView)) { - viewHydrator.hydrateInPlaceHostView(hostView, imperativeHostView); - } - - hydrate(hostView); - } - - it('should dehydrate child components', () => { - var shadowView = createEmptyView(); - createAndHydrate(createProtoView(), shadowView); - - expect(shadowView.hydrated).toBe(true); - dehydrate(hostView); - - expect(shadowView.hydrated).toBe(false); - }); - - it('should not clear static child components', () => { - var shadowView = createEmptyView(); - createAndHydrate(createProtoView(), shadowView); - dehydrate(hostView); - - expect(hostView.componentChildViews[0]).toBe(shadowView); - expect(shadowView.rootNodes[0].parentNode).toBeTruthy(); - expect(viewFactory.spy('returnView')).not.toHaveBeenCalled(); - }); - - it('should clear dynamic child components', () => { - var shadowView = createEmptyView(); - createAndHydrate(null, shadowView); - expect(shadowView.rootNodes[0].parentNode).toBeTruthy(); - - dehydrate(hostView); - - expect(hostView.componentChildViews[0]).toBe(null); - expect(shadowView.rootNodes[0].parentNode).toBe(null); - expect(viewFactory.spy('returnView')).toHaveBeenCalledWith(shadowView); - }); - - it('should clear views in ViewContainers', () => { - createAndHydrate(null, null); - var vc = hostView.getOrCreateViewContainer(0); - var childView = createEmptyView(); - vc.insert(childView); - - dehydrate(hostView); - - expect(viewFactory.spy('returnView')).toHaveBeenCalledWith(childView); - }); - - it('should clear imperatively added child components', () => { - var shadowView = createEmptyView(); - createAndHydrate(createProtoView(), shadowView); - var impHostView = createHostView(createHostProtoView(createProtoView()), createEmptyView()); - shadowView.imperativeHostViews = [impHostView]; - - var rootNodes = impHostView.rootNodes; - expect(rootNodes[0].parentNode).toBeTruthy(); - - dehydrate(hostView); - - expect(shadowView.imperativeHostViews).toEqual([]); - expect(impHostView.rootNodes).toEqual([]); - expect(rootNodes[0].parentNode).toBeFalsy(); - expect(viewFactory.spy('returnView')).toHaveBeenCalledWith(impHostView); - }); - - }); - - }); -} - -@proxy -@IMPLEMENTS(EventManager) -class SpyEventManager extends SpyObject { - constructor(){super(EventManager);} - noSuchMethod(m){return super.noSuchMethod(m)} -} - -@proxy -@IMPLEMENTS(ShadowDomStrategy) -class SpyShadowDomStrategy extends SpyObject { - constructor(){super(ShadowDomStrategy);} - noSuchMethod(m){return super.noSuchMethod(m)} -} - -@proxy -@IMPLEMENTS(LightDom) -class SpyLightDom extends SpyObject { - constructor(){super(LightDom);} - noSuchMethod(m){return super.noSuchMethod(m)} -} - -@proxy -@IMPLEMENTS(ViewFactory) -class SpyViewFactory extends SpyObject { - constructor(){super(ViewFactory);} - noSuchMethod(m){return super.noSuchMethod(m)} -} diff --git a/modules/angular2/test/render/dom/view/view_spec.js b/modules/angular2/test/render/dom/view/view_spec.js index 0e53797515..8411dc0fca 100644 --- a/modules/angular2/test/render/dom/view/view_spec.js +++ b/modules/angular2/test/render/dom/view/view_spec.js @@ -20,7 +20,6 @@ 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 {DomView} from 'angular2/src/render/dom/view/view'; -import {ViewContainer} from 'angular2/src/render/dom/view/view_container'; import {LightDom} from 'angular2/src/render/dom/shadow_dom/light_dom'; import {DOM} from 'angular2/src/dom/dom_adapter'; @@ -82,21 +81,6 @@ export function main() { }); - describe('getOrCreateViewContainer', () => { - it('should create a new container', () => { - var pv = createProtoView([new ElementBinder()]); - var view = createView(pv, 1); - expect(view.getOrCreateViewContainer(0) instanceof ViewContainer).toBe(true); - }); - - it('should return an existing container', () => { - var pv = createProtoView([new ElementBinder()]); - var view = createView(pv, 1); - var vc = view.getOrCreateViewContainer(0); - expect(view.getOrCreateViewContainer(0)).toBe(vc); - }); - }); - }); } diff --git a/modules/angular2_material/src/components/dialog/dialog.js b/modules/angular2_material/src/components/dialog/dialog.js index 10d27b144f..560ab9f991 100644 --- a/modules/angular2_material/src/components/dialog/dialog.js +++ b/modules/angular2_material/src/components/dialog/dialog.js @@ -22,6 +22,7 @@ import {View} from 'angular2/src/core/annotations_impl/view'; // TODO(jelbourn): Pre-built `alert` and `confirm` dialogs. // TODO(jelbourn): Animate dialog out of / into opening element. +var _nextDialogId = 0; /** * Service for opening modal dialogs. @@ -49,7 +50,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 = DOM.createElement('div'); + var dialogElement = this._createHostElement(); DOM.appendChild(DOM.query('body'), dialogElement); // TODO(jelbourn): Use hostProperties binding to set these once #1539 is fixed. @@ -75,7 +76,7 @@ export class MdDialog { // First, load the MdDialogContainer, into which the given component will be loaded. return this.componentLoader.loadIntoNewLocation( - MdDialogContainer, elementRef, dialogElement).then(containerRef => { + MdDialogContainer, elementRef, `:document#${dialogElement.id}`).then(containerRef => { dialogRef.containerRef = containerRef; // Now load the given component into the MdDialogContainer. @@ -101,12 +102,18 @@ export class MdDialog { /** Loads the dialog backdrop (transparent overlay over the rest of the page). */ _openBackdrop(elementRef:ElementRef, injector: Injector): Promise { - var backdropElement = DOM.createElement('div'); + var backdropElement = this._createHostElement(); DOM.addClass(backdropElement, 'md-backdrop'); DOM.appendChild(DOM.query('body'), backdropElement); return this.componentLoader.loadIntoNewLocation( - MdBackdrop, elementRef, backdropElement, injector); + MdBackdrop, elementRef, `:document#${backdropElement.id}`, injector); + } + + _createHostElement() { + var hostElement = DOM.createElement('div'); + hostElement.id = `mdDialog${_nextDialogId++}`; + return hostElement; } alert(message: string, okMessage: string): Promise { diff --git a/modules/benchmarks/src/tree/tree_benchmark.js b/modules/benchmarks/src/tree/tree_benchmark.js index ef381f2e78..c81d14c71f 100644 --- a/modules/benchmarks/src/tree/tree_benchmark.js +++ b/modules/benchmarks/src/tree/tree_benchmark.js @@ -16,13 +16,11 @@ import {getIntParameter, getStringParameter, bindAction} from 'angular2/src/test import {If} from 'angular2/directives'; import {BrowserDomAdapter} from 'angular2/src/dom/browser_adapter'; import {APP_VIEW_POOL_CAPACITY} from 'angular2/src/core/compiler/view_pool'; -import * as rvf from 'angular2/src/render/dom/view/view_factory'; import {bind} from 'angular2/di'; function createBindings():List { var viewCacheCapacity = getStringParameter('viewcache') == 'true' ? 10000 : 1; return [ - bind(rvf.VIEW_POOL_CAPACITY).toValue(viewCacheCapacity), bind(APP_VIEW_POOL_CAPACITY).toValue(viewCacheCapacity) ]; }