Skip to content

FIX: Adding template anchors to ContentView #560

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 25, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions nativescript-angular/directives/action-bar.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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;
Expand All @@ -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;
}
},
Expand Down
10 changes: 8 additions & 2 deletions nativescript-angular/element-registry.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -24,7 +26,7 @@ const defaultViewMeta: ViewClassMeta = {
skipAddToDom: false,
};

const elementMap = new Map<string, { resolver: ViewResolver, meta?: ViewClassMeta }>();
const elementMap = new Map<string, { resolver: ViewResolver, meta?: ViewClassMeta }>();
const camelCaseSplit = /([a-z0-9])([A-Z])/g;

export function registerElement(
Expand Down Expand Up @@ -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.
Expand Down
41 changes: 26 additions & 15 deletions nativescript-angular/view-util.ts
Original file line number Diff line number Diff line change
@@ -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:";
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -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;
}

Expand Down
41 changes: 21 additions & 20 deletions ng-sample/app/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
Expand Down

This file was deleted.

114 changes: 98 additions & 16 deletions tests/app/tests/renderer-tests.ts
Original file line number Diff line number Diff line change
@@ -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: `<StackLayout><Label text="Layout"></Label></StackLayout>`
Expand Down Expand Up @@ -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();
};
Expand All @@ -183,25 +186,25 @@ describe('Renderer E2E', () => {

setTimeout(() => {
testApp.zone.runOutsideAngular(() => {
view.notify(evetArg);
view.notify(eventArg);
});
}, 10);
});

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

testApp.zone.runOutsideAngular(() => {
testApp.renderer.listen(view, eventName, callback);

view.notify(evetArg);
view.notify(eventArg);
});
});

Expand Down Expand Up @@ -265,7 +268,7 @@ describe('Renderer createElement', () => {
return TestApp.create().then((app) => {
testApp = app;
renderer = testApp.renderer;
})
});
});

after(() => {
Expand All @@ -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 = <ContentView>renderer.createElement(null, "ContentView", null);
const button = <Button>renderer.createElement(parent, "Button", null);

assert.equal(parent.content, button);
assert.equal(button.parent, parent);
});

it("createElement element with parent attaches element to layout view", () => {
const parent = <StackLayout>renderer.createElement(null, "StackLayout", null);
const button = <Button>renderer.createElement(parent, "Button", null);

assert.equal(parent.getChildAt(0), button);
assert.equal(button.parent, parent);
});

it("detachView element removes element from content view", () => {
const parent = <ContentView>renderer.createElement(null, "ContentView", null);
const button = <Button>renderer.createElement(parent, "Button", null);

renderer.detachView([button]);

assert.isNull(parent.content);
assert.isUndefined(button.parent);
});

it("detachView element removes element from layout view", () => {
const parent = <StackLayout>renderer.createElement(null, "StackLayout", null);
const button = <Button>renderer.createElement(parent, "Button", null);

renderer.detachView([button]);

assert.equal(parent.getChildrenCount(), 0);
assert.isUndefined(button.parent);
});

it("attaching template anchor in content view does not replace its content", () => {
const parent = <ContentView>renderer.createElement(null, "ContentView", null);
const button = <Button>renderer.createElement(parent, "Button", null);

assert.equal(parent.content, button);

const anchor = <NgView>renderer.createTemplateAnchor(parent);

assert.equal(parent.content, button);
assert.equal(anchor.parent, parent);
assert.equal(anchor.templateParent, parent);
});

it("attaching and detaching template anchor to content view does not affect its content", () => {
const parent = <ContentView>renderer.createElement(null, "ContentView", null);
const button = <Button>renderer.createElement(parent, "Button", null);
const anchor = <NgView>renderer.createTemplateAnchor(null);
assert.equal(parent.content, button);

renderer.attachViewAfter(button, [anchor]);
assert.equal(parent.content, button);

renderer.detachView([anchor]);
assert.equal(parent.content, button);
});
});