Skip to content

Commit 06d632a

Browse files
committed
Merge pull request NativeScript#106 from NativeScript/nnikolov/ValueAccessors
Added value accessors to support Angular2 ngModel.
2 parents 92f9762 + c19906e commit 06d632a

File tree

13 files changed

+484
-87
lines changed

13 files changed

+484
-87
lines changed

ng-sample/app/app.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@ import {NavigationTest} from "./examples/navigation/navigation-test";
2525
import {ActionBarTest} from "./examples/action-bar/action-bar-test";
2626

2727

28-
// nativeScriptBootstrap(RendererTest);
29-
// nativeScriptBootstrap(Benchmark);
30-
// nativeScriptBootstrap(ListTest);
31-
// nativeScriptBootstrap(ListTestAsync);
32-
// nativeScriptBootstrap(Benchmark);
33-
// nativeScriptBootstrap(ListTest);
34-
// nativeScriptBootstrap(ListTestAsync);
35-
// nativeScriptBootstrap(ImageTest);
36-
// nativeScriptBootstrap(NavigationTest, [NS_ROUTER_PROVIDERS]);
37-
nativeScriptBootstrap(ActionBarTest, [NS_ROUTER_PROVIDERS], { startPageActionBarHidden: false });
28+
nativeScriptBootstrap(RendererTest);
29+
//nativeScriptBootstrap(Benchmark);
30+
//nativeScriptBootstrap(ListTest);
31+
//nativeScriptBootstrap(ListTestAsync);
32+
//nativeScriptBootstrap(Benchmark);
33+
//nativeScriptBootstrap(ListTest);
34+
//nativeScriptBootstrap(ListTestAsync);
35+
//nativeScriptBootstrap(ImageTest);
36+
//nativeScriptBootstrap(NavigationTest, [NS_ROUTER_PROVIDERS]);
37+
//nativeScriptBootstrap(ActionBarTest, [NS_ROUTER_PROVIDERS], { startPageActionBarHidden: false });
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<StackLayout orientation='vertical'>
2+
<Progress value="50" style="color: red"></Progress>
3+
<!--<TimePicker [(ngModel)]='model.deliveryTime' ></TimePicker>
4+
<Label [text]='model.deliveryTime' ></Label>
5+
<DatePicker [(ngModel)]='model.deliveryDate' ></DatePicker>
6+
<Label [text]='model.deliveryDate' ></Label>-->
7+
<SearchBar [(ngModel)]='model.search'></SearchBar>
8+
<Label [text]='model.search'></Label>
9+
<Slider [(ngModel)]='model.sliderTest'></Slider>
10+
<Label [text]='model.sliderTest'></Label>
11+
<ListPicker [items]='model.listPickerItems' [(ngModel)]='model.selectedIndex'></ListPicker>
12+
<TextField [(ngModel)]='model.selectedIndex'></TextField>
13+
<SegmentedBar [items]='model.segmentedBarItems' [(ngModel)]='model.selectedIndex'></SegmentedBar>
14+
<Label [text]='model.selectedIndex'></Label>
15+
<Switch [(ngModel)]='model.testBoolean'></Switch>
16+
<Label [text]='model.testBoolean'></Label>
17+
<Label [text]='model.test'></Label>
18+
<TextView #name [ngModel]='model.test' (ngModelChange)="model.test=setUpperCase($event)" fontSize='20' padding='20'></TextView>
19+
<Label [class.valid]="isValid" [class.invalid]="!isValid" text='Name' fontSize='20' verticalAlignment='center' padding='20'></Label>
20+
<TextField #name text='John' fontSize='20' padding='20'></TextField>
21+
<Button [text]='buttonText' (tap)='onSave($event, name.text, $el)'></Button>
22+
<Button text='Toggle details' (tap)='onToggleDetails()'></Button>
23+
<TextView *ngIf='showDetails' [text]='detailsText'></TextView>
24+
<Label text='==============================' fontSize='20'></Label>
25+
<StackLayout #more *ngIf='showDetails' orientation='vertical'>
26+
<TextField *ngFor='#detailLine of detailLines' [text]='detailLine'></TextField>
27+
</StackLayout>
28+
<Label text='==============================' fontSize='20'></Label>
29+
<templated-component [renderChild]="true"></templated-component>
30+
</StackLayout>

ng-sample/app/examples/renderer-test.ts

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {Component, Directive, Host, ElementRef, Input} from 'angular2/core';
2-
import { Observable } from 'data/observable';
2+
import {Observable} from 'data/observable';
33
import {TextValueAccessor} from '../nativescript-angular/value-accessors/text-value-accessor';
44
import {CheckedValueAccessor} from '../nativescript-angular/value-accessors/checked-value-accessor';
55

@@ -28,26 +28,7 @@ export class ProgressComponent {
2828
@Component({
2929
selector: 'renderer-test',
3030
directives: [TemplatedComponent, ProgressComponent, TextValueAccessor, CheckedValueAccessor],
31-
template: `
32-
<StackLayout orientation='vertical'>
33-
<Progress value="50" style="color: red"></Progress>
34-
<Switch [(ngModel)]='model.testBoolean'></Switch>
35-
<Label [text]='model.testBoolean'></Label>
36-
<Label [text]='model.test'></Label>
37-
<TextView #name [ngModel]='model.test' (ngModelChange)="model.test=setUpperCase($event)" fontSize='20' padding='20'></TextView>
38-
<Label [class.valid]="isValid" [class.invalid]="!isValid" text='Name' fontSize='20' verticalAlignment='center' padding='20'></Label>
39-
<TextField #name text='John' fontSize='20' padding='20'></TextField>
40-
<Button [text]='buttonText' (tap)='onSave($event, name.text, $el)'></Button>
41-
<Button text='Toggle details' (tap)='onToggleDetails()'></Button>
42-
<TextView *ngIf='showDetails' [text]='detailsText'></TextView>
43-
<Label text='==============================' fontSize='20'></Label>
44-
<StackLayout #more *ngIf='showDetails' orientation='vertical'>
45-
<TextField *ngFor='#detailLine of detailLines' [text]='detailLine'></TextField>
46-
</StackLayout>
47-
<Label text='==============================' fontSize='20'></Label>
48-
<templated-component [renderChild]="true"></templated-component>
49-
</StackLayout>
50-
`,
31+
templateUrl: './examples/renderer-test.html'
5132
})
5233
export class RendererTest {
5334
public buttonText: string = "";
@@ -63,7 +44,23 @@ export class RendererTest {
6344
this.showDetails = true;
6445
this.detailsText = 'plain ng-if directive \ndetail 1-2-3...';
6546
this.moreDetailsText = 'More details:';
66-
this.model = new Observable({'test': 'Jack', 'testBoolean': false});
47+
this.model = new Observable({
48+
'test': 'Jack',
49+
'testBoolean': false,
50+
'deliveryDate': new Date(),
51+
'deliveryTime': new Date(),
52+
'sliderTest': 0,
53+
'search': null,
54+
'selectedIndex': 0,
55+
'listPickerItems': [
56+
1,2,3,4,5
57+
],
58+
'segmentedBarItems': [
59+
{'title': 'first'},
60+
{'title': 'second'},
61+
{'title': 'third'}
62+
]
63+
});
6764

6865
this.detailLines = [
6966
"ngFor inside a ngIf 1",

ng-sample/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,4 @@
5353
"version": "1.6.0"
5454
}
5555
}
56-
}
56+
}
Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,26 @@
1-
import {Type} from 'angular2/src/facade/lang';
2-
import {ListViewComponent} from './list-view-comp';
3-
import {TextValueAccessor} from '../value-accessors/text-value-accessor';
4-
import {CheckedValueAccessor} from '../value-accessors/checked-value-accessor';
5-
import {TabViewDirective, TabViewItemDirective} from './tab-view';
6-
import {ActionBarComponent, ActionBarScope, ActionItemDirective, NavigationButtonDirective} from './action-bar';
7-
8-
export const NS_DIRECTIVES: Type[] = [
9-
ListViewComponent,
10-
11-
TabViewDirective,
12-
TabViewItemDirective,
13-
14-
TextValueAccessor,
15-
CheckedValueAccessor,
16-
17-
ActionBarComponent,
18-
ActionBarScope,
19-
ActionItemDirective,
20-
NavigationButtonDirective
21-
];
1+
import {Type} from 'angular2/src/facade/lang';
2+
import {ListViewComponent} from './list-view-comp';
3+
import {TextValueAccessor} from '../value-accessors/text-value-accessor';
4+
import {CheckedValueAccessor} from '../value-accessors/checked-value-accessor';
5+
import {DateValueAccessor} from '../value-accessors/date-value-accessor';
6+
import {TimeValueAccessor} from '../value-accessors/time-value-accessor';
7+
import {NumberValueAccessor} from '../value-accessors/number-value-accessor';
8+
import {SelectedIndexValueAccessor} from '../value-accessors/selectedIndex-value-accessor';
9+
import {TabViewDirective, TabViewItemDirective} from './tab-view';
10+
import {ActionBarComponent, ActionBarScope, ActionItemDirective, NavigationButtonDirective} from './action-bar';
11+
12+
export const NS_DIRECTIVES: Type[] = [
13+
ListViewComponent,
14+
TabViewDirective,
15+
TabViewItemDirective,
16+
TextValueAccessor,
17+
CheckedValueAccessor,
18+
DateValueAccessor,
19+
TimeValueAccessor,
20+
SelectedIndexValueAccessor,
21+
NumberValueAccessor,
22+
ActionBarComponent,
23+
ActionBarScope,
24+
ActionItemDirective,
25+
NavigationButtonDirective
26+
];
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import {View} from "ui/core/view";
2+
import {ControlValueAccessor} from 'angular2/src/common/forms/directives/control_value_accessor';
3+
4+
export class BaseValueAccessor<TView> implements ControlValueAccessor {
5+
constructor(public view: TView) { }
6+
7+
onChange = (_) => { };
8+
private pendingChangeNotification: number = 0;
9+
10+
registerOnChange(fn: (_: any) => void): void {
11+
this.onChange = (arg) => {
12+
if (this.pendingChangeNotification) {
13+
clearTimeout(this.pendingChangeNotification);
14+
}
15+
this.pendingChangeNotification = setTimeout(() => {
16+
this.pendingChangeNotification = 0;
17+
fn(arg);
18+
}, 20);
19+
}
20+
}
21+
22+
writeValue(value: any) {
23+
//
24+
}
25+
26+
registerOnTouched(fn: () => void): void {
27+
//
28+
}
29+
}
Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
import {Directive, ElementRef, Renderer, Self, forwardRef, Provider} from 'angular2/core';
2-
import {NG_VALUE_ACCESSOR, ControlValueAccessor} from 'angular2/src/common/forms/directives/control_value_accessor';
3-
import {isBlank, CONST_EXPR} from 'angular2/src/facade/lang';
1+
import {Directive, ElementRef, Renderer, Self, forwardRef, provide} from 'angular2/core';
2+
import {NG_VALUE_ACCESSOR} from 'angular2/src/common/forms/directives/control_value_accessor';
3+
import {isBlank} from 'angular2/src/facade/lang';
4+
import {BaseValueAccessor} from './base-value-accessor';
5+
import {Switch} from "ui/switch";
46

5-
const CHECKED_VALUE_ACCESSOR = CONST_EXPR(new Provider(
6-
NG_VALUE_ACCESSOR, { useExisting: forwardRef(() => CheckedValueAccessor), multi: true }));
7+
const CHECKED_VALUE_ACCESSOR = provide(NG_VALUE_ACCESSOR, { useExisting: forwardRef(() => CheckedValueAccessor), multi: true });
78

89
/**
9-
* The accessor for writing a text and listening to changes that is used by the
10-
* {@link NgModel}, {@link NgFormControl}, and {@link NgControlName} directives.
10+
* The accessor for setting a checked property and listening to changes that is used by the
11+
* {@link NgModel} directives.
1112
*
1213
* ### Example
1314
* ```
@@ -16,23 +17,27 @@ const CHECKED_VALUE_ACCESSOR = CONST_EXPR(new Provider(
1617
*/
1718
@Directive({
1819
selector: 'Switch[ngModel]',
19-
// TODO: vsavkin replace the above selector with the one below it once
20-
// https://github.com/angular/angular/issues/3011 is implemented
21-
// selector: '[ngControl],[ngModel],[ngFormControl]',
2220
host: { '(checkedChange)': 'onChange($event.value)' },
2321
bindings: [CHECKED_VALUE_ACCESSOR]
2422
})
25-
export class CheckedValueAccessor implements ControlValueAccessor {
26-
onChange = (_) => { };
23+
export class CheckedValueAccessor extends BaseValueAccessor<Switch> {
2724
onTouched = () => { };
2825

29-
constructor(private _renderer: Renderer, private _elementRef: ElementRef) { }
26+
constructor(elementRef: ElementRef) {
27+
super(elementRef.nativeElement);
28+
}
3029

3130
writeValue(value: any): void {
32-
var normalizedValue = isBlank(value) ? false : value;
33-
this._renderer.setElementProperty(this._elementRef.nativeElement, 'checked', normalizedValue);
31+
let normalizedValue = false;
32+
if (!isBlank(value)) {
33+
if (typeof value === 'string') {
34+
normalizedValue = value.toLowerCase() === 'true' ? true : false;
35+
} else {
36+
normalizedValue = !!value;
37+
}
38+
}
39+
this.view.checked = normalizedValue;
3440
}
3541

36-
registerOnChange(fn: (_: any) => void): void { this.onChange = fn; }
3742
registerOnTouched(fn: () => void): void { this.onTouched = fn; }
38-
}
43+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import {Directive, ElementRef, Renderer, Self, forwardRef, provide} from 'angular2/core';
2+
import {NG_VALUE_ACCESSOR} from 'angular2/src/common/forms/directives/control_value_accessor';
3+
import {isBlank, isDate} from 'angular2/src/facade/lang';
4+
import {BaseValueAccessor} from './base-value-accessor';
5+
import {DatePicker} from "ui/date-picker";
6+
7+
const DATE_VALUE_ACCESSOR = provide(NG_VALUE_ACCESSOR, { useExisting: forwardRef(() => DateValueAccessor), multi: true });
8+
9+
/**
10+
* The accessor for setting a date and listening to changes that is used by the
11+
* {@link NgModel} directives.
12+
*
13+
* ### Example
14+
* ```
15+
* <DatePicker [(ngModel)]='model.test'>
16+
* ```
17+
*/
18+
@Directive({
19+
selector: 'DatePicker[ngModel]',
20+
host: { '(dateChange)': 'onChange($event.value)' },
21+
bindings: [DATE_VALUE_ACCESSOR]
22+
})
23+
export class DateValueAccessor extends BaseValueAccessor<any> {
24+
//TODO: change <any> above to DatePicker
25+
onTouched = () => { };
26+
27+
constructor(elementRef: ElementRef) {
28+
super(elementRef.nativeElement);
29+
}
30+
31+
writeValue(value: any): void {
32+
var normalizedValue = isBlank(value) ? new Date() : value;
33+
if (!isDate(normalizedValue)) {
34+
if (typeof normalizedValue === 'string' || typeof normalizedValue === 'number') {
35+
normalizedValue = new Date(normalizedValue);
36+
}
37+
if (!isDate(normalizedValue)) {
38+
normalizedValue = new Date();
39+
}
40+
}
41+
this.view.date = normalizedValue;
42+
}
43+
44+
registerOnTouched(fn: () => void): void { this.onTouched = fn; }
45+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import {Directive, ElementRef, Renderer, Self, forwardRef, provide} from 'angular2/core';
2+
import {NG_VALUE_ACCESSOR} from 'angular2/src/common/forms/directives/control_value_accessor';
3+
import {isBlank, isNumber} from 'angular2/src/facade/lang';
4+
import {BaseValueAccessor} from './base-value-accessor';
5+
import {Slider} from "ui/slider";
6+
7+
const NUMBER_VALUE_ACCESSOR = provide(NG_VALUE_ACCESSOR, { useExisting: forwardRef(() => NumberValueAccessor), multi: true });
8+
9+
/**
10+
* The accessor for setting a value and listening to changes that is used by the
11+
* {@link NgModel}
12+
*
13+
* ### Example
14+
* ```
15+
* <Slider [(ngModel)]='model.test'>
16+
* ```
17+
*/
18+
@Directive({
19+
selector: 'Slider[ngModel]',
20+
host: { '(valueChange)': 'onChange($event.value)' },
21+
bindings: [NUMBER_VALUE_ACCESSOR]
22+
})
23+
export class NumberValueAccessor extends BaseValueAccessor<Slider> {
24+
onTouched = () => { };
25+
26+
constructor(elementRef: ElementRef) {
27+
super(elementRef.nativeElement);
28+
}
29+
30+
writeValue(value: any): void {
31+
let normalizedValue;
32+
if (isBlank(value)) {
33+
normalizedValue = 0;
34+
} else {
35+
if (isNumber(value)) {
36+
normalizedValue = value;
37+
} else {
38+
let parsedValue = Number(value);
39+
normalizedValue = isNaN(parsedValue) ? 0 : parsedValue;
40+
}
41+
}
42+
this.view.value = normalizedValue;
43+
}
44+
45+
registerOnTouched(fn: () => void): void { this.onTouched = fn; }
46+
}

0 commit comments

Comments
 (0)