From 88d842c6c58f0cda97eff42ab48eb5f53238348f Mon Sep 17 00:00:00 2001 From: Hristo Deshev Date: Tue, 6 Dec 2016 15:23:15 +0200 Subject: [PATCH 1/2] feat(forms): Enable reactive forms. Add control[formControlName] selectors to value accessor directives. --- nativescript-angular/index.ts | 1 + .../value-accessors/base-value-accessor.ts | 17 ++++++++++++++ .../value-accessors/checked-value-accessor.ts | 4 ++-- .../value-accessors/date-value-accessor.ts | 4 ++-- .../value-accessors/number-value-accessor.ts | 4 ++-- .../selectedIndex-value-accessor.ts | 4 ++-- .../value-accessors/text-value-accessor.ts | 4 ++-- .../value-accessors/time-value-accessor.ts | 4 ++-- tests/app/tests/value-accessor-tests.ts | 23 ++++++++++++++++++- tests/package.json | 2 +- 10 files changed, 53 insertions(+), 14 deletions(-) diff --git a/nativescript-angular/index.ts b/nativescript-angular/index.ts index 36809fd2e..ea7af7b34 100644 --- a/nativescript-angular/index.ts +++ b/nativescript-angular/index.ts @@ -17,3 +17,4 @@ export * from "./view-util"; export * from "./animation-driver"; export * from "./resource-loader"; export * from "./element-registry"; +export * from "./value-accessors/base-value-accessor"; diff --git a/nativescript-angular/value-accessors/base-value-accessor.ts b/nativescript-angular/value-accessors/base-value-accessor.ts index 06df0584c..8ebac8ec4 100644 --- a/nativescript-angular/value-accessors/base-value-accessor.ts +++ b/nativescript-angular/value-accessors/base-value-accessor.ts @@ -1,5 +1,22 @@ import { ControlValueAccessor } from "@angular/forms"; +export function generateValueAccessorSelector(...tagNames: string[]): string { + const tags: string[] = []; + tagNames.forEach(tagName => { + tags.push(tagName); // regular tag + tags.push(tagName.charAt(0).toLowerCase() + tagName.slice(1)); // lowercase first char + tags.push(tagName.split(/(?=[A-Z])/).join("-").toLowerCase()); // kebab case + }); + + const selectors = []; + for (const tag of tags) { + for (const directive of ["ngModel", "formControlName"]) { + selectors.push(`${tag}[${directive}]`); + } + } + return selectors.join(", "); +} + export class BaseValueAccessor implements ControlValueAccessor { constructor(public view: TView) { } diff --git a/nativescript-angular/value-accessors/checked-value-accessor.ts b/nativescript-angular/value-accessors/checked-value-accessor.ts index 71214e919..e763a2ab3 100644 --- a/nativescript-angular/value-accessors/checked-value-accessor.ts +++ b/nativescript-angular/value-accessors/checked-value-accessor.ts @@ -1,7 +1,7 @@ import { Directive, ElementRef, forwardRef, HostListener } from "@angular/core"; import { NG_VALUE_ACCESSOR } from "@angular/forms"; import { isBlank } from "../lang-facade"; -import { BaseValueAccessor } from "./base-value-accessor"; +import { BaseValueAccessor, generateValueAccessorSelector } from "./base-value-accessor"; import { Switch } from "ui/switch"; const CHECKED_VALUE_ACCESSOR = {provide: NG_VALUE_ACCESSOR, @@ -17,7 +17,7 @@ const CHECKED_VALUE_ACCESSOR = {provide: NG_VALUE_ACCESSOR, * ``` */ @Directive({ - selector: "Switch[ngModel], switch[ngModel]", // tslint:disable-line:directive-selector + selector: generateValueAccessorSelector("Switch"), providers: [CHECKED_VALUE_ACCESSOR] }) export class CheckedValueAccessor extends BaseValueAccessor { // tslint:disable-line:directive-class-suffix diff --git a/nativescript-angular/value-accessors/date-value-accessor.ts b/nativescript-angular/value-accessors/date-value-accessor.ts index 11990c6dd..f865fa7c1 100644 --- a/nativescript-angular/value-accessors/date-value-accessor.ts +++ b/nativescript-angular/value-accessors/date-value-accessor.ts @@ -1,7 +1,7 @@ import { Directive, ElementRef, forwardRef, HostListener } from "@angular/core"; import { NG_VALUE_ACCESSOR } from "@angular/forms"; import { isBlank, isDate } from "../lang-facade"; -import { BaseValueAccessor } from "./base-value-accessor"; +import { BaseValueAccessor, generateValueAccessorSelector } from "./base-value-accessor"; import { DatePicker } from "ui/date-picker"; const DATE_VALUE_ACCESSOR = {provide: NG_VALUE_ACCESSOR, @@ -17,7 +17,7 @@ const DATE_VALUE_ACCESSOR = {provide: NG_VALUE_ACCESSOR, * ``` */ @Directive({ - selector: "DatePicker[ngModel], datePicker[ngModel], date-picker[ngModel]", // tslint:disable-line:max-line-length directive-selector + selector: generateValueAccessorSelector("DatePicker"), providers: [DATE_VALUE_ACCESSOR] }) export class DateValueAccessor extends BaseValueAccessor { // tslint:disable-line:directive-class-suffix diff --git a/nativescript-angular/value-accessors/number-value-accessor.ts b/nativescript-angular/value-accessors/number-value-accessor.ts index 3a32cf98f..65042a1fc 100644 --- a/nativescript-angular/value-accessors/number-value-accessor.ts +++ b/nativescript-angular/value-accessors/number-value-accessor.ts @@ -1,7 +1,7 @@ import { Directive, ElementRef, forwardRef, HostListener } from "@angular/core"; import { NG_VALUE_ACCESSOR } from "@angular/forms"; import { isBlank, isNumber } from "../lang-facade"; -import { BaseValueAccessor } from "./base-value-accessor"; +import { BaseValueAccessor, generateValueAccessorSelector } from "./base-value-accessor"; import { Slider } from "ui/slider"; const NUMBER_VALUE_ACCESSOR = {provide: NG_VALUE_ACCESSOR, @@ -17,7 +17,7 @@ const NUMBER_VALUE_ACCESSOR = {provide: NG_VALUE_ACCESSOR, * ``` */ @Directive({ - selector: "Slider[ngModel], slider[ngModel]", // tslint:disable-line:directive-selector + selector: generateValueAccessorSelector("Slider"), providers: [NUMBER_VALUE_ACCESSOR] }) export class NumberValueAccessor extends BaseValueAccessor { // tslint:disable-line:directive-class-suffix diff --git a/nativescript-angular/value-accessors/selectedIndex-value-accessor.ts b/nativescript-angular/value-accessors/selectedIndex-value-accessor.ts index f8da13979..692990f2e 100644 --- a/nativescript-angular/value-accessors/selectedIndex-value-accessor.ts +++ b/nativescript-angular/value-accessors/selectedIndex-value-accessor.ts @@ -1,6 +1,6 @@ import { Directive, ElementRef, forwardRef, AfterViewInit, HostListener } from "@angular/core"; import { NG_VALUE_ACCESSOR } from "@angular/forms"; -import { BaseValueAccessor } from "./base-value-accessor"; +import { BaseValueAccessor, generateValueAccessorSelector } from "./base-value-accessor"; import { View } from "ui/core/view"; import { convertToInt } from "../common/utils"; @@ -19,7 +19,7 @@ export type SelectableView = {selectedIndex: number} & View; * ``` */ @Directive({ - selector: "SegmentedBar[ngModel], segmentedBar[ngModel], segmented-bar[ngModel], ListPicker[ngModel], listPicker[ngModel], list-picker[ngModel], TabView[ngModel], tabView[ngModel], tab-view[ngModel]", // tslint:disable-line:max-line-length directive-selector + selector: generateValueAccessorSelector("SegmentedBar", "ListPicker", "TabView"), providers: [SELECTED_INDEX_VALUE_ACCESSOR] }) export class SelectedIndexValueAccessor extends BaseValueAccessor implements AfterViewInit { // tslint:disable-line:max-line-length directive-class-suffix diff --git a/nativescript-angular/value-accessors/text-value-accessor.ts b/nativescript-angular/value-accessors/text-value-accessor.ts index 1dc699f16..02993cdc5 100644 --- a/nativescript-angular/value-accessors/text-value-accessor.ts +++ b/nativescript-angular/value-accessors/text-value-accessor.ts @@ -1,7 +1,7 @@ import { Directive, ElementRef, forwardRef, HostListener } from "@angular/core"; import { NG_VALUE_ACCESSOR } from "@angular/forms"; import { isBlank } from "../lang-facade"; -import { BaseValueAccessor } from "./base-value-accessor"; +import { BaseValueAccessor, generateValueAccessorSelector } from "./base-value-accessor"; import { View } from "ui/core/view"; const TEXT_VALUE_ACCESSOR = {provide: NG_VALUE_ACCESSOR, @@ -19,7 +19,7 @@ export type TextView = {text: string} & View; * ``` */ @Directive({ - selector: "TextField[ngModel], textField[ngModel], text-field[ngModel], TextView[ngModel], textView[ngModel], text-view[ngModel], SearchBar[ngModel], search-bar[ngModel], searchBar[ngModel]", // tslint:disable-line:max-line-length directive-selector + selector: generateValueAccessorSelector("TextField", "TextView", "SearchBar"), providers: [TEXT_VALUE_ACCESSOR] }) export class TextValueAccessor extends BaseValueAccessor { // tslint:disable-line:directive-class-suffix diff --git a/nativescript-angular/value-accessors/time-value-accessor.ts b/nativescript-angular/value-accessors/time-value-accessor.ts index 4354578f9..36caae188 100644 --- a/nativescript-angular/value-accessors/time-value-accessor.ts +++ b/nativescript-angular/value-accessors/time-value-accessor.ts @@ -1,7 +1,7 @@ import { Directive, ElementRef, forwardRef, HostListener } from "@angular/core"; import { NG_VALUE_ACCESSOR } from "@angular/forms"; import { isBlank, isDate } from "../lang-facade"; -import { BaseValueAccessor } from "./base-value-accessor"; +import { BaseValueAccessor, generateValueAccessorSelector } from "./base-value-accessor"; import { TimePicker } from "ui/time-picker"; const TIME_VALUE_ACCESSOR = {provide: NG_VALUE_ACCESSOR, @@ -17,7 +17,7 @@ const TIME_VALUE_ACCESSOR = {provide: NG_VALUE_ACCESSOR, * ``` */ @Directive({ - selector: "TimePicker[ngModel], timePicker[ngModel], time-picker[ngModel]", // tslint:disable-line:max-line-length directive-selector + selector: generateValueAccessorSelector("TimePicker"), providers: [TIME_VALUE_ACCESSOR] }) export class TimeValueAccessor extends BaseValueAccessor { // tslint:disable-line:directive-class-suffix diff --git a/tests/app/tests/value-accessor-tests.ts b/tests/app/tests/value-accessor-tests.ts index f737e78c7..550f8eb6a 100644 --- a/tests/app/tests/value-accessor-tests.ts +++ b/tests/app/tests/value-accessor-tests.ts @@ -8,6 +8,7 @@ import {TimePicker} from "ui/time-picker"; import {ListPicker} from "ui/list-picker"; import {TextField} from "ui/text-field"; import { + generateValueAccessorSelector, NumberValueAccessor, CheckedValueAccessor, DateValueAccessor, @@ -100,7 +101,7 @@ describe("two-way binding via ng-model", () => { it("converts strings to int selection", () => { const accessor = new TestSelectedIndexValueAccessor() - + accessor.writeValue(null); accessor.ngAfterViewInit(); assert.strictEqual(0, accessor.view.selectedIndex, "default to 0 on empty") @@ -143,6 +144,26 @@ describe("two-way binding via ng-model", () => { }); }) +describe("target selector registration", () => { + it("supports uppercase(unchanged) camel tags", () => { + assert.include(generateValueAccessorSelector("TextField"), "TextField[ngModel]"); + }); + it("supports lowercase camel tags", () => { + assert.include(generateValueAccessorSelector("TextField"), "textField[ngModel]"); + }); + it("supports kebab case tags", () => { + assert.include(generateValueAccessorSelector("TextField"), "text-field[ngModel]"); + }); + it("supports formControlName", () => { + assert.include(generateValueAccessorSelector("TextField"), "TextField[formControlName]"); + }); + it("supports multiple tags", () => { + const selector = generateValueAccessorSelector("TextField", "TextView"); + assert.include(selector, "TextField[ngModel]"); + assert.include(selector, "TextField[formControlName]"); + }); +}); + function formatDate(date: Date) { return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`; } diff --git a/tests/package.json b/tests/package.json index 7e33ecd76..e685bec60 100644 --- a/tests/package.json +++ b/tests/package.json @@ -2,7 +2,7 @@ "nativescript": { "id": "org.nativescript.ngtests", "tns-android": { - "version": "2.4.0" + "version": "2.4.1" }, "tns-ios": { "version": "2.4.0" From d587380001aa89ab8e73d467efaa6f6dcebbfee6 Mon Sep 17 00:00:00 2001 From: Hristo Deshev Date: Thu, 8 Dec 2016 12:50:00 +0200 Subject: [PATCH 2/2] fix(typescript): Pin TypeScript version to 2.0.x --- nativescript-angular/package.json | 2 +- ng-sample/package.json | 2 +- tests/package.json | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/nativescript-angular/package.json b/nativescript-angular/package.json index ba7466b1c..f12334cc6 100644 --- a/nativescript-angular/package.json +++ b/nativescript-angular/package.json @@ -44,7 +44,7 @@ "devDependencies": { "tns-core-modules": ">=2.5.0 || >=2.5.0-2016", "zone.js": "^0.6.21", - "typescript": "^2.0.2", + "typescript": "~2.0.10", "tslint": "~4.0.1", "codelyzer": "^2.0.0-beta.1", "@angular/compiler-cli": "~2.2.1", diff --git a/ng-sample/package.json b/ng-sample/package.json index 798e6e816..8cfb96479 100644 --- a/ng-sample/package.json +++ b/ng-sample/package.json @@ -45,7 +45,7 @@ "nativescript-dev-typescript": "^0.3.1", "nativescript-dev-webpack": "0.0.13", "shelljs": "^0.7.0", - "typescript": "^2.0.2" + "typescript": "~2.0.10" }, "nativescript": { "id": "org.nativescript.ngsample", diff --git a/tests/package.json b/tests/package.json index e685bec60..4671216a0 100644 --- a/tests/package.json +++ b/tests/package.json @@ -56,7 +56,7 @@ "mocha": "^2.4.5", "nativescript-dev-appium": "0.0.14", "nativescript-dev-typescript": "^0.3.1", - "typescript": "^2.0.2", + "typescript": "~2.0.10", "socket.io": "1.4.8", "socket.io-client": "1.4.8", "wd": "0.4.0" @@ -67,4 +67,4 @@ "run-appium-android": "nativescript-dev-appium android", "appium-ios-simulator": "tns build ios && nativescript-dev-appium ios-simulator" } -} \ No newline at end of file +}