From 81c066e5e335add12fb68eb6f767af3653c54ad9 Mon Sep 17 00:00:00 2001 From: Vladimir Enchev Date: Sat, 12 Sep 2015 11:02:32 +0300 Subject: [PATCH] Support for binding expressions in event bindings --- .../xml-declaration/xml-declaration-tests.ts | 37 ++++++++++++ ui/builder/component-builder.ts | 56 +++---------------- ui/core/bindable.ts | 36 +++++++----- ui/core/view-common.ts | 11 ++++ ui/core/view.d.ts | 2 + 5 files changed, 80 insertions(+), 62 deletions(-) diff --git a/apps/tests/xml-declaration/xml-declaration-tests.ts b/apps/tests/xml-declaration/xml-declaration-tests.ts index 9d0079a1c..5f933a2bf 100644 --- a/apps/tests/xml-declaration/xml-declaration-tests.ts +++ b/apps/tests/xml-declaration/xml-declaration-tests.ts @@ -560,6 +560,43 @@ export function test_parse_ShouldParseNestedListViewInListViewTemplate() { } } +export function test_parse_ShouldEvaluateEventBindingExpressionInListViewTemplate() { + var p = builder.parse(''); + + function testAction(views: Array) { + 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 = 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 = "" + diff --git a/ui/builder/component-builder.ts b/ui/builder/component-builder.ts index ba026e74b..3878a4d38 100644 --- a/ui/builder/component-builder.ts +++ b/ui/builder/component-builder.ts @@ -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(); } diff --git a/ui/core/bindable.ts b/ui/core/bindable.ts index b79e922e3..9cc0f646a 100644 --- a/ui/core/bindable.ts +++ b/ui/core/bindable.ts @@ -188,12 +188,12 @@ export class Binding { } private static getProperties(property: string): Array { - if (property) { - return property.split("."); - } - else { - return []; - } + if (property) { + return property.split("."); + } + else { + return []; + } } private resolveObjectsAndProperties(source: Object, propsArray: Array) { @@ -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,10 +484,10 @@ 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; + + var result; if (property === bc.parentValueKey) { result = target.parent; } @@ -499,7 +499,7 @@ export class Binding { if (indexParams && indexParams.length > 1) { index = indexParams[2]; } - + if (!isNaN(index)) { var indexAsInt = parseInt(index); while (indexAsInt > 0) { @@ -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) { diff --git a/ui/core/view-common.ts b/ui/core/view-common.ts index f8f741f4e..358ef8d56 100644 --- a/ui/core/view-common.ts +++ b/ui/core/view-common.ts @@ -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; diff --git a/ui/core/view.d.ts b/ui/core/view.d.ts index 6981bbbbf..ef69f1304 100644 --- a/ui/core/view.d.ts +++ b/ui/core/view.d.ts @@ -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. */