感谢您的访问。此页面目前仅提供英语版本。我们正在开发中文版本。谢谢您的理解。

APL Data-Binding Evaluation

APL documents rely on data-binding expressions to:

  • Incorporate user-provided data
  • Include styles and other visual resources
  • Pass parameters into layouts
  • Conditionally inflate layouts based on screen characteristics.

Data-binding syntax is covered separately.

Thus, understanding how data-binding works is essential to understanding APL.

Data-binding algorithm

Consider the definition of a Text component in a sample APL document.

{
  "type": "Text",
  "text": "There are ${data.value} peas in the pod",
  "color": "@myBlue",
  "fontSize": "${@textSizePrimary * 1.2}"
}

This definition includes three expressions to evaluate. First, the text has the data.value number inserted into a longer expression. Second, the color is set to "myBlue", which in this case refers to a user-defined resource. Finally, the fontSize is set to 1.2 times larger than the standard primary text size of the document.

All of these expressions are evaluated using the following three-step algorithm: Expression evaluation, Resource lookup, and type coercion.

Step 1: Expression evaluation

If the right-hand side (RHS) is a string, the algorithm scans the string for data-binding expressions (embedded '${}'). If it finds one or more embedded expressions, the entire string is converted to an abstract syntax tree (AST). For example, the text example above is converted to this tree:

Concatenate
  String("There are ")
  AttributeLookup
    Symbol("data")
    String("value")
  String(" peas in the pod")

The AST is now evaluated using the current data-binding context. The data-binding context is a JSON dictionary of key-value pairs, where each value can be a number, boolean, object, array, or string. Operators such as "+" do implicit type conversion as necessary. For example, if the data-binding context is as shown here, then the "data" symbol will return the object "{ value: 5}" and the attribute accessor of "value" will return the number 5. The Ternary concatenation operator has two strings and a number, and thus will cast the number as a string and return "There are 5 peas in the pod" as a string value.

{
  "data": {
    "value": 5
  }
}

Step 2: Resource lookup

After data-binding, string values may be a reference to a system-defined resource. Resources references start with "@" and consist of a single, unhyphenated word. Resources are typed. If you refer back to the original example, the color of the Text is set to @myBlue. The evaluator looks for a resource with the name @myBlue in the colors resources. Assume that the user has provided a resources block of the form:

{
  "resources": [
    {
      "colors": {
        "myBlue": "#0033ff"
      }
    }
  ]
}

Then the resource lookup stage will translate "@myBlue" into "#0033ff".

Step 3: Type coercion

The final step is to ensure that the set value is of the correct type. In the color example, a string value of "#0033ff" has been returned. Since the target value is known to be a color, this string value must be converted into the correct internal type for a color.

Initial data-binding context

The data-binding context is cleared when a new APL document is inflated. The cleared data-binding context currently has the following pre-defined objects:

Name Description
environment Information about the current runtime environment.
viewport Configuration information about the current device. See Viewport Characteristics
Math Built-in mathematical functions. See Function calls.
String Built-in string functions. See Function calls.

environment

The environment object contains runtime information about the operating APL environment. It contains the following properties:

Name Description
agentName Name of the runtime environment
agentVersion Version of the runtime environment

The information provided by agentName and agentVersion is meant to assist with debugging for teams that develop runtime agents used to render APL documents. If available, please include this information if you report APL bugs or issues. Do not use the values of these properties to provide conditional responses based on these values, because these could change if an agent is updated.

Extending the data-binding context

APL layouts have parameters. Inflating a layout adds the named parameters to the data-binding context, as shown in this example.

  "myQuoteLayout": {
    "parameters": [ "quotes" ],
    "item": {
      "type": "Text",
      "text": "${quotes.shakespeareQuotes[0]}"
    }
  }
// Inflation
{
  "type": "myQuoteLayout",
  "quotes": {
    "shakespeareQuotes": [
      "First thing we do, let's kill all the lawyers.",
      "The lady doth protest too much, methinks.",
      "Love all, trust a few, do wrong to none."
    ],
  "shakespeareSonnets": [
     "Weary with toil, I haste me to my bed, The dear repose for limbs..." 
	 ]
  }
)

In this example, quotes is a parameter for the custom myQuoteLayout. When myQuoteLayout is used, the data-binding context is extended by adding a new quotes property with a matching value. If quotes has not been specified, the default value for quotes (usually null) will be added to the data-binding context. This augmented data-binding context is valid only for the inflation of the custom layout and its children.

Extensions to the data-binding context

As components are inflated, the data-binding context is extended to pass additional information to the inflated component.

Component bind extension

Each component has an optional bind property that extends the current data-binding context. The bind property is an ordered array of (name, value, type) tuples. Each value is evaluated in the current data-binding context and then added to the context.

Component child extension

Certain components, such as Sequence, Container, and Pager have multiple children. These components add the following global names to the data-binding context.

Name Description
data New data assigned from a user-specified data array during component inflation.
index The 0-based index of the current child.
ordinal The 1-based ordinal of the current child. See numbering under Sequence and Container components to see how ordinals work.
length The total number of children in the current component.

Note that data and ordinal are only set if the appropriate property has been set in the component.

Explicitly Propagating Data Binding Context

In some cases, the index, ordinal, or length property of a child must be propagated down. For example, if a Sequence's child is itself a Container, then the index property for the children of the Container will be based on the child Container rather than the Sequence. In a situation like this, to propagate the Container's index to its children, you can use the "bind" property as follows:

{
  "type": "Sequence",
  "width": "100%",
  "height": "100%",
  "numbered": true,
  "data": "${payload.templateData.properties.rows}",
  "item": {
    "type": "Container",
    "bind": [
      {
        "name": "parentIndex",
        "value": "${index}"
      }
    ],
    "items": [
      {
        "type": "Text",
        "text": "Index ${parentIndex}"
      }
    ]
  }
}

Resource definitions

New resources defined in a package are added to the current data-binding context following the pattern @. For example, the "myBlue" color above can be explicitly referenced as "${@myBlue}" as well as implicitly by "@myBlue".

Data-binding with arrays

Many APL expressions involve evaluating an array. APL supports type coercion of arrays, implicit array-ification, and interpolation of data-bound expressions into arrays.

Array type coercion

If a property is defined as holding an array of a known type, then during property assignment each element in the array will be coerced to that type. For example, a layout expecting an array of numbers would coerce each element of that array into a number during assignment.

Implicit array-ification

For convenience, all APL properties that take an array of values will also accept a single property without the array brackets. For example, the items property of a container is defined as an array of component. If only a single item is passed, these approaches are equivalent:

"item": {<<ITEM>>}
"item": [ {<<ITEM>>} ]

The APL runtime expands both of these into an array of length 1.

Many of the properties that expect an array have a plural alias. Thus, item and items are the same property.

Interpolation of data-bound expressions into arrays

Array expansion supports the interpolation of arrays into arrays. For example:

// Context
{
	"a": "value",
	"b": [ "alpha", "bravo" ]
}

"values": "${a}" 		-> values = [ "value" ] // Implicit array-ification
"values": [ "${a}" ] 	-> values = [ "value" ]
"values": "${b}" 		-> values = [ "alpha", "bravo" ]
"values": [ "x", "${b}", "${a}" ] -> values = [ "x", "alpha", "bravo", "value" ]

The rules of array-ification are:

  • If the value is a string, evaluate it using data-binding and coerce it to the correct type (and apply array-ification).

  • If the value is an array, for each element of the array that is a string, evaluate it using data binding. If the result is a single item, insert it in the array. If it is an array of items, insert all of them into the array.