Skip to content

Commit 8bc83ce

Browse files
author
Nedyalko Nikolov
committed
Added binding convert option.
1 parent cc829e0 commit 8bc83ce

File tree

9 files changed

+196
-39
lines changed

9 files changed

+196
-39
lines changed

CrossPlatformModules.csproj

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@
9494
<TypeScriptCompile Include="apps\template-master-detail\main-page.ts">
9595
<DependentUpon>main-page.xml</DependentUpon>
9696
</TypeScriptCompile>
97+
<TypeScriptCompile Include="apps\tests\app\binding_tests.ts">
98+
<DependentUpon>binding_tests.xml</DependentUpon>
99+
</TypeScriptCompile>
97100
<TypeScriptCompile Include="apps\tests\app\location-example.ts" />
98101
<TypeScriptCompile Include="apps\tests\app\style_props.ts" />
99102
<TypeScriptCompile Include="apps\tests\console-tests.ts" />
@@ -491,6 +494,7 @@
491494
</ItemGroup>
492495
<ItemGroup>
493496
<Content Include="apps\gallery-app\layouts\dock-layout.xml" />
497+
<Content Include="apps\tests\app\binding_tests.xml" />
494498
<Content Include="apps\ui-tests-app\pages\i86.xml" />
495499
<Content Include="apps\template-blank\app.css" />
496500
<Content Include="apps\template-hello-world\app.css" />
@@ -1456,7 +1460,7 @@
14561460
<SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
14571461
</WebProjectProperties>
14581462
</FlavorProperties>
1459-
<UserProperties ui_2scroll-view_2package_1json__JSONSchema="http://json.schemastore.org/package" apps_2editable-text-demo_2package_1json__JSONSchema="http://json.schemastore.org/package" apps_2absolute-layout-demo_2package_1json__JSONSchema="http://json.schemastore.org/package" apps_2gallery-app_2package_1json__JSONSchema="http://json.schemastore.org/package" ui_2content-view_2package_1json__JSONSchema="http://json.schemastore.org/package" ui_2web-view_2package_1json__JSONSchema="http://json.schemastore.org/package" ui_2layouts_2linear-layout_2package_1json__JSONSchema="http://json.schemastore.org/package" ui_2layouts_2absolute-layout_2package_1json__JSONSchema="http://json.schemastore.org/package" ui_2layouts_2dock-layout_2package_1json__JSONSchema="" ui_2layouts_2grid-layout_2package_1json__JSONSchema="" ui_2layouts_2wrap-layout_2package_1json__JSONSchema="http://json.schemastore.org/package" />
1463+
<UserProperties ui_2layouts_2wrap-layout_2package_1json__JSONSchema="http://json.schemastore.org/package" ui_2layouts_2grid-layout_2package_1json__JSONSchema="" ui_2layouts_2dock-layout_2package_1json__JSONSchema="" ui_2layouts_2absolute-layout_2package_1json__JSONSchema="http://json.schemastore.org/package" ui_2layouts_2linear-layout_2package_1json__JSONSchema="http://json.schemastore.org/package" ui_2web-view_2package_1json__JSONSchema="http://json.schemastore.org/package" ui_2content-view_2package_1json__JSONSchema="http://json.schemastore.org/package" apps_2gallery-app_2package_1json__JSONSchema="http://json.schemastore.org/package" apps_2absolute-layout-demo_2package_1json__JSONSchema="http://json.schemastore.org/package" apps_2editable-text-demo_2package_1json__JSONSchema="http://json.schemastore.org/package" ui_2scroll-view_2package_1json__JSONSchema="http://json.schemastore.org/package" />
14601464
</VisualStudio>
14611465
</ProjectExtensions>
14621466
</Project>

apps/tests/app/binding_tests.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import pageModule = require("ui/page");
2+
//import stackLayoutModule = require("ui/layouts/stack-layout");
3+
//import textFieldModule = require("ui/text-field");
4+
import observableModule = require("data/observable");
5+
import bindableModule = require("ui/core/bindable");
6+
//import enums = require("ui/enums");
7+
import trace = require("trace");
8+
trace.setCategories(trace.categories.Test);
9+
trace.enable();
10+
11+
export function pageLoaded(args: observableModule.EventData) {
12+
var page: pageModule.Page = <pageModule.Page>args.object;
13+
var model = new observableModule.Observable();
14+
//var model = page.bindingContext;
15+
model.set("paramProperty", "%%%");
16+
var toUpperConverter: bindableModule.ValueConverter = {
17+
toModel: function (value, param1) {
18+
return param1 + value.toLowerCase();
19+
},
20+
toView: function (value, param1) {
21+
return value.toUpperCase();
22+
}
23+
};
24+
model.set("toUpper", toUpperConverter);
25+
model.set("testProperty", "Alabala");
26+
27+
page.bindingContext = model;
28+
}
29+
30+
//export function createPage() {
31+
// var stackLayout = new stackLayoutModule.StackLayout();
32+
// var firstTextField = new textFieldModule.TextField();
33+
// firstTextField.updateTextTrigger = enums.UpdateTextTrigger.textChanged;
34+
// var secondTextField = new textFieldModule.TextField();
35+
// secondTextField.updateTextTrigger = enums.UpdateTextTrigger.textChanged;
36+
37+
// var model = new observableModule.Observable();
38+
39+
// var bindOptions: bindableModule.BindingOptions = {
40+
// sourceProperty: "testProperty",
41+
// targetProperty: "text",
42+
// twoWay: true,
43+
// expression: "testProperty | toUpper('$$$')"
44+
// };
45+
46+
// firstTextField.bind(bindOptions, model);
47+
// secondTextField.bind({
48+
// sourceProperty: "testProperty",
49+
// targetProperty: "text",
50+
// twoWay: true
51+
// }, model);
52+
53+
// stackLayout.addChild(firstTextField);
54+
// stackLayout.addChild(secondTextField);
55+
56+
// var page = new pageModule.Page();
57+
// page.on("loaded", pageLoaded);
58+
// page.content = stackLayout;
59+
// page.bindingContext = model;
60+
// return page;
61+
//}

apps/tests/app/binding_tests.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<Page xmlns="http://www.nativescript.org/tns.xsd" loaded="pageLoaded">
2+
<StackLayout padding="7">
3+
<TextField text="{{ testProperty, testProperty | toUpper(paramProperty) }}" />
4+
<!--<TextField text="{{ testProperty | toUpper }}" />-->
5+
<TextField text="{{ testProperty }}" />
6+
</StackLayout>
7+
</Page>

js-libs/polymer-expressions/polymer-expressions.d.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ declare module "js-libs/polymer-expressions" {
55
}
66

77
class Expression {
8-
getValue(model);
8+
/**
9+
* Evaluates a value for an expression.
10+
* @param model - Context of the expression.
11+
* @param isBackConvert - Denotes if the convertion is forward (from model to ui) or back (ui to model).
12+
* @param changedModel - A property bag which contains all changed properties (in case of two way binding).
13+
*/
14+
getValue(model, isBackConvert, changedModel);
915
}
1016
}
1117

js-libs/polymer-expressions/polymer-expressions.js

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,17 @@ var Path = require("js-libs/polymer-expressions/path-parser").Path;
5353
if (!this.valueFn_) {
5454
var name = this.name;
5555
var path = this.path;
56-
this.valueFn_ = function (model, observer) {
56+
this.valueFn_ = function (model, observer, changedModel) {
5757
if (observer)
5858
observer.addPath(model, path);
5959

60+
if (changedModel) {
61+
var result = path.getValueFrom(changedModel);
62+
if (result !== undefined) {
63+
return result;
64+
}
65+
}
66+
6067
return path.getValueFrom(model);
6168
}
6269
}
@@ -185,8 +192,8 @@ var Path = require("js-libs/polymer-expressions/path-parser").Path;
185192
// object.
186193
if (toModelDirection) {
187194
fn = fn.toModel;
188-
} else if (typeof fn.toDOM == 'function') {
189-
fn = fn.toDOM;
195+
} else if (typeof fn.toView == 'function') {
196+
fn = fn.toView;
190197
}
191198

192199
if (typeof fn != 'function') {
@@ -394,11 +401,10 @@ var Path = require("js-libs/polymer-expressions/path-parser").Path;
394401
}
395402

396403
Expression.prototype = {
397-
getValue: function (model, observer, filterRegistry) {
398-
var value = getFn(this.expression)(model, observer, filterRegistry);
404+
getValue: function (model, isBackConvert, changedModel, observer) {
405+
var value = getFn(this.expression)(model, observer, changedModel);
399406
for (var i = 0; i < this.filters.length; i++) {
400-
value = this.filters[i].transform(model, observer, filterRegistry,
401-
false, [value]);
407+
value = this.filters[i].transform(model, observer, model, isBackConvert, [value]);
402408
}
403409

404410
return value;

text/span-common.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,19 +47,24 @@ export class Span extends bindable.Bindable implements definition.Span, view.App
4747
}
4848
}
4949

50-
get foregroundColor(): colorModule.Color {
51-
return this._foregroundColor;
52-
}
53-
set foregroundColor(value: colorModule.Color) {
54-
var foreColor;
50+
private _getColorValue(value: any): colorModule.Color {
51+
var result;
5552
if (types.isString(value) && (<any>value).indexOf("#") === 0) {
56-
foreColor = new colorModule.Color(<any>value);
53+
result = new colorModule.Color(<any>value);
5754
}
5855
else {
59-
foreColor = value;
56+
result = value;
6057
}
61-
if (this._foregroundColor !== foreColor) {
62-
this._foregroundColor = foreColor;
58+
return result;
59+
}
60+
61+
get foregroundColor(): colorModule.Color {
62+
return this._foregroundColor;
63+
}
64+
set foregroundColor(value: colorModule.Color) {
65+
var convertedColor = this._getColorValue(value);
66+
if (this._foregroundColor !== convertedColor) {
67+
this._foregroundColor = convertedColor;
6368
this.updateAndNotify();
6469
}
6570
}
@@ -68,8 +73,9 @@ export class Span extends bindable.Bindable implements definition.Span, view.App
6873
return this._backgroundColor;
6974
}
7075
set backgroundColor(value: colorModule.Color) {
71-
if (this._backgroundColor !== value) {
72-
this._backgroundColor = value;
76+
var convertedColor = this._getColorValue(value);
77+
if (this._backgroundColor !== convertedColor) {
78+
this._backgroundColor = convertedColor;
7379
this.updateAndNotify();
7480
}
7581
}

ui/builder/component-builder.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export function getComponentModule(elementName: string, namespace: string, attri
7777
// Get the event handler from instance.bindingContext.
7878
var propertyChangeHandler = (args: observable.PropertyChangeData) => {
7979
if (args.propertyName === "bindingContext") {
80-
var handler = instance.bindingContext && instance.bindingContext[getPropertyNameFromBinding(attrValue)];
80+
var handler = instance.bindingContext && instance.bindingContext[getBindingExpressionFromAttribute(attrValue)];
8181
// Check if the handler is function and add it to the instance for specified event name.
8282
if (types.isFunction(handler)) {
8383
instance.on(attr, handler, instance.bindingContext);
@@ -157,10 +157,10 @@ function isKnownEvent(name: string, exports: any): boolean {
157157
}
158158

159159
function getBinding(instance: view.View, name: string, value: string): bindable.BindingOptions {
160-
return { targetProperty: name, sourceProperty: getPropertyNameFromBinding(value), twoWay: true };
160+
return bindable.Bindable._getBindingOptions(name, getBindingExpressionFromAttribute(value));
161161
}
162162

163-
function getPropertyNameFromBinding(value: string): string {
163+
function getBindingExpressionFromAttribute(value: string): string {
164164
return value.replace("{{", "").replace("}}", "").trim();
165165
}
166166

ui/core/bindable.d.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,27 @@
1717
* True to establish a two-way binding, false otherwise. A two-way binding will synchronize both the source and the target property values regardless of which one initiated the change.
1818
*/
1919
twoWay?: boolean;
20+
/**
21+
* An expression used for calculations (convertions) based on the value of the property.
22+
*/
23+
expression?: string;
24+
}
25+
26+
/**
27+
* An interface which defines methods need to create binding value converter.
28+
*/
29+
export interface ValueConverter {
30+
/**
31+
* A method that will be executed when a value (of the binding property) should be converted to the observable model.
32+
* For example: user types in a text field, but our business logic requires to store data in a different manner (e.g. in lower case).
33+
* @param params - An array of parameters where first element is the value of the property and next elements are parameters send to converter.
34+
*/
35+
toModel: (...params: any[]) => any;
36+
/**
37+
* A method that will be executed when a value should be converted to the UI view. For example we have a date object which should be displayed to the end user in a specific date format.
38+
* @param params - An array of parameters where first element is the value of the property and next elements are parameters send to converter.
39+
*/
40+
toView: (...params: any[]) => any;
2041
}
2142

2243
/**
@@ -45,6 +66,7 @@
4566
unbind(property: string);
4667

4768
//@private
69+
static _getBindingOptions(name: string, bindingExpression: string): BindingOptions;
4870
_updateTwoWayBinding(propertyName: string, value: any);
4971
_onBindingContextChanged(oldValue: any, newValue: any);
5072
//@endprivate

ui/core/bindable.ts

Lines changed: 61 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import types = require("utils/types");
66
import trace = require("trace");
77
import polymerExpressions = require("js-libs/polymer-expressions");
88

9+
var expressionSymbolsRegex = /[ \+\-\*%\?:<>=!\|&\(\)\[\]]/;
10+
911
var bindingContextProperty = new dependencyObservable.Property(
1012
"bindingContext",
1113
"Bindable",
@@ -105,7 +107,7 @@ export class Bindable extends dependencyObservable.DependencyObservable implemen
105107
}
106108

107109
trace.write(
108-
"Binding target: " + binding.target.get() +
110+
"Binding target: " + binding.target.get() +
109111
" targetProperty: " + binding.options.targetProperty +
110112
" to the changed context: " + newValue, trace.categories.Binding);
111113
binding.unbind();
@@ -114,6 +116,31 @@ export class Bindable extends dependencyObservable.DependencyObservable implemen
114116
}
115117
}
116118
}
119+
120+
private static extractPropertyNameFromExpression(expression: string): string {
121+
var firstExpressionSymbolIndex = expression.search(expressionSymbolsRegex);
122+
if (firstExpressionSymbolIndex > -1) {
123+
return expression.substr(0, firstExpressionSymbolIndex);
124+
}
125+
else {
126+
return expression;
127+
}
128+
}
129+
130+
public static _getBindingOptions(name: string, bindingExpression: string): definition.BindingOptions {
131+
var result: definition.BindingOptions;
132+
result = {
133+
targetProperty: name,
134+
sourceProperty: ""
135+
};
136+
if (types.isString(bindingExpression)) {
137+
var params = bindingExpression.split(",");
138+
result.sourceProperty = Bindable.extractPropertyNameFromExpression(params[0]);
139+
result.expression = params[1];
140+
result.twoWay = params[2] ? params[2].toLowerCase() === "true" : true;
141+
}
142+
return result;
143+
}
117144
}
118145

119146
export class Binding {
@@ -142,16 +169,16 @@ export class Binding {
142169
if (typeof (obj) === "number") {
143170
obj = new Number(obj);
144171
}
145-
172+
146173
if (typeof (obj) === "boolean") {
147174
obj = new Boolean(obj);
148175
}
149-
176+
150177
if (typeof (obj) === "string") {
151178
obj = new String(obj);
152179
}
153180
/* tslint:enable */
154-
181+
155182
this.source = new WeakRef(obj);
156183
this.updateTarget(this.getSourceProperty());
157184

@@ -188,34 +215,52 @@ export class Binding {
188215

189216
public updateTwoWay(value: any) {
190217
if (this.options.twoWay) {
191-
this.updateSource(value);
218+
if (this._isExpression(this.options.expression)) {
219+
var changedModel = {};
220+
changedModel[this.options.sourceProperty] = value;
221+
this.updateSource(this._getExpressionValue(this.options.expression, true, changedModel));
222+
}
223+
else {
224+
this.updateSource(value);
225+
}
192226
}
193227
}
194228

195229
private _isExpression(expression: string): boolean {
196-
return expression.indexOf(" ") !== -1;
230+
if (expression) {
231+
var result = expression.indexOf(" ") !== -1;
232+
return result;
233+
}
234+
else {
235+
return false;
236+
}
197237
}
198238

199-
private _getExpressionValue(expression: string): any {
200-
var exp = polymerExpressions.PolymerExpressions.getExpression(expression);
201-
if (exp) {
202-
return exp.getValue(this.source && this.source.get && this.source.get() || global);
239+
private _getExpressionValue(expression: string, isBackConvert: boolean, changedModel: any): any {
240+
try {
241+
var exp = polymerExpressions.PolymerExpressions.getExpression(expression);
242+
if (exp) {
243+
var context = this.source && this.source.get && this.source.get() || global;
244+
return exp.getValue(context, isBackConvert, changedModel);
245+
}
246+
return undefined;
247+
}
248+
catch (e) {
249+
return undefined;
203250
}
204-
205-
return undefined;
206251
}
207252

208253
public onSourcePropertyChanged(data: observable.PropertyChangeData) {
209-
if (this._isExpression(this.options.sourceProperty)) {
210-
this.updateTarget(this._getExpressionValue(this.options.sourceProperty));
254+
if (this._isExpression(this.options.expression)) {
255+
this.updateTarget(this._getExpressionValue(this.options.expression, false, undefined));
211256
} else if (data.propertyName === this.options.sourceProperty) {
212257
this.updateTarget(data.value);
213258
}
214259
}
215260

216261
private getSourceProperty() {
217-
if (this._isExpression(this.options.sourceProperty)) {
218-
return this._getExpressionValue(this.options.sourceProperty);
262+
if (this._isExpression(this.options.expression)) {
263+
return this._getExpressionValue(this.options.expression, false, undefined);
219264
}
220265

221266
if (!this.sourceOptions) {

0 commit comments

Comments
 (0)