From bd4aa70a65132fe4da4d25a473fb71973ee5c22e Mon Sep 17 00:00:00 2001 From: vakrilov Date: Thu, 24 Nov 2016 10:40:20 +0200 Subject: [PATCH] FIX: Adding template anchors to ContentView --- nativescript-angular/directives/action-bar.ts | 10 +- nativescript-angular/element-registry.ts | 10 +- nativescript-angular/view-util.ts | 41 ++++--- ng-sample/app/app.ts | 41 ++++--- .../nativescript-angular-sync .js | 1 - tests/app/tests/renderer-tests.ts | 114 +++++++++++++++--- 6 files changed, 158 insertions(+), 59 deletions(-) delete mode 100644 ng-sample/hooks/before-livesync/nativescript-angular-sync .js diff --git a/nativescript-angular/directives/action-bar.ts b/nativescript-angular/directives/action-bar.ts index 5452e4dd5..c99093f41 100644 --- a/nativescript-angular/directives/action-bar.ts +++ b/nativescript-angular/directives/action-bar.ts @@ -1,9 +1,9 @@ import { Directive, Component, ElementRef, Optional } from "@angular/core"; import { ActionItem, ActionBar, NavigationButton } from "ui/action-bar"; import { isBlank } from "../lang-facade"; -import {Page} from "ui/page"; +import { Page } from "ui/page"; import { View } from "ui/core/view"; -import { registerElement, ViewClassMeta, NgView } from "../element-registry"; +import { registerElement, ViewClassMeta, NgView, TEMPLATE } from "../element-registry"; let actionBarMeta: ViewClassMeta = { skipAddToDom: true, @@ -17,7 +17,7 @@ let actionBarMeta: ViewClassMeta = { } else if (child instanceof ActionItem) { bar.actionItems.addItem(childView); childView.parent = bar; - } else if (child.nodeName === "template") { + } else if (child.nodeName === TEMPLATE) { child.templateParent = parent; } else if (child.nodeName !== "#text" && child instanceof View) { bar.titleView = childView; @@ -34,8 +34,8 @@ let actionBarMeta: ViewClassMeta = { } else if (child instanceof ActionItem) { bar.actionItems.removeItem(childView); childView.parent = null; - } else if (child.nodeName !== "template" && child instanceof View && - bar.titleView && bar.titleView === childView) { + } else if (child.nodeName !== TEMPLATE && child instanceof View && + bar.titleView && bar.titleView === childView) { bar.titleView = null; } }, diff --git a/nativescript-angular/element-registry.ts b/nativescript-angular/element-registry.ts index 948db15f1..b177e5257 100644 --- a/nativescript-angular/element-registry.ts +++ b/nativescript-angular/element-registry.ts @@ -1,12 +1,14 @@ -import {View} from "ui/core/view"; +import { View } from "ui/core/view"; export type ViewResolver = () => ViewClass; export type NgView = View & ViewExtensions; +export const TEMPLATE = "template"; export interface ViewClassMeta { skipAddToDom?: boolean; insertChild?: (parent: NgView, child: NgView, atIndex: number) => void; removeChild?: (parent: NgView, child: NgView) => void; + isTemplateAnchor?: boolean; } export interface ViewExtensions { @@ -24,7 +26,7 @@ const defaultViewMeta: ViewClassMeta = { skipAddToDom: false, }; -const elementMap = new Map(); +const elementMap = new Map(); const camelCaseSplit = /([a-z0-9])([A-Z])/g; export function registerElement( @@ -69,6 +71,10 @@ export function isKnownView(elementName: string): boolean { elementMap.has(elementName.toLowerCase()); } +// Empty view used for template anchors +export class TemplateView extends View { +} +registerElement(TEMPLATE, () => TemplateView, { isTemplateAnchor: true }); // Register default NativeScript components // Note: ActionBar related components are registerd together with action-bar directives. diff --git a/nativescript-angular/view-util.ts b/nativescript-angular/view-util.ts index 978449f2b..476b85336 100644 --- a/nativescript-angular/view-util.ts +++ b/nativescript-angular/view-util.ts @@ -1,21 +1,22 @@ -import {isString, isDefined} from "utils/types"; -import {View} from "ui/core/view"; -import {Placeholder} from "ui/placeholder"; -import {ContentView} from "ui/content-view"; -import {LayoutBase} from "ui/layouts/layout-base"; +import { isString, isDefined } from "utils/types"; +import { View } from "ui/core/view"; +import { Placeholder } from "ui/placeholder"; +import { ContentView } from "ui/content-view"; +import { LayoutBase } from "ui/layouts/layout-base"; import { ViewClass, getViewClass, getViewMeta, isKnownView, ViewExtensions, - NgView + NgView, + TEMPLATE } from "./element-registry"; -import {getSpecialPropertySetter} from "ui/builder/special-properties"; -import {StyleProperty, getPropertyByName, withStyleProperty} from "ui/styling/style-property"; -import {ValueSource} from "ui/core/dependency-observable"; -import {platformNames, Device} from "platform"; -import {rendererLog as traceLog, styleError} from "./trace"; +import { getSpecialPropertySetter } from "ui/builder/special-properties"; +import { StyleProperty, getPropertyByName, withStyleProperty } from "ui/styling/style-property"; +import { ValueSource } from "ui/core/dependency-observable"; +import { platformNames, Device } from "platform"; +import { rendererLog as traceLog, styleError } from "./trace"; const IOS_PREFX: string = ":ios:"; const ANDROID_PREFX: string = ":android:"; @@ -70,7 +71,12 @@ export class ViewUtil { parent.addChild(child); } } else if (isContentView(parent)) { - parent.content = child; + // Explicit handling of template anchors inside ContentView + if (child.meta.isTemplateAnchor) { + parent._addView(child, atIndex); + } else { + parent.content = child; + } } else if (parent && parent._addChildFromBuilder) { parent._addChildFromBuilder(child.nodeName, child); } else { @@ -91,6 +97,11 @@ export class ViewUtil { if (parent.content === child) { parent.content = null; } + + // Explicit handling of template anchors inside ContentView + if (child.meta.isTemplateAnchor) { + parent._removeView(child); + } } else if (isView(parent)) { parent._removeView(child); } else { @@ -152,10 +163,10 @@ export class ViewUtil { } public createTemplateAnchor(parentElement: NgView) { - // HACK: Using a ContentView here, so that it creates a native View object - const anchor = this.createAndAttach("template", ContentView, parentElement); - anchor.visibility = "collapse"; + const viewClass = getViewClass(TEMPLATE); + const anchor = this.createAndAttach(TEMPLATE, viewClass, parentElement); anchor.templateParent = parentElement; + traceLog("Created templateAnchor: " + anchor); return anchor; } diff --git a/ng-sample/app/app.ts b/ng-sample/app/app.ts index 41214301b..85017eb3f 100644 --- a/ng-sample/app/app.ts +++ b/ng-sample/app/app.ts @@ -19,9 +19,9 @@ import { Page } from "ui/page"; import { Color } from "color"; import trace = require("trace"); -// trace.setCategories(rendererTraceCategory); +trace.setCategories(rendererTraceCategory); // trace.setCategories(routerTraceCategory); -trace.setCategories(listViewTraceCategory); +// trace.setCategories(listViewTraceCategory); trace.enable(); import { RendererTest } from './examples/renderer-test'; @@ -114,28 +114,29 @@ const customPageFactoryProvider = { platformNativeScriptDynamic().bootstrapModule(makeExampleModule(RendererTest)); // platformNativeScriptDynamic(undefined, [customPageFactoryProvider]).bootstrapModule(makeExampleModule(RendererTest)); -//platformNativeScriptDynamic().bootstrapModule(makeExampleModule(TabViewTest)); -//platformNativeScriptDynamic().bootstrapModule(makeExampleModule(Benchmark)); -//platformNativeScriptDynamic().bootstrapModule(makeExampleModule(ListTest)); +// platformNativeScriptDynamic().bootstrapModule(makeExampleModule(TabViewTest)); +// platformNativeScriptDynamic().bootstrapModule(makeExampleModule(Benchmark)); +// platformNativeScriptDynamic().bootstrapModule(makeExampleModule(ListTest)); // platformNativeScriptDynamic().bootstrapModule(makeExampleModule(ListTemplateSelectorTest)); -//platformNativeScriptDynamic().bootstrapModule(makeExampleModule(ListTestAsync)); -//platformNativeScriptDynamic().bootstrapModule(makeExampleModule(ImageTest)); +// platformNativeScriptDynamic().bootstrapModule(makeExampleModule(ListTestAsync)); +// platformNativeScriptDynamic().bootstrapModule(makeExampleModule(ImageTest)); // platformNativeScriptDynamic().bootstrapModule(makeExampleModule(ModalTest)); -//platformNativeScriptDynamic().bootstrapModule(makeExampleModule(HttpTest)); -//platformNativeScriptDynamic().bootstrapModule(makeExampleModule(PlatfromDirectivesTest)); -//platformNativeScriptDynamic().bootstrapModule(makeExampleModule(ActionBarTest)); +// platformNativeScriptDynamic().bootstrapModule(makeExampleModule(HttpTest)); +// platformNativeScriptDynamic().bootstrapModule(makeExampleModule(PlatfromDirectivesTest)); +// platformNativeScriptDynamic().bootstrapModule(makeExampleModule(ActionBarTest)); -//new router -//platformNativeScriptDynamic().bootstrapModule(makeExampleModule(RouterOutletAppComponent)); +// router +// platformNativeScriptDynamic().bootstrapModule(makeExampleModule(RouterOutletAppComponent)); // platformNativeScriptDynamic().bootstrapModule(makeExampleModule(PageRouterOutletAppComponent)); -//platformNativeScriptDynamic().bootstrapModule(makeExampleModule(PageRouterOutletNestedAppComponent)); -//platformNativeScriptDynamic().bootstrapModule(makeExampleModule(ClearHistoryAppComponent)); -//platformNativeScriptDynamic().bootstrapModule(makeExampleModule(LoginAppComponent)); -//animations -//platformNativeScriptDynamic().bootstrapModule(makeExampleModule(AnimationStatesTest)); -//platformNativeScriptDynamic().bootstrapModule(makeExampleModule(AnimationNgClassTest)); -//platformNativeScriptDynamic().bootstrapModule(makeExampleModule(AnimationKeyframesTest)); -//platformNativeScriptDynamic().bootstrapModule(makeExampleModule(AnimationEnterLeaveTest)); +// platformNativeScriptDynamic().bootstrapModule(makeExampleModule(PageRouterOutletNestedAppComponent)); +// platformNativeScriptDynamic().bootstrapModule(makeExampleModule(ClearHistoryAppComponent)); +// platformNativeScriptDynamic().bootstrapModule(makeExampleModule(LoginAppComponent)); + +// animations +// platformNativeScriptDynamic().bootstrapModule(makeExampleModule(AnimationStatesTest)); +// platformNativeScriptDynamic().bootstrapModule(makeExampleModule(AnimationNgClassTest)); +// platformNativeScriptDynamic().bootstrapModule(makeExampleModule(AnimationKeyframesTest)); +// platformNativeScriptDynamic().bootstrapModule(makeExampleModule(AnimationEnterLeaveTest)); //Livesync test var cachedUrl: string; diff --git a/ng-sample/hooks/before-livesync/nativescript-angular-sync .js b/ng-sample/hooks/before-livesync/nativescript-angular-sync .js deleted file mode 100644 index 5a8510cb2..000000000 --- a/ng-sample/hooks/before-livesync/nativescript-angular-sync .js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require("nativescript-angular/hooks/before-livesync"); diff --git a/tests/app/tests/renderer-tests.ts b/tests/app/tests/renderer-tests.ts index 48722c6fc..fb60eaac6 100644 --- a/tests/app/tests/renderer-tests.ts +++ b/tests/app/tests/renderer-tests.ts @@ -1,12 +1,15 @@ //make sure you import mocha-config before @angular/core -import {assert} from "./test-config"; -import {Component, ElementRef, Renderer, NgZone} from "@angular/core"; -import {ProxyViewContainer} from "ui/proxy-view-container"; -import {Red} from "color/known-colors"; -import {dumpView} from "./test-utils"; -import {TestApp} from "./test-app"; -import {LayoutBase} from "ui/layouts/layout-base"; -import {StackLayout} from "ui/layouts/stack-layout"; +import { assert } from "./test-config"; +import { Component, ElementRef, Renderer, NgZone } from "@angular/core"; +import { ProxyViewContainer } from "ui/proxy-view-container"; +import { Red } from "color/known-colors"; +import { dumpView } from "./test-utils"; +import { TestApp } from "./test-app"; +import { LayoutBase } from "ui/layouts/layout-base"; +import { StackLayout } from "ui/layouts/stack-layout"; +import { ContentView } from "ui/content-view"; +import { Button } from "ui/button"; +import { NgView } from "nativescript-angular/element-registry"; @Component({ template: `` @@ -170,9 +173,9 @@ describe('Renderer E2E', () => { it("executes events inside NgZone when listen is called inside NgZone", (done) => { const eventName = "someEvent"; const view = new StackLayout(); - const evetArg = { eventName, object: view }; + const eventArg = { eventName, object: view }; const callback = (arg) => { - assert.equal(arg, evetArg); + assert.equal(arg, eventArg); assert.isTrue(NgZone.isInAngularZone(), "Event should be executed inside NgZone"); done(); }; @@ -183,7 +186,7 @@ describe('Renderer E2E', () => { setTimeout(() => { testApp.zone.runOutsideAngular(() => { - view.notify(evetArg); + view.notify(eventArg); }); }, 10); }); @@ -191,9 +194,9 @@ describe('Renderer E2E', () => { it("executes events inside NgZone when listen is called outside NgZone", (done) => { const eventName = "someEvent"; const view = new StackLayout(); - const evetArg = { eventName, object: view }; + const eventArg = { eventName, object: view }; const callback = (arg) => { - assert.equal(arg, evetArg); + assert.equal(arg, eventArg); assert.isTrue(NgZone.isInAngularZone(), "Event should be executed inside NgZone"); done(); }; @@ -201,7 +204,7 @@ describe('Renderer E2E', () => { testApp.zone.runOutsideAngular(() => { testApp.renderer.listen(view, eventName, callback); - view.notify(evetArg); + view.notify(eventArg); }); }); @@ -265,7 +268,7 @@ describe('Renderer createElement', () => { return TestApp.create().then((app) => { testApp = app; renderer = testApp.renderer; - }) + }); }); after(() => { @@ -291,4 +294,83 @@ describe('Renderer createElement', () => { const result = renderer.createElement(null, "unknown-tag", null); assert.instanceOf(result, ProxyViewContainer, "Renderer should create ProxyViewContainer form 'unknown-tag'") }); -}) +}); + + +describe('Renderer attach/detach', () => { + let testApp: TestApp = null; + let renderer: Renderer = null; + + before(() => { + return TestApp.create().then((app) => { + testApp = app; + renderer = testApp.renderer; + }); + }); + + after(() => { + testApp.dispose(); + }); + + it("createElement element with parent attaches element to content view", () => { + const parent = renderer.createElement(null, "ContentView", null); + const button =