Support for binding expressions in event bindings

This commit is contained in:
Vladimir Enchev
2015-09-12 11:02:32 +03:00
parent 67c29e728d
commit 81c066e5e3
5 changed files with 80 additions and 62 deletions

View File

@ -560,6 +560,43 @@ export function test_parse_ShouldParseNestedListViewInListViewTemplate() {
} }
} }
export function test_parse_ShouldEvaluateEventBindingExpressionInListViewTemplate() {
var p = <Page>builder.parse('<Page xmlns="http://www.nativescript.org/tns.xsd"><ListView items="{{ items }}" itemLoading="{{ itemLoading }}"><ListView.itemTemplate><SegmentedBar items="{{ $parents[\'ListView\'].items }}" selectedIndexChanged="{{ $parents[\'ListView\'].changed }}" /></ListView.itemTemplate></ListView></Page>');
function testAction(views: Array<viewModule.View>) {
var ctrl: segmentedBar.SegmentedBar;
var changed;
var obj = new observable.Observable();
obj.set("items", [1, 2, 3]);
obj.set("itemLoading", function (args: listViewModule.ItemEventData) {
ctrl = <segmentedBar.SegmentedBar>args.view
});
obj.set("changed", function (args: observable.EventData) {
changed = true;
});
p.bindingContext = obj;
TKUnit.wait(0.2);
ctrl.selectedIndex = 1;
TKUnit.assert(changed, "Expected result: true!; Actual result: " + changed);
};
helper.navigate(function () { return p; });
try {
testAction([p.content, p]);
}
finally {
helper.goBack();
}
}
export function test_parse_NestedRepeaters() { export function test_parse_NestedRepeaters() {
var pageXML = var pageXML =
"<Page xmlns='http://www.nativescript.org/tns.xsd'>" + "<Page xmlns='http://www.nativescript.org/tns.xsd'>" +

View File

@ -1,10 +1,8 @@
import observable = require("data/observable"); import view = require("ui/core/view");
import view = require("ui/core/view");
import bindable = require("ui/core/bindable"); import bindable = require("ui/core/bindable");
import types = require("utils/types"); import types = require("utils/types");
import definition = require("ui/builder/component-builder"); import definition = require("ui/builder/component-builder");
import fs = require("file-system"); import fs = require("file-system");
import gestures = require("ui/gestures");
import bindingBuilder = require("ui/builder/binding-builder"); import bindingBuilder = require("ui/builder/binding-builder");
import platform = require("platform"); import platform = require("platform");
import pages = require("ui/page"); import pages = require("ui/page");
@ -29,8 +27,6 @@ var MODULES = {
var CODEFILE = "codeFile"; var CODEFILE = "codeFile";
var CSSFILE = "cssFile"; var CSSFILE = "cssFile";
var eventHandlers = {};
export function getComponentModule(elementName: string, namespace: string, attributes: Object, exports: Object): definition.ComponentModule { export function getComponentModule(elementName: string, namespace: string, attributes: Object, exports: Object): definition.ComponentModule {
var instance: view.View; var instance: view.View;
var instanceModule: Object; var instanceModule: Object;
@ -140,8 +136,6 @@ export function getComponentModule(elementName: string, namespace: string, attri
} }
} }
eventHandlers = {};
componentModule = { component: instance, exports: instanceModule, bindings: bindings }; componentModule = { component: instance, exports: instanceModule, bindings: bindings };
} }
@ -150,21 +144,16 @@ export function getComponentModule(elementName: string, namespace: string, attri
export function setPropertyValue(instance: view.View, instanceModule: Object, exports: Object, propertyName: string, propertyValue: string) { export function setPropertyValue(instance: view.View, instanceModule: Object, exports: Object, propertyName: string, propertyValue: string) {
// Note: instanceModule can be null if we are loading custom compnenet with no code-behind. // Note: instanceModule can be null if we are loading custom compnenet with no code-behind.
var isEventOrGesture: boolean = isKnownEventOrGesture(propertyName, instance);
if (isBinding(propertyValue) && instance.bind) { if (isBinding(propertyValue) && instance.bind) {
if (isEventOrGesture) { var bindOptions = bindingBuilder.getBindingOptions(propertyName, getBindingExpressionFromAttribute(propertyValue));
attachEventBinding(instance, propertyName, propertyValue); instance.bind({
} else { sourceProperty: bindOptions[bindingBuilder.bindingConstants.sourceProperty],
var bindOptions = bindingBuilder.getBindingOptions(propertyName, getBindingExpressionFromAttribute(propertyValue)); targetProperty: bindOptions[bindingBuilder.bindingConstants.targetProperty],
instance.bind({ expression: bindOptions[bindingBuilder.bindingConstants.expression],
sourceProperty: bindOptions[bindingBuilder.bindingConstants.sourceProperty], twoWay: bindOptions[bindingBuilder.bindingConstants.twoWay]
targetProperty: bindOptions[bindingBuilder.bindingConstants.targetProperty], }, bindOptions[bindingBuilder.bindingConstants.source]);
expression: bindOptions[bindingBuilder.bindingConstants.expression], } else if (view.isEventOrGesture(propertyName, instance)) {
twoWay: bindOptions[bindingBuilder.bindingConstants.twoWay]
}, bindOptions[bindingBuilder.bindingConstants.source]);
}
} else if (isEventOrGesture) {
// Get the event handler from page module exports. // Get the event handler from page module exports.
var handler = exports && exports[propertyValue]; var handler = exports && exports[propertyValue];
@ -196,33 +185,6 @@ export function setPropertyValue(instance: view.View, instanceModule: Object, ex
} }
} }
function attachEventBinding(instance: view.View, eventName: string, value: string) {
// Get the event handler from instance.bindingContext.
eventHandlers[eventName] = (args: observable.PropertyChangeData) => {
if (args.propertyName === "bindingContext") {
var handler = instance.bindingContext && instance.bindingContext[getBindingExpressionFromAttribute(value)];
// Check if the handler is function and add it to the instance for specified event name.
if (types.isFunction(handler)) {
instance.on(eventName, handler, instance.bindingContext);
}
instance.off(observable.Observable.propertyChangeEvent, eventHandlers[eventName]);
}
};
instance.on(observable.Observable.propertyChangeEvent, eventHandlers[eventName]);
}
function isKnownEventOrGesture(name: string, instance: any): boolean {
if (types.isString(name)) {
var evt = `${name}Event`;
return instance.constructor && evt in instance.constructor ||
gestures.fromString(name.toLowerCase()) !== undefined;
}
return false;
}
function getBindingExpressionFromAttribute(value: string): string { function getBindingExpressionFromAttribute(value: string): string {
return value.replace("{{", "").replace("}}", "").trim(); return value.replace("{{", "").replace("}}", "").trim();
} }

View File

@ -188,12 +188,12 @@ export class Binding {
} }
private static getProperties(property: string): Array<string> { private static getProperties(property: string): Array<string> {
if (property) { if (property) {
return property.split("."); return property.split(".");
} }
else { else {
return []; return [];
} }
} }
private resolveObjectsAndProperties(source: Object, propsArray: Array<string>) { private resolveObjectsAndProperties(source: Object, propsArray: Array<string>) {
@ -336,8 +336,8 @@ export class Binding {
this.prepareContextForExpression(context, expression); this.prepareContextForExpression(context, expression);
model[contextKey] = context; model[contextKey] = context;
return exp.getValue(model, isBackConvert, changedModel); return exp.getValue(model, isBackConvert, changedModel);
} }
return new Error(expression + " is not a valid expression."); return new Error(expression + " is not a valid expression.");
} }
@ -484,10 +484,10 @@ export class Binding {
private getParentView(target, property) { private getParentView(target, property) {
if (!target || !(target instanceof viewModule.View)) { if (!target || !(target instanceof viewModule.View)) {
return {view: null, index: null}; return { view: null, index: null };
} }
var result; var result;
if (property === bc.parentValueKey) { if (property === bc.parentValueKey) {
result = target.parent; result = target.parent;
} }
@ -499,7 +499,7 @@ export class Binding {
if (indexParams && indexParams.length > 1) { if (indexParams && indexParams.length > 1) {
index = indexParams[2]; index = indexParams[2];
} }
if (!isNaN(index)) { if (!isNaN(index)) {
var indexAsInt = parseInt(index); var indexAsInt = parseInt(index);
while (indexAsInt > 0) { while (indexAsInt > 0) {
@ -544,10 +544,16 @@ export class Binding {
this.updating = true; this.updating = true;
try { try {
if (optionsInstance instanceof observable.Observable) { if (optionsInstance instanceof viewModule.View &&
optionsInstance.set(options.property, value); viewModule.isEventOrGesture(options.property, optionsInstance) &&
types.isFunction(value)) {
optionsInstance.on(options.property, value, optionsInstance.bindingContext);
} else { } else {
optionsInstance[options.property] = value; if (optionsInstance instanceof observable.Observable) {
optionsInstance.set(options.property, value);
} else {
optionsInstance[options.property] = value;
}
} }
} }
catch (ex) { catch (ex) {

View File

@ -15,6 +15,17 @@ import color = require("color");
import animationModule = require("ui/animation"); import animationModule = require("ui/animation");
import observable = require("data/observable"); import observable = require("data/observable");
export function isEventOrGesture(name: string, view: View): boolean {
if (types.isString(name)) {
var evt = `${name}Event`;
return view.constructor && evt in view.constructor ||
gestures.fromString(name.toLowerCase()) !== undefined;
}
return false;
}
export function getViewById(view: View, id: string): View { export function getViewById(view: View, id: string): View {
if (!view) { if (!view) {
return undefined; return undefined;

2
ui/core/view.d.ts vendored
View File

@ -30,6 +30,8 @@ declare module "ui/core/view" {
*/ */
export function getAncestor(view: View, criterion: string | Function): View; export function getAncestor(view: View, criterion: string | Function): View;
export function isEventOrGesture(name: string, view: View): boolean;
/** /**
* Defines interface for an optional parameter used to create a view. * Defines interface for an optional parameter used to create a view.
*/ */