APL Data-Binding Syntax

You use data-binding syntax to write expressions that Alexa evaluates when displaying your document. You use data-binding expressions to bind component properties to your data source, and to write conditional logic to hide and show components depending on criteria such as the viewport characteristics.

About data binding expressions

You use data binding expressions inside JSON strings. A data binding expression has the form "${expression}". You can use any number of expressions inside a string, for example: "${2}+${2} = ${2+2}".

Expressions are evaluated within the current data-binding context. The data-binding context is a global dictionary that supports booleans, numbers, strings, arrays, objects, null, and references to defined resources.

Supported value types

Identifier

An identifier is a name used to identify a data-binding variable. Identifiers must follow the C identifier naming convention: [a-zA-Z_][a-zA-Z0-9_]*. That is, the identifier must start with an upper or lower-case ASCII letter or underscore and may be followed by zero or more ASCII letters, numbers, or the underscore:

${data}
${_myWord23}
${__AnUgly26_letter__examplE}

String literals

Strings are defined using either single or double quotes. The starting and ending quote must match. Quotes, carriage returns, and line-feeds may be escaped.

	${"Double-quoted string"}
	${'Single-quoted string'}
	${"Inner quote: \" or '"}

Expressions may be nested inside of a string.

	${"Two plus two is ${2+2}"}

Numbers

Positive, negative, and floating point numbers are supported. Scientific notation is not supported. All numbers are internally stored as doubles:

${1}
${-34.75}
${64000000000}

Booleans

Boolean values of true and false are supported.

	${true}
	${false}

null

The null constant is supported.

	${null}

Resources

Resources defined in the data-binding context use the reserved character "@". For example:

${@myBlue}
${@isLandscape ? @myWideValue : @myNarrowValue}

APL packages define resources exposed in data-binding.

Absolute dimensions

A dimensional suffix converts a number into an absolute viewport dimension. The valid dimensional suffixes are "dp", "px", "vh", and "vw":

${23 dp}     // 23 display-independent pixels
${10 px}     // 10 pixels
${50 vw}     // 50% of the width of the viewport
${100vh}     // 100% of the height of the viewport

Dimensional suffixes must be attached to a number, not a more complex expression. For example ${(20*20) dp} is not valid, but ${20*20dp} is valid.

Truthy and Coercion

Data-binding expressions involve a number of different types. These types may be converted into other types. The following table gives an example of the different conversions (note that this assumes a viewport width of 512dp and a dpi of 320):

Object Example As Boolean As Number As String As Color As Dimension
Null null false 0 "" transparent 0dp
Boolean true true 1 "true" transparent 0dp
Boolean false false 0 "false" transparent 0dp
Number 23 true 23 "23" #00000017 23dp
Number 0 false 0 "0" transparent 0dp
String "My dog" true 0 "My dog" transparent 0dp
String "" false 0 "" transparent 0dp
String "-2.3" true -2.3 "-2.3" transparent -2.3dp
String "red" true 0 "red" #ff0000ff 0dp
String "50vw" true 50 "50vw" transparent 256dp
Array [] true 0 "" transparent 0dp
Map {} true 0 "" transparent 0dp
Color red true 0 "#ff0000ff" #ff0000ff 0dp
Dimension 32px true 16 "16dp" transparent 16dp
Dimension 0vh false 0 "0dp" transparent 0dp
Dimension 23% true 0.23 "23%" transparent 23%
Dimension 0% false 0 "0%" transparent 0%
Dimension auto true 0 "auto" transparent auto
Anything else ... true 0 "" transparent 0dp
             

Boolean coercion

A truthy value is a value that is considered true when evaluated in a boolean context. All values are truthy except for false, 0, "", a zero absolute or relative dimension, and null.

Number coercion

The Boolean "true" value is converted to the number 1. String values are converted using the C++ std::stod method (note that this is influenced by the locale). Absolute dimensions convert to the number of dp in the absolute dimension; relative dimensions convert to the percentage value (e.g., 32% -> 0.32). Everything else converts to 0.

String coercion

Internal types are converted to strings using the rules in the following table:

Object Example Result Description
Null null '' The null value is not displayed.
Boolean true false 'true' 'false' Boolean true & false are displayed as strings.
Number -23 '-23' Integers have no decimal places.
  1/3 '0.333333' Non-integers have decimal places.
String "My "dog" " 'My "dog" ' String values
Array [...] '' Arrays are not displayed.
Map {...} '' Maps are not displayed
Color red '#ff0000ff' Colors are shown in #rrggbbaa format.
Dimension 23 dp '20dp' Absolute dimensions are shown with the suffix 'dp'
Dimension 20 % '20%' Percentage dimensions are shown with the suffix '%'
Dimension auto 'auto' The auto dimension is shown as 'auto'
Anything else ${Math.min} '' Math functions are not shown.

The specific format of non-integer numbers is not defined, but should follow closely the C++ standard for sprintf(buf, "%f", value). It may change based on the locale.

Color coercion

Color values are stored internally as 32-bit RGBA values. Numeric values will are treated as unsigned 32-bit integers and converted directly. String values are parsed according to the rules in Data Types - Color.

Absolute dimension coercion

Numeric values are assumed to be measurements in "dp" and are converted to absolute dimensions. String values are parsed according to the rules in Data Types - Dimension. All other values are 0.

Relative dimension coercion

Numeric values are assumed to be percentages and are converted directly. For example, 0.5 converts to 50%. Strings are parsed according to the rules in Data Types - Dimension. All other values are 0.

Operators

APL supports different types of operators: arithmetic, logical, comparison, and ternary.

Arithmetic operators

The standard arithmetic operations for addition, subtraction, multiplication, division, and remainder are supported:

${1+2}  // 3
${1-2}  // -1
${1*2}  // 2
${1/2}  // 0.5
${1%2}  // 1.

Addition and subtraction work for pairs of numbers, absolute dimensions, and relative dimensions. When a number is combined with either an absolute or relative dimension, the number is coerced into the appropriate dimension.

The addition operator also acts as a string-concatenation operator if either the left or right operand is a string.

${27+''}     // '27'
${1+' dog'}  // '1 dog'
${'have '+3} // 'have 3'

Multiplication, division, and the remainder operator work for pairs of numbers. Multiplication also works if the one of the operands is a dimension (either relative or absolute) and the other is a number; the result is a dimension. Division also works if the first operand is a dimension (either relative or absolute) and the second is a number; the result is a dimension.

The remainder operator behaves as in JavaScript. That is,

${10 % 3}  // 1
${-1 % 2}  // -1
${3 % -6}  // 3
${6.5 % 2} // 0.5

Logical operators

The standard logical and/or/not operators are supported.

${true || false}   // true
${true && false}   // false
${!true}           // false

The && returns the first operand if it is not truthy and the second otherwise. The || operator returns the first operand if it is truthy and the second otherwise.

${7 && 2}    // 2
${null && 3} // null
${7 || 2}    // 7
${0 || -16}  // -16

Comparison Operators

Comparison operators return boolean values.

${1 < 2}
${75 <= 100}
${3 > -1}
${4 >= 4}
${myNullValue == null}
${(2>1) == true}
${1 != 2}

The comparison operators do not apply to arrays and objects.

Null coalescing

The ?? operator is the null-coalescing operator. It returns the left-hand operand if the operand is not null; otherwise it returns the right-hand operand. The null-coalescing operator may be chained:

${person.name ?? person.surname ?? 'Hey, you!'}

The null-coalescing operator will return the left-hand operand if it is anything but null:

${1==2 ?? 'Dog'}   // returns false
${1==2 || 'Dog'}   // returns 'Dog'

Ternary Operator

The ternary conditional operator ${a ? b : c} evaluates the left-hand operand. If it evaluates to true or a truthy value, the middle operand is returned. Otherwise the right-hand operand is returned.

${person.rank > 8 ? 'General' : 'Private'}

Array and Object access

Array

Array access uses the [] operator, where the operand should be an integer. Arrays also support the .length operator to return the length of the array. Accessing an element outside of the array bounds returns null.

${myArray[4]}     // 5th element in the array (0-indexed)
${myArray.length} // Length of the array
${myArray[-1])}   // Last element in the array
${myArray[myArray.length]}  // Returns null (out of bounds)

Passing a negative index counts backwards through the array.

${a[-1] == a[a.length - 1]}  // True

Object

Objects support the . operator and the [] array access operator with string values.

${myObject.name}    // The 'name' property of myObject
${myObject['name']} // The 'name' property of myObject

If the property is not defined, null is returned.

Calling the . or [] operator on null returns null.

${myNullObject.address.zipcode}  // Returns null

The right-side operand of the dot operator must be a valid identifier.

Function calls

Data-binding supports a limited number of built-in functions. Functions use the form:

functionName( arg1, arg2, … )

Functions do not require arguments. A function returns a single value. The following examples show a variety of functional expressions.

${Math.floor(1.1)}       // 1
${Math.ceil(1.2)}        // 2
${Math.round(1.2)}       // 1
${Math.min(1,2,3,4)}     // 1
${Math.max(1,2,3,4)}     // 4
${String.toUpperCase('Hello')}    // HELLO
${String.toLowerCase('Hello')}    // hello
${String.slice('Hello', 1, -1)}   // ell

The available functions are grouped by top-level property:

Math functions

The top-level Math property is a collection of functions and constants for numerical calculations.

Function Description Example
Math.abs(x) The absolute value of x ${Math.abs(-2.3)} == 2.3
Math.acos(x) The arccosine of x ${Math.acos(1)} == 0
Math.asin(x) The arcsine of x ${Math.asin(0)} == 0
Math.atan(x) The arctangent of x ${Math.atan(1)} == 0.7853981633974483
Math.ceil(x) The smallest integer greater than or equal to x. ${Math.ceil(2.3)} == 3
Math.clamp(x,y,z) Return x if y<x, z if y>z and otherwise y. ${Math.clamp(1, 22.3,10)} == 10
Math.cos(x) The cosine of x ${Math.cos(0)} == 1
Math.floor(x) The largest integer less than or equal to x. ${Math.floor(2.3)} = 2
Math.max(x1,x2,…) The largest argument ${Math.max(2,3)} == 3
Math.min(x1,x2,…) The smallest argument ${Math.min(2,3)} == 2
Math.PI The value of π (PI) 3.141592653589793
Math.random() A random number between 0 and 1 ${Math.random()} == 0.7113654073137101 (the actual random number returned will be different)
Math.round(x) Return the nearest integer to x ${Math.round(2.3)} == 2
Math.sign(x) The sign of x: -1, 0, or +1 ${Math.sign(-43.1) == -1
Math.sin(x) The sine of x ${Math.sin(Math.PI/6)} == 0.5
Math.sqrt(x) The square root of x ${Math.sqrt(9)} == 3
Math.tan(x) The tangent of x ${Math.tan(Math.PI/4)} == 0.5

String functions

The top-level String property is a collection of functions for manipulating strings.

Function Description Example
String.slice(x,y[,z]) Return the subset of x starting at index y and extending to but not including index z. If z is omitted, the remainder of the string is returned. If y is a negative number, select from the end of the string. ${String.slice('berry', 2, 4)} == 'rr' ${String.slice('berry', -2)} == 'ry'
String.toLowerCase(x) Lowercase the string ${String.toLowerCase('bEn')} == 'ben'
String.toUpperCase(x) Uppercase the string ${String.toUpperCase('bEn')} == 'BEN'

Time functions

The top-level Time property is a collection of functions that convert from time values in milliseconds into years, months, days, hours, minutes, and seconds. The examples in the table assume a bound time value T=1567786974710, which in human-readable terms is Friday September 6th, 2019 at 16:22:54 and 710 milliseconds.

Function Description Example
Time.year(x) The year ${Time.year(T)} == 2019
Time.month(x) The month (from 0-11) ${Time.month(T)} == 8 (September)
Time.date(x) The date of the month (1-31) ${Time.date(T)} == 6
Time.weekDay(x) The day of the week (0-6) ${Time.weekDay(T)} == 5 (Friday)
Time.hours(x) The hour of the day (0-23) ${Time.hours(T)} == 16
Time.minutes(x) The minutes of the hour (0-59) ${Time.minutes(T)} == 22
Time.seconds(x) The seconds in the minute (0-59) ${Time.seconds(T)} == 54
Time.milliseconds(x) The milliseconds (0-999) ${Time.milliseconds(T)} == 710
Time.format(f, x) A formatted text string ${Time.format('H:mm', T)} == "16:22"

To construct a simple digital clock, the developer could use the Time functions to build a 24-hour clock out of a Text component and the localTime property:

{
  "type": "Text",
  "bind": {
    "name": "T",
    "value": "${localTime}"
  },
  "text": "${Time.hours(T)}:${Time.minutes(T)}"
}

This example binds the value of localTime into a local variable T to make the text expression a little simpler. This clock renders a time like 4:04 pm as "16:4", which might not be what you want. You can use conditional statements to check the time and include leading/trailing zeros if necessary.

You can also use conditional statement to display a 12-hour clock as shown in this example. This binds both the hours and minutes to local variables, then uses conditional statements to output the time in a 12-hour format with "am" or "pm".

{
  "type": "Text",
  "bind": [
    {
      "name": "h",
      "value": "${Time.hours(localTime)}"
    },
    {
      "name": "m",
      "value": "${Time.minutes(localTime)}"
    }
  ],
  "text": "${h >= 12 ? h - 12 : h}:${m < 10 ? '0' : ''}${m} ${h >= 12 ? 'pm' : 'am'}"
}

You can also use the Time.format function to simplify the code. This example also uses a conditional statement based on hours to add the "am" or "pm" at the end.

{
  "type": "Text",
  "bind": [
    {
      "name": "h",
      "value": "${Time.hours(localTime)}"
    }
  ],
  "text": "${Time.format('h:mm', localTime) + (h >= 12 ? ' pm' : ' am')}"
}

Time.format

The format function takes a string argument containing the formatting codes and a time value. The following formatting codes are supported:

Value Range Description
YY 00..99 Year, two digits
YYYY 1970..XXXX Year, four digits
M 1..12 Month (1=January)
MM 01..12 Month, two digits (1=January)
D 1..31 Day of the month
DD 01..31 Day of the month, two digits
DDD 0..N Days, any number of digits
H 0..23 24h hour
HH 00..23 24h hour, two digits
HHH 0..N Hours, any number of digits
h 1..12 12h hour
hh 01..12 12h hour, two digits
m 0..59 Minutes
mm 00..59 Minutes, two digits
mmm 0..N Minutes, any number of digits
s 0..59 Seconds
ss 00..59 Seconds, two digits
sss 0..N Seconds, any number of digits
S 0..9 Deciseconds
SS 00..99 Centiseconds
SSS 000..999 Milliseconds

Note that all of the formatting codes return digits and digits only.

Here are some examples of the use of the formatting functions, using the time value September 6th, 2019 at 16:22:54 and 710 milliseconds.

Format Value
"DD-MM-YYYY" 06-09-2019
"M/D/YY" 9/6/19
"DDD days" 18145 days (since the epoch)
"H:mm" 16:22
"hh:mm" 04:22
"H:mm:ss" 16:22:54

Time formatting also works for relative times from timers. This example uses the value 7523194, which corresponds to 2 hours, 5 minutes, 23.194 seconds.

Format Value
"mmm:ss.S" 125:23.1
"HHH:mm:ss.SS" 2:05:23.19
"sss.SSS" 7523.194

For example, to show elapsedTime since the document has loaded:

{
  "type": "Text",
  "text": "${Time.format('mmm:ss.S', elapsedTime)}"
}

Data-binding string conversion

Because APL is serialized in JSON, all data-bound expressions are defined inside of a JSON string:

{
  "MY_EXPRESSION": "${....}"
}

If there are no spaces between the quotation marks and the data-binding expression, then the result of the expression is the result of the data-binding evaluation. For example:

"${true}"               -> Boolean true
"${2+4}"                -> Number 6
"${0 <= 1 && 'three'}"  -> String 'three'

When extra spaces are in the string outside of the data-binding expression or when two data-binding expressions are juxtaposed, the result is a string concatenation:

" ${true}"     -> String ' true'
"${2+4} "      -> String '6 '
"${2+1}${1+2}" -> String '33'