APL Commands
In Alexa Presentation Language (APL), commands are messages that change the visual or audio presentation of the content on the screen. You send commands to the device in response to a spoken utterance with the Alexa.Presentation.APL.ExecuteCommands
directive. You use event handlers in your APL document to trigger commands directly, such as the response to a button press.
For specific commands, see APL Standard Commands.
Commands and screen actions
Commands support the following types of actions on the scene:
- Navigate within a scene
- Scrolling a
ScrollView
orSequence
- Scrolling to bring a component into view
- Change the page displayed in a
Pager
- Scrolling a
- Change a component within an existing scene
- Update an input control to reflect a new state
- Change the visibility on an existing component
- Play or pause a video clip within the existing scene
- Speech
- Read the audio content of a single component
- Read the audio content from more than one components
- Send commands to an APL extension
Command summary
The following sections describe the algorithm for running a command started by either an event handler or an externally-generated command.
Assume an array of commands to run in a series. The commands run in either normal mode or fast mode.
Normal mode evaluation
A command that runs in normal mode must have a named sequencer
.
By default, a command runs on the MAIN
sequencer except in the following scenarios:
- The
sequencer
property of the command contains a value. - The command is a subcommand of a command that has a different
sequencer
.
For each command in the array in turn, Alexa performs the following steps:
-
Evaluate the
when
property of the command. Ifwhen
evaluates tofalse
, skip the command and continue to the next command in the array. -
Check for a value in the
delay
property of the command. Ifdelay
is greater than zero, pause for the specified duration in milliseconds. -
Check for a value in the
sequencer
property of the command. Whensequencer
has a value that's different from the current sequencer, hand this command off to the specified sequencer and continue to the next command in the array. -
Validate the
type
property of the command. Whentype
contains an unrecognized value, skip the command and continue to the next command in the array.Valid
type
values include the following:- A built-in command, such as
SendEvent
. Run the command if all required command properties have values. Some commands return immediately. Other commands pause the sequencer until the command finishes running. - A user-defined command. User-defined commands inflate into an array of commands. Run those commands following the normal mode evaluation rules and continue when all the commands complete.
- An extension command. Run the command. Some extension commands return immediately. Other commands pause the sequencer until the command finishes running.
- A built-in command, such as
Fast mode evaluation
A command running in fast mode
doesn't have a named sequencer
. For each command in the array in turn, do the following:
-
Evaluate the
when
property of the command. Ifwhen
evaluates tofalse
, skip the command and continue to the next command in the array. -
Check for a value in the
sequencer
property of the command. Whensequencer
has a value, hand this command off to the appropriate sequencer and continue to the next command in the array. The handed-off command now runs in normal mode instead of fast mode. -
Validate the
type
property of the command. Whentype
contains an unrecognized value, skip the command and continue to the next command in the array.Valid
type
values include the following:- A built-in command that supports fast mode, such as
SetValue
. Run the command if all required command properties contain values. For a list of commands that support fast mode, see the tables in Fast mode commands. - A built-in command that doesn't support fast mode, such as
SendEvent
. Skip the command. - A user-defined command. User-defined commands inflate into an array of commands. Run those commands following the fast mode evaluation rules and continue when all the commands complete.
- An extension command that supports fast mode. Run the command.
- An extension command that doesn't support fast mode. Skip the command.
- A built-in command that supports fast mode, such as
Command evaluation
The individual properties of a command support data-binding. The data-binding context for a command includes the context in which the command is defined, extended by an event
property containing information about the circumstances that triggered the command and the component that was the target of the command.
Event context
Commands evaluate in their source data-binding context:
- document data-binding context – Contains the
viewport
,environment
, and document payload. Has access to named resources and the document data-binding. - component data-binding context – The data-binding context specific to a component. Has access to all data-bindings in the component and all component ancestors, as well as the document data-binding context.
A command sent to the device by the ExecuteCommands
directive or run by a document event handler evaluates in the document data-binding context. A command issued in response to an APL event (such as a screen tap) evaluates in the component data-binding context where the command is defined.
For example, consider the following TouchWrapper
example.
{
"type": "TouchWrapper",
"bind": [
"name": "myRandomData",
"value": 24.3
],
"onPress": {
"type": "SendEvent",
"arguments": [ "The value is ${myRandomData}" ]
}
}
When the user taps the TouchWrapper
, the device sends the skill a UserEvent
. The arguments
array in this request contains the string "The value is 24.3".
Event property
Evaluating a command extends the source data-binding context with event data. Access this event data within the event
property.
The event property has two sub-properties: event.source
and event.target
.
- The
event.source
property contains system-provided information about the component that caused the event - The
event.target
property contains system-provided information about the component that receives the event (if applicable).
Not all commands have an event.target
property. For example, the SendEvent
command doesn't have an event.target
property. All commands do have a event.source
property. The details with event.source
can vary and might not represent a component. For example, the source might come from an extension rather than a component.
The event
property added to the data-binding context has the following form.
"event": {
"source": { // Always exists
"type": "COMPONENT_TYPE", // The type of the component or "Document"
"handler": "EVENT_HANDLER", // The name of the event handler (see command notes)
"id": "SOURCE_ID", // The ID of the source component
"uid": "SOURCE_UID", // The UID of the source component
... // Additional source component properties
},
"target": { // Only exists when the command has a component target
"type": "COMPONENT_TYPE", // The type of the target component
"id": "TARGET_ID", // The ID of the target component
"uid": "TARGET_UID", // The UID of the target component
... // Additional target component properties
},
... // Command-specific properties
}
The event.source
property has a handler
property, and the event.target
property doesn't.
The following table lists the standard event.source
and event.target
properties that different components report. For complete documentation on the properties for a given component, see the documentation for that component.
Property | Type | Description | Reported By |
---|---|---|---|
|
Map |
Data-binding context | |
|
Boolean |
Checked state | |
|
Color |
Current color | |
|
Integer |
Current playback position | |
|
Boolean |
Disabled state | |
|
Integer |
Duration of the current video (ms) | |
|
Boolean |
True if the video has ended | |
|
Boolean |
Focused state | |
|
Number |
Height in dp | |
|
String |
Component id | |
|
String |
Current | |
|
Boolean |
Current muted state of video | |
|
Number |
Local opacity (not cumulative) | |
|
Integer |
Current displayed page | |
|
Boolean |
True if the video has paused | |
|
Number |
Percentage of scrolled distance | |
|
Boolean |
Pressed state | |
|
String |
Displayed text | |
|
Integer |
Number of tracks | |
|
Integer |
Index of the current track | |
|
String |
Component type (for example, "Frame") | |
|
String |
Runtime-generated unique id | |
|
String |
Source URL | |
|
Number |
Width in dp |
The bind
property provides access to the data-binding context of the component. The following example illustrates how you access a bound value. The component with the ID MyText
is the target of the SetValue
command. Therefore, the event.target.bind
property contains the data bound to the MyText
component and the command can use that value.
[
{
"type": "TouchWrapper",
"onPress": {
"type": "SetValue",
"componentId": "MyText",
"property": "text",
"value": "The word of the day is ${event.target.bind.WordOfTheDay}"
},
"items": [
{
"type": "Text",
"text": "Click me!"
}
]
},
{
"type": "Text",
"bind": [
{
"name": "WordOfTheDay",
"value": "Bear"
}
],
"id": "MyText"
}
]
event.source
The event.source
property of the event
contains meta-information about the circumstances that triggered the event. The APL runtime generates event.source
. You can use this information in your document. The event.source
property contains all standard properties along with the following properties:
Property | Type | Description |
---|---|---|
handler | String | The name of the event handler that initiated this message. For example, Press , Checked . |
value | Any | The value of the component that initiated this message. |
The event.source.value
property depends on the component. For example, for a TouchWrapper
, event.source.value
contains the checked state of the component. For a ScrollView
, the property contains the scroll position. Refer to the individual component definitions for the specific event.source.value
property provided.
event.source.value
property is deprecated and should not be used. Use the direct properties documented in for each component instead. For example, for the checked state of a TouchWrapper
, use event.source.checked
instead of event.source.value
.For backwards-compatibility with older versions of APL, event.source
property also contains a source
sub-property equal to the type
property. Therefore, ${event.source.source == event.source.type}
. The event.source.source
property is deprecated. Avoid using this property.
event.target
The event.target
property provides state information about the component receiving the event. The values in the target
depend on the specific component. Refer to the individual component definitions for the specific target
properties you can expect.
For backwards-compatibility with older versions of APL the target
property of the event also reports an source
sub-property equal to the url
property for the Image
, VectorGraphic
, and Video
components. Therefore, ${event.target.source == event.target.url}
. The event.target.source
property is deprecated. Avoid using this property.
Evaluation notes
- Most commands take a
componentId
as a target. You can omitcomponentId
for a command that targets itself. - When you use the
ExecuteCommands
directive to send a command to the device, you must always specifycomponentId
. - When the command includes the
componentId
property, the command searches through all the components starting at the root of the tree hierarchy in a depth-first search manner and targets the first component with an identifier that matches the value. To guarantee that the command targets the correct component, do one of the following:- Assign a unique value to the target component with the
id
property and setcomponentId
for the command to that value. - Use the system-generated
uid
property value provided in the visual context.
- Assign a unique value to the target component with the
- Command data-binding expressions evaluate when the command runs, not when the command is defined. For example, an
ExecuteCommands
directive can contain data-bound expressions that refer to the global data-binding context. These expressions are evaluated when the command runs on the device, not when the command is constructed in the cloud. - Event handlers and the
ExecuteCommands
directive take an array of commands to run. This array works like aSequential
command with arepeatCount
of 0.
Command sequencing
The APL runtime environment runs the commands. User and environmental actions trigger the commands to run. The Parallel
and Sequential
commands also trigger a set of commands to run. The command sequencing rules determine the behavior when multiple commands run at the same time. These rules prevent commands from conflicting with each other. You can partially control the sequencing behavior based on how you define your commands.
Note the following terms:
- Resource – Something used by a command that other commands can't share. For example, a command that speaks text uses the "speech" resource. A command that acts on a specific component over time (such as
AnimateItem
) considers that component a resource. - Normal mode – The standard method of running commands. Most commands run in normal mode. Normal mode commands might take time to run.
- Fast mode – An alternative method of running commands, used when it would be inappropriate to run commands that take time to run. When a command runs in fast mode, the command ignores all delays and skips commands that take time to run. Event handlers, such as
onScroll
, use fast mode. These event handlers might fire multiple times per second. - Sequencer – A named entity that can run a single command at a time. Each normal mode command has a named sequencer. You set the
sequencer
property to specify the sequencer. - Subcommand – A command contained within another command. Multiple commands can contain subcommands, including Sequential, Parallel, OpenURL, and Select. A hierarchy of subcommands is also called a command tree.
Command sequencing rules
The APL runtime uses the following rules to sequence commands.
- Every normal mode command runs on a single
sequencer
. If that sequencer is already running a command, the runtime stops the running command and starts the new command. - Commands running in fast mode don't use a sequencer.
- Any command that explicitly specifies a sequencer runs in normal mode.
- A subcommand of a command runs on the same sequencer as its parent unless the subcommand explicitly specifies a sequencer. When the subcommand does specify a sequencer, the runtime hands off the command to the sequencer and marks the command as complete in the parent.
- The default sequencer is
MAIN
. When a normal mode command that isn't a subcommand doesn't explicitly name a sequencer, the command runs on theMAIN
sequencer. Normal mode subcommands follow rule 4. - Any physical user interaction with the device, such as touching the screen or typing on a keyboard, stops any command running on the
MAIN
sequencer
. This rule doesn't apply to voice interactions. When the user speaks the wake word to "barge in" during a command sequence, theMAIN
sequencer continues and doesn't stop. - When a command starts running, if the command requires a resource that's in use by a command that's already running, the runtime stops the running command.
- When a configuration change handler runs, any commands running in the previous configuration stop.
For example, the user touches a button the screen. This interaction starts a series of commands on the MAIN
sequencer to do the following:
- Animate a change on the screen.
- Scroll down a list to a specific location.
- Speak an item.
The user can interrupt this series of commands at any time by touching the screen again. When the user touches the screen, the MAIN
sequencer stops the currently running command.
To override the default command sequencing behavior, specify a sequencer
. Use this technique to run commands in parallel. Some scenarios in which you would change the sequencing behavior:
- You want an event handler that runs in fast mode by default to run a normal mode command. For example, you want the
onScroll
event handler of aScrollView
to trigger aSendEvent
command. Normally, theonScroll
handler ignores normal mode commands. When you provide a named sequencer onSendEvent
, theonScroll
handler runs the command in normal mode, so theSendEvent
runs. - You want to override normal user interaction behavior and continue to run a command even when the user touches the screen. For example, you use an
AnimateItem
command to animate an on-screen component in a children's game where touching the screen shouldn't stop the animation. - When multiple components animations are triggered to show new components appearing or old component disappearing.
Resources
Each command might use one or more system resources when running. As defined in the sequencer rules, only a single command using a given resource can run at a time. When a new command that requires that resource starts running, the old command stops.
The following is a list of resources and which commands use those resources:
- Foreground audio playback
SpeakItem
SpeakList
PlayMedia
when using the foreground audio track.ControlMedia
running theplay
command on a foreground audio track.
- Background audio playback
PlayMedia
when using the background audio track.ControlMedia
running theplay
command on a background audio track.
- Scroll position (per the scrolling component; either a
ScrollView
orSequence
) - Pager position (per the
Pager
component) - Component animation (per the component property being animated)
The SpeakItem
and SpeakList
can use multiple resources.
Normal mode
The APL runtime runs a command in normal mode in the following scenarios:
- When the document initially loads (Document
onMount
and ComponentonMount
). - The user touches or selects a
TouchWrapper
(onPress
). - The user swipes on a
Pager
and changes the displayed page (onPageChanged
) - A Video component ends playback of the video or changes the track (
onEnd
,onPause
,onPlay
,onTrackUpdate
). - A new set of commands arrives from an external source such as the
ExecuteCommands
directive or an extension event handler. - The user presses or releases a key on the keyboard (
handleKeyDown
andhandleKeyUp
).
By default all normal mode commands use the MAIN
sequencer.
In the following example the onPress
event handler has an array of commands. This array is treated as a sequential array. Therefore, SpeakItem
runs first. After the speech finishes, the Scroll
command runs. When Scroll
finishes, the SendEvent
command runs and sends a UserEvent
to your skill. All of these commands run on the MAIN
sequencer.
{
"type": "TouchWrapper",
"items": {
"type": "Text",
"id": "myText",
"speech": "VALUE TO SPEAK"
},
"onPress": [
{
"type": "SpeakItem",
"componentId": "myText"
},
{
"type": "Scroll",
"componentId": "myScrollRegion",
"distance": 2
},
{
"type": "SendEvent",
"arguments": [
"The button was pushed and spoken have I"
]
}
]
}
In the previous example, the component running the Scroll
command generates scrolling events which might invoke commands defined in the component's onScroll
event handler. These commands run in fast mode and therefore don't affect the currently running sequence of commands.
If the user taps the TouchWrapper
multiple times, the command sequence ends and restarts with each tap. Although you could set the onPress
handler to also disable the TouchWrapper
, this doesn't solve the problem. Any user tap on the screen stops the commands on the MAIN
sequencer.
Instead, to guarantee that the sequence of commands completes, assign a different sequencer to the commands. The following example both disables the TouchWrapper
so that the user can't restart the sequence until it finishes and uses a different sequencer so that touching the screen doesn't cancel the commands.
{
"type": "TouchWrapper",
"items": {
"type": "Text",
"id": "myText",
"speech": "VALUE TO SPEAK"
},
"onPress": [
{
"type": "Sequential",
"sequencer": "MySequencer",
"commands": [
{
"type": "SetValue",
"property": "disabled",
"value": true
},
{
"type": "SpeakItem",
"componentId": "myText"
},
{
"type": "Scroll",
"componentId": "myScrollRegion",
"distance": 2
},
{
"type": "SendEvent",
"arguments": [
"The button was pushed and spoken have I"
]
}
],
"finally": {
"type": "SetValue",
"property": "disabled",
"value": false
}
}
]
}
Because the custom "MySequencer" isn't the MAIN
sequencer, another tap on the screen doesn't cancel the commands. Note that this example runs the final SetValue
command in the finally
block. If the Sequential
stops for some reason, the commands specified in finally
still run to re-enable the TouchWrapper
.
Note that the previous example works for a series of commands because all the commands are subcommands of the Sequential
. According to the sequencer rules, subcommands run on the same sequencer as the parent unless they specify their own.
In contrast, the following array of commands assigned to onPress
doesn't work. When the user taps this TouchWrapper
, nothing happens:
{
"type": "TouchWrapper",
"items": {
"type": "Text",
"id": "myText",
"speech": "VALUE TO SPEAK"
},
"onPress": [
{
"type": "SetValue",
"property": "disabled",
"value": true,
"sequencer": "BadIdea"
},
{
"type": "SpeakItem",
"componentId": "myText",
"sequencer": "BadIdea"
},
{
"type": "Scroll",
"componentId": "myScrollRegion",
"distance": 2,
"sequencer": "BadIdea"
},
{
"type": "SendEvent",
"arguments": [
"The button was pushed and spoken have I"
],
"sequencer": "BadIdea"
},
{
"type": "SetValue",
"property": "disabled",
"value": false,
"sequencer": "BadIdea"
}
]
}
This example fails because the onPress
command runs on the MAIN sequencer by default. Specifying a different sequencer causes the handler to hand off the command to the other sequencer and then run the next command in the array. When a sequencer receives a new command, it stops any existing command and replaces the existing command with the new one.
The sequence of events for the failed example would be the following.
- The
onPress
handler hands offSetValue
to theBadIdea
sequencer. - The
onPress
handler moves on toSpeakItem
and immediately hands offSpeakItem
toBadIdea
. - The
BadIdea
sequencer cancelsSetValue
to startSpeakItem
. - The
onPress
handler hands offSendEvent
toBadIdea
. - The
BadIdea
sequencer cancelsSpeakItem
to startSendEvent
. - The
onPress
handler hands offSetValue
toBadIdea
. - The
BadIdea
sequencer cancelsSendEvent
to startSetValue
. - The
SetValue
command does successfully run, but has no effect because theTouchWrapper
is already enabled.
Sequential
command and assign the new sequencer to the Sequential
command.You can also use a sequencer to toggle a command between running and not running. In the following example, the first TouchWrapper
runs an AnimateItem
command to animate moving the ball. The second TouchWrapper
forces the animation to stop by sending a new command to the same sequencer to cancel the AnimateItem
command.
The onPress
handler for the second TouchWrapper
runs an Idle
command on the BallSequencer
to stop AnimateItem
. The Idle
command doesn't do anything, but it does stop any command currently running on the sequencer. Any other command sent to BallSequencer
would have the same result.
Fast mode
Some event handlers can trigger many times per second. For example, the onScroll
handler on a ScrollView
runs every time the scroll position moves. At the same time, some commands might take a measurable amount of time to run. For example, scrolling a list on the screen or speaking text takes measurable time to run.
To avoid issues with long-running commands running from frequently-fired event handlers, those handlers run their commands in fast mode.
Any set of commands triggered from an event handler that runs at frame rate uses fast mode. All other command sequences run in normal mode. In fast mode, the handler ignores all delay
settings in the commands and skips commands that take measurable time to run. The event handlers have the following behavior:
Event Handler | Behavior |
---|---|
Actionable handleKeyDown |
Normal mode |
Actionable handleKeyUP |
Normal mode |
Actionable onBlur |
Fast mode |
Actionable onFocus |
Fast mode |
Component handleTick |
Fast mode |
Component onCursorEnter |
Fast mode |
Component onCursorExit |
Fast mode |
Component onMount |
Normal mode |
Component onSpeechMark |
Fast mode |
Document handleTick |
Fast mode |
Document onConfigChange |
Fast mode |
Document onDisplayStateChange |
Fast mode |
Document onMount |
Normal mode |
Pager onPageChanged |
Normal or fast mode |
ScrollView onScroll |
Fast mode |
Sequence onScroll |
Fast mode |
Touchable onDown |
Fast mode |
Touchable onMove |
Fast mode |
Touchable onPress |
Normal mode |
Touchable onUp |
Fast mode |
Video onEnd |
Normal or fast mode |
Video onPause |
Normal or fast mode |
Video onPlay |
Normal or fast mode |
Video onTimeUpdate |
Fast mode |
Video onTrackUpdate |
Normal or fast mode |
A one-time event that occurs due to user interaction, such as tapping a TouchWrapper
, always runs in normal mode. Events such as scrolling run in fast mode. Events that can run in either mode can be triggered by a normal action (such as a video track ending) or by a command that was ultimately triggered by fast mode (such as a video pause from a scroll event). External commands and extension commands run in normal mode.
Each command documents its behavior in fast mode. The following table summarizes fast-mode behavior:
Command | Fast mode behavior |
---|---|
AnimateItem |
Jumps to end state. |
AutoPage |
Ignored |
ClearFocus |
Runs |
ControlMedia |
Ignored for command="play" , run otherwise. |
Finish |
Runs |
Idle |
Ignored |
InsertItem |
Runs |
Log |
Runs |
OpenUrl |
Ignored |
Parallel |
Runs |
PlayMedia |
Ignored |
Reinflate |
Runs |
RemoveItem |
Runs |
Scroll |
Ignored |
ScrollToComponent |
Ignored |
ScrollToIndex |
Ignored |
Select |
Runs |
SendEvent |
Ignored |
Sequential |
Runs |
SetFocus |
Runs |
SetPage |
Ignored |
SetValue |
Runs |
SpeakItem |
Ignored |
SpeakList |
Ignored |
When a command that normally runs in fast mode has a value in the sequencer
property, the command runs in normal mode on the specified sequencer instead. Use this feature to run normal mode commands from fast mode event handlers.
The following example checks the position of a scroll view whenever it moves and sends an event when the scroll position is at the top. It applies a rate limit by recording the time when it sends then event, so at least a second passes between events. Without the sequencer
property, the SendEvent
never runs.
{
"type": "ScrollView",
"bind": [
{
"name": "LastEventSentTime",
"value": 0
}
],
"onScroll": [
{
"type": "Sequential",
"when": "${event.source.value == 0 && utcTime > LastEventSentTime + 1000}",
"commands": [
{
"type": "SetValue",
"property": "LastEventSentTime",
"value": "${utcTime}"
},
{
"type": "SendEvent",
"arguments": [
"top of scroll view reached"
],
"sequencer": "ScrollSender"
}
]
}
]
}
Command Trees
A command tree is the complete set of commands that run. Command trees occur because commands can nest within each other or because a command might invoke a new event handler. The following primitive commands have nested commands:
OpenURL
Parallel
Select
Sequential
User-defined commands also support nested commands.
Primitive commands can also invoke event handlers. For example, the Scroll
, ScrollToComponent
, and SpeakList
commands can all invoke the onScroll
event handler.
Command trees can run to completion or stop before completion. A source starts running a command tree. The command tree then runs to completion unless it stops early. When a command tree stops, APL stops running all the commands in the tree immediately.
The following example illustrates a command tree for an ExecuteCommands
directive that includes the Scroll
, SpeakItem
, and PlayVideo
commands.
ExecuteCommand
+ Scroll (distance=-10000) // Scroll to top
+ onScroll // Invokes multiple times as the view scrolls to the top
+ SetValue (name="opacity", value=event.source.value * 10) // Change opacity
+ SpeakItem (id) // Scroll item into view and run karaoke
+ onScroll // Invokes multiple times as the view scrolls
+ SetValue (name="opacity"....)
+ PlayVideo (synchronously)
+ onStart // Invokes once
+ onTrackUpdate // Invokes each time a new track is displayed
+ SetValue (name="progress"...) // Update a progress bar display
+ onStop // Invokes once
This series of commands scrolls to the top of the screen, speaks one of the items, and then plays a video. If the user taps on the screen during playback, any running speech, scrolling, or video playback stops.
An individual command inside of a series of commands might specify a different sequencer
. When command is reached, it's handed off to the appropriate sequencer and then the next command in the sequence runs immediately. Commands that don't have an explicit sequencer property run on the current sequencer. Commands only run on the next sequencer after the command delay has been processed. For example, consider the following series of commands running on the main sequencer:
+ Sequential
+ AnimateItem A (delay=100, duration=1000)
+ AnimateItem B (delay=200, sequencer="other", duration=2000)
+ Parallel (delay=200)
+ AnimateItem C (duration=1000)
+ AnimateItem D (sequencer="other", duration=2000)
+ AnimateItem E (delay=100, duration=1000)
The following table summarizes the time line of actions caused by this command tree.
Time | Action |
---|---|
0 |
Sequential command starts |
100 |
AnimateItem A starts |
1100 |
AnimateItem A finishes |
1300 |
AnimateItem B starts on sequencer |
1500 |
Parallel command starts |
AnimateItem C starts | |
AnimateItem D starts on sequencer | |
2500 |
AnimateItem C finishes |
Parallel command finishes | |
2600 |
AnimateItem E starts |
3500 |
AnimateItem D finishes on sequencer |
3600 |
AnimateItem E finishes |
Sequential command finishes |
When a command tree stops, APL makes a number of assumptions to get the device to a consistent state:
- Scrolling stops
- Page turns are canceled and return to either the original page or the next page (whichever is closer)
- Speaking immediately stops
- Scene changes and structural changes to the layout "jump" to their final position.
Selector
Several commands have a componentId
property which specifies the target component for the command. The target of a command is the component that the command acts upon. The following commands each have the componentId
property:
- AnimateItem
- AutoPage
- ControlMedia
- InsertItem
- PlayMedia
- RemoveItem
- Scroll
- ScrollToComponent
- ScrollToIndex
- SendEvent
- SetFocus
- SetPage
- SetValue
- SpeakItem
- SpeakList
- Any APL Extensions that use
componentId
.
The componentId
property is a string selector
parsed according to a selector grammar. The selector grammar is the following.
componentId ::= element? modifier*
element ::= uid | id | ":source" | ":root"
modifier ::= modifierType "(" arg? ")"
modifierType ::= ":parent" | ":child" | ":find" | ":next" | ":previous"
arg ::= number | "id=" id | "type=" type
uid ::= ":" [0-9]*
id ::= [_a-zA-Z][_a-zA-Z0-9]*
number ::= "0" | "-"? [1-9][0-9]*
type ::= STRING
A selector
string ignores whitespace between the element
and each modifier
. Don't include whitespace within a modifier
.
The following shows several examples of valid componentId
expressions.
FOO # The component with id=FOO
:1003 # The component with unique ID ":1003"
:source # The component that issued the command
:root # The top of the component hierarchy
:source:child(2) # The third child of the source component
:child(2) # The third child of the source component (:source is implicit)
FOO:child(-1) # The last child of the FOO component
FOO:parent(1) # The parent of FOO
FOO:child(id=BAR) # The first direct child of FOO where id=BAR
FOO:find(id=BAR) # The first descendant of FOO where id=BAR
FOO:child(type=Text) # The first direct child of FOO where type=Text
FOO:parent():child(id=BAR) # The first sibling of FOO where id=BAR
FOO:next(id=BAR) # The first sibling after FOO where id=BAR
FOO:parent(2) # The grandparent of FOO
FOO:next(1) # The sibling right after FOO
FOO:previous(2) # Two siblings before FOO
The componentId
value defines the component that receives the command. If the componentId
doesn't match a component, the command doesn't run. The componentId
consists of an optional element
followed by zero or more modifier
expressions.
APL evaluates the selector expression sequentially, starting with the element
and proceeding through each modifier
. If at any time no valid component matches the expression, the expression returns a null componentId
and the command using the selector doesn't run. For example, the selector :root:parent():find(id=FOO)
never returns a component even if there is a component in the hierarchy with id=FOO
because the parent of the root component isn't defined.
Starting element
The element
is a uid
, id
, the string :source
, or the string :root
. For a component, the element
defaults to :source
. Therefore, you can omitelement
for event handlers defined in the scope of a component. Don't omit the element
for event handlers at the document level.
Starting element: uid
The uid
element returns the component with the matching internal uid
. The APL runtime assigns each component a unique string ID when creating the component. This internal ID is unique within the scope of the document and doesn't conflict with any assigned id
values. Event handlers can access the uid
and store it in a bound variable for later use. Don't make assumptions about what uid
value is assigned to the component, as different runtimes might assign different values.
Because APL assigns the uid
, it's rarely used to target commands directly. You might find it useful to store the uid
value for a component in one event handler, and then use the stored value to modify the component later. The following example defines a Sequence
that displays a list of TouchWrapper
components. Tapping one of these components stores the uid
for that component in a binding variable called LAST_PRESSED
. When the user then scrolls the list, an event handler on the Sequence
uses the uid
stored in LAST_PRESSED
to target that component and update the text shown in the list.
Starting element: id
The id
element returns the first component in the hierarchy with a matching Component id property. Because you set the id
values yourself, APL can't guarantee that they're unique. The search order is depth-first, but depends on how many non-visible components have been inflated. Use the id
element to target commands when you know that the id
values are unique.
The following example displays a list of TouchWrapper
components in a Container
. Each component has an id
calculated from the colors provided in the data
array. For example, the id
for the first component is Text_Red
. When the user taps the component, the SetValue
command changes the text and color of the TouchWrapper
to reveal the "hidden" color.
This example works because each color in the list is unique, which means that each component id
is unique. If you update the data
array to repeat one of the colors, the SetValue
command updates the first instance with that id
and not the later instance.
Starting element: :source
The :source
element selects the component that issued the command. The :source
element is optional. An event handler defined in the context of a component defaults to the target of the component being the component itself. Note that extension-issued commands and document-issued commands don't have a source component. Therefore, for these commands you must specify either a target component or :root
. Use :source
when you want to be explicit about the intended target.
Starting element: :root
The :root
element selects the top component in the inflated component hierarchy. Use :root
to modify bound values attached to the document.
The following example shows an APL document with bind
defined at the mainTemplate
level. The onMount
event handler updates this top-level bind
variable when the document inflates. To re-inflate the document, refresh the page.
Note that this command fails without a valid componentId
because all document-level command handlers must specify a target component.
Modifiers
The modifier
expressions select a target relative to the component identified by the element
. A modifier
can select the parent, a sibling, a child, or search the component child hierarchy. The modifier specified defines how to search for the matching component.
A modifier
takes an optional arg
which specifies the criteria to match. The argument can be an integer or a key-value pair where the valid keys are id
and type
.
Modifier :parent
The :parent()
modifier selects the parent of the component. The :parent()
modifier can take a numerical argument which selects further ancestors. The argument must be a positive integer. If you don't provide an argument, the result is the same as :parent(1)
. For example:
FOO:parent(1) == FOO:parent() # The parent of FOO
FOO:parent(2) == FOO:parent():parent() # The grandparent of FOO
The id
and type
patterns match the first ancestor that matches.
For example, assume the following document hierarchy.
TouchWrapper (id=MyButton)
Frame (id=OuterFrame)
Frame (id=InnerFrame)
Text (id=FOO)
Based on this hierarchy, note the results of the following example expressions.
FOO:parent(1) # InnerFrame
FOO:parent(2) # OuterFrame
FOO:parent(id=MyButton) # TouchWrapper
FOO:parent(type=Frame) # InnerFrame
FOO:parent(id=OuterFrame) # OuterFrame
Modifier :child
The :child()
modifier selects the first direct child of the component that matches the expression.
A numeric argument returns the N-th child of the component, where N=0 is the first child, N=1 is the second child, and so on. A negative number selects from the end of the child list, where N=-1 is the last child, N=-2 is the second-to-last child, and so on. An empty argument is the same as :child(0)
. For example, note the following example expressions.
FOO:child() # The first child of FOO
FOO:child(0) # The first child of FOO
FOO:child(2) # The third child of FOO
FOO:child(-1) # The last child of FOO
A named argument (either id
or type
) selects the first child which matches. For example, assume the following document hierarchy in which id
values aren't unique.
Sequence (id=FOO)
Container
Text (id=TEXT)
Image (id=IMAGE)
Container
Text (id=TEXT)
Image (id=IMAGE)
Based on this hierarchy, note the results of the following example expressions.
FOO:child(0):child(0) # The Text component in the first Container
FOO:child(id=TEXT) # Null (only direct children are considered, not indirect)
FOO:child(0):child(id=TEXT) # The Text component in the first Container
FOO:child(1):child(type=Image) # The Image component in the second Container
Modifier :find
The :find()
modifier selects the first descendant of the component that matches the expression.
A numeric argument returns the N-th component in the depth-first search. Must be a positive number. A negative or zero argument returns the first child. A named argument (either id
or type
) selects the first child which matches. For example, assume the following document hierarchy.
Sequence (id=FOO)
Container
Text (id=TEXT)
Image (id=IMAGE)
Container
Text (id=TEXT)
Image (id=IMAGE)
Based on this hierarchy, note the results of the following example expressions.
FOO:find(3) # The Image component in the first Container
FOO:find(5) # The Text component in the second Container
FOO:find(id=TEXT) # The Text component in the first Container
FOO:find(type=Image) # The Image component in the first Container
Modifier :next
The :next()
modifier selects the first sibling of the component that matches the expression, searching from the current component forwards. A numeric argument returns the N-th sibling of the component, where N=1 is the first sibling, N=2 is the second sibling, and so on. An empty argument is the same as :next(1)
. A named argument (either id
or type
) selects the first sibling which matches.
For example, assume the following list of sibling components.
TouchWrapper (id=MyButton)
Container (id=FOO)
Frame
Image (id=ImageA)
Video (id=VideoA)
Video (id=VideoB)
Based on this list, note the results of the following example expressions.
FOO:next() # Frame
FOO:next(2) # Image
FOO:next(9) # Null (ran off the end of the sibling list)
FOO:next(id=MyButton) # Null (wrong direction of search)
FOO:next(type=Video) # VideoA
FOO:next(id=VideoB) # VideoB
Modifier :previous
The :previous()
modifier selects the first sibling of the component that matches the expression, searching from the current component backwards. A numeric argument returns the N-th sibling of the component, where N=1 is the first sibling, N=2 is the second sibling, and so on. An empty argument is the same as :previous(1)
. A named argument (either id
or type
) selects the first sibling which matches.
For example, assume the following list of sibling components.
TouchWrapper (id=MyButton)
Frame
Image (id=ImageA)
Video (id=VideoA)
Container (id=FOO)
Video (id=VideoB)
Based on this list, note the results of the following example expressions.
FOO:previous() # VideoA
FOO:previous(2) # Image
FOO:previous(9) # Null (ran off the end of the sibling list)
FOO:previous(id=MyButton) # TouchWrapper
FOO:previous(type=Frame) # Frame
Matching by type
A component can have multiple types when it's part of a layout. In this situation, both the layout name and the underlying component type can match.
The following example shows a document with a layout named MyText
. The layout displays a Text
component. The mainTemplate
displays two instances of the MyText
layout.
The onMount
handler then runs two commands, one that targets :root:find(type=MyText)
and one that targets :root:find(type=Text)
. When these commands run, they update the same Text
component with the value "Doctor Jane Doe." The component color changes first to blue, and then to purple. To reload the document to see the change, either refresh the page or click the TouchWrapper
below the two Text
components to run the Reinflate
command.
Related topics
Last updated: Jun 10, 2024