loading table of contents...

5.3.6. Value Expressions

The interface com.coremedia.ui.data.ValueExpression describes objects that provide access to a possibly mutable value and that notify listeners when the value changes. They may also allow you to receive a value that can then become the next value of the expression. Value expressions may be as simple as defining a one-to-one wiring of a widget property to a model property, but they may encapsulate complex logic that accesses many objects to determine a result value. As an application developer, you can think of value expressions as an abstraction layer that hides that potential complexity from you, and use a common, simple interface when wiring up UI state to complex model state.

The Studio SDK offers the following primary implementations of the ValueExpression interface. You can use the factory methods from com.coremedia.ui.data.ValueExpressionFactory to create a ValueExpression programmatically from ActionScript.

  • PropertyPathExpression. This is meant to be used in simple scenarios, where you want to attach a simple bean property to a corresponding widget property. It starts from a bean and navigates through a path of property names to a value. Long paths can be separated with a dot. You can obtain this value expression flavor using ValueExpressionFactory#create(expression, bean).

  • FunctionValueExpression. Use this in scenarios where your UI state requires potentially complex calculations on the model, using multiple beans (remote or local). This value expression object wraps an ActionScript function computing the expression's value. When a listener is attached to the returned value expression, the current value of the expression is cached, and dependencies of the computation are tracked. As soon as a dependency is invalidated, the cached value is invalidated and eventually a change event is sent to all listeners (if the computed value has actually changed). You can use ValueExpressionFactory#createFromFunction(function, ...args) to create this flavor. See below for details on how to use FunctionValueExpressions.

In many cases, you can use the facilities provided by plugins (and thus use EXML to specify your value expression), without ever constructing a value expression programmatically. Nevertheless, value expressions are a vital part of the Studio SDK's data binding framework, so it is helpful to understand how they work.

Values

The method getValue() returns the current value of the expression. How this value is computed depends on the type of value expression used. Like bean properties, value expressions may evaluate to any ActionScript value.

When a value expression accesses remote beans that have not yet been loaded, its value is undefined. Getting the value or attaching a change listener (see below) subsequently triggers loading all remote bean necessary to evaluate the expression. If you need a defined value, you can use the loadValue(Function) method instead. The loadValue method ensures that all remote beans have been loaded and only then calls back the given function (and, in contrast to change listeners, only once, see below) with the concrete value, which is never undefined.

Like remote beans, value expressions may turn out to be unreadable due to missing read rights. In this case, getValue() returns undefined, too, and the special condition is signaled by the method isReadable() returning false.

Events

A listener may be attached to a value expression using the method addChangeListener(listener) and removed using the method removeChangeListener(listener). The listener must be a function that takes the value expression as its single argument. The listener may then query the value expression for the current value.

Contrary to bean events, value expression events are sent asynchronously after the calls modifying the value have already completed. The framework does however not guarantee that listeners are notified on all changes of the value. When the value is updated many times in quick succession, some intermediate values might not be visible to the listener.

The listener is also notified when the readability of the value changes.

As long as you have a listener attached to a value expression, the value expression may in turn be registered as a listener at other objects. To make sure that the value expression can be garbage collected, you must eventually remove all listeners added to it.

A common pattern when adding a listener to a value expression involves an upfront initialization and subsequent updates on events:

import com.coremedia.ui.data.ValueExpression;

public class MyComponentBase extends AnExtJSComponent {
  public function MyComponentBase(config:Object = undefined) {
    ...
    var valueExpr:ValueExpression = ...;
    valueExpr.addChangeListener(valueExprChanged);
    valueExprChanged(valueExpr);
    ...
  }

  private function valueExprChanged(valueExpr:
    ValueExpression):void
  {
    var value:* = valueExpr.getValue();
    ...
  }
  ...
}

Example 5.10. Adding a listener and initializing


By calling the private function once immediately after adding the listener, it is possible to reuse the functionality of the listener for initializing the component.

Property Path Expressions

The most commonly used value expression is the property path expression. It allows you to navigate from an object to a value by successively reading property values on which the next read operation takes place. For example, a property path expression may operate on the object obj and be configured to read the properties a, b, and then c. If the property a of obj is obj1, the property b of obj1 is obj2, and the property c of obj2 is 4, then the expression will evaluate to 4. A path of property names is denoted by a string that joins the property names with dots, in this case "a.b.c". If you want to address array elements you have to add the index of the element with another dot, such as a.b.c.3, and not use the more obvious but false a.b.c[3] notation.

You can create a property path expression manually in the following way:

import com.coremedia.ui.data.ValueExpression;
import com.coremedia.ui.data.ValueExpressionFactory;
...
var ppe:ValueExpression =
  ValueExpressionFactory.create("a.b.c", obj);

Example 5.11. Creating a property path expression


The dot notation above might suggest that property path expressions operate exactly like ActionScript expressions, but that is not quite correct. Property path expressions support the following access methods for properties:

  • read the property of a bean using the get(property) method;

  • call a publicly defined getter method whose name consists of the string "get" followed the name of the property, first letter capitalized;

  • call a publicly defined getter method whose name consists of the string "is" followed the name of the property, first letter capitalized;

  • read from a publicly defined field of an object. This is the classic ActionScript case.

At different steps in the property path, different access methods may be used.

Even if there are many properties in the path, changes to any of the objects traversed while computing the value will trigger a recomputation of the expression value and potentially, if the value has changed, an event. This is only possible, however, for objects that can send property change events.

  • For beans, a listener is registered using addPropertyChangeListener().

  • For instances of ext.util.Observable, a listener is registered using addListener().

Property path expressions may be updated. When invoking setValue(value), a new value for the value expression is established. This will only work if the last property in the property path is writable for the object computed by the prefix of the path. More precisely, a value may be

  • written into a property of a bean using the set(property,value) method;

  • passed to a publicly defined setter method that takes the new value as its single argument and whose name consists of the string "set" followed by the name of the property, first letter capitalized;

  • written into a publicly defined field of an ActionScript class.

At various points of the API, a value expression is provided to allow a component to bind to varying data. Using the method extendBy(extensionPath) adds further property dereferencing steps to the existing expression. For example, ValueExpressionFactory.create("a.b.c", obj) is equivalent to ValueExpressionFactory.create("a", obj).extendBy("b.c").

To create a property path expression from within an EXML file, you can use the valueExpression element from the exml:com.coremedia.ui.config namespace.

...
<ui:valueExpression expression="myProperty"
  context="{getModel()}"/>
...

Example 5.12. The valueExpression EXML element


Function Value Expressions

Function value expressions differ from property path expressions in that they allow arbitrary ActionScript code to be executed while computing their values. This flexibility comes at a cost, however: such an expression cannot be used to update variables, only to compute values. They are therefore most useful to compute complex GUI state that is displayed later on.

To create a function value expression, you use the method createFromFunction of the class ValueExpressionFactory.

ValueExpressionFactory.createFromFunction(function():Object {
  return ...;
});

Example 5.13. Creating a function value expression


The function in the previous example did not take arguments. In this case, it can still use all variables in its scope as starting point for its computation or it might access global variables. To make the code more readable, you might want to define a named function in your ActionScript class and use that function when building the expression.

private function doSomething():void {
  ...
  expr = ValueExpressionFactory.
    createFromFunction(calculateSomething);
  ...
}

private function calculateSomething():Object {
  return ...;
}

Example 5.14. Creating a value expression from a private function


If you want to pass arguments to the function, you can provide them as additional argument of the factory method. The following code fragment uses this feature to pass a model bean to a static function.

private function doSomething():void {
  ...
  expr = ValueExpressionFactory.
    createFromFunction(calculateSomething, getModel());
  ...
}

private static function calculateSomething(model:Bean):Object
{
  return ...;
}

Example 5.15. Creating a value expression from a static function


Function value expressions fire value change events when their value changes. To this end, they track their dependencies on various objects when their value is computed. For accessed beans and value expressions, the dependency is taken into account automatically: whenever the bean or the value expression changes relevantly, the value of the function value expression changes automatically, and an event for the function value expression is fired.

If you access other mutable objects, you should make sure that these objects inherit from Observable, so that you can register the dependencies yourself. To this end, you can use the static methods of the class DependencyTracker. In particular, the method dependOnObservable(Observable,String) provides a way to specify the observable and the event name that indicates a relevant change. As a shortcut, the method dependOnFieldValue(Field) allows you to depend on the value of an input field.

var observable:Obserable;
var field:Field;

private function calculateSomething():Object {
  DependencyTracker.dependOnObservable(observable, "fooEvent");
  DependencyTracker.dependOnFieldValue(field);
  ... observable.fooMethod() ...;
  ... field.getValue() ...;
  return ...;
}

Example 5.16. Manual dependency tracking


If you register a dependency while no function value is being computed, the call to DependencyTracker is ignored. This means that you can register dependencies in your own functions, and the methods will work whether they are called in the context of a function value expression or not.

To create a function value expression in EXML, you have to insert an ActionScript block into the EXML:

...
<exml:import
 class="com.coremedia.ui.data.ValueExpressionFactory"/>
...
<ui:bindPropertyPlugin bindTo="{ValueExpressionFactory.
  createFromFunction(calculateSomething)}"/>
...

Example 5.17. The bindPropertyPlugin EXML element


This assumes that you have defined a function calculateSomething in the base class of your EXML component with visibility protected. Of course, you may also use static functions or anonymous functions specified inline in the EXML file, but the latter might be more difficult to read.