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() {
var pageXML =
"<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 types = require("utils/types");
import definition = require("ui/builder/component-builder");
import fs = require("file-system");
import gestures = require("ui/gestures");
import bindingBuilder = require("ui/builder/binding-builder");
import platform = require("platform");
import pages = require("ui/page");
@ -29,8 +27,6 @@ var MODULES = {
var CODEFILE = "codeFile";
var CSSFILE = "cssFile";
var eventHandlers = {};
export function getComponentModule(elementName: string, namespace: string, attributes: Object, exports: Object): definition.ComponentModule {
var instance: view.View;
var instanceModule: Object;
@ -140,8 +136,6 @@ export function getComponentModule(elementName: string, namespace: string, attri
}
}
eventHandlers = {};
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) {
// 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 (isEventOrGesture) {
attachEventBinding(instance, propertyName, propertyValue);
} else {
var bindOptions = bindingBuilder.getBindingOptions(propertyName, getBindingExpressionFromAttribute(propertyValue));
instance.bind({
sourceProperty: bindOptions[bindingBuilder.bindingConstants.sourceProperty],
targetProperty: bindOptions[bindingBuilder.bindingConstants.targetProperty],
expression: bindOptions[bindingBuilder.bindingConstants.expression],
twoWay: bindOptions[bindingBuilder.bindingConstants.twoWay]
}, bindOptions[bindingBuilder.bindingConstants.source]);
}
} else if (isEventOrGesture) {
var bindOptions = bindingBuilder.getBindingOptions(propertyName, getBindingExpressionFromAttribute(propertyValue));
instance.bind({
sourceProperty: bindOptions[bindingBuilder.bindingConstants.sourceProperty],
targetProperty: bindOptions[bindingBuilder.bindingConstants.targetProperty],
expression: bindOptions[bindingBuilder.bindingConstants.expression],
twoWay: bindOptions[bindingBuilder.bindingConstants.twoWay]
}, bindOptions[bindingBuilder.bindingConstants.source]);
} else if (view.isEventOrGesture(propertyName, instance)) {
// Get the event handler from page module exports.
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 {
return value.replace("{{", "").replace("}}", "").trim();
}

View File

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

View File

@ -15,6 +15,17 @@ import color = require("color");
import animationModule = require("ui/animation");
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 {
if (!view) {
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 isEventOrGesture(name: string, view: View): boolean;
/**
* Defines interface for an optional parameter used to create a view.
*/