Add a control as a child.
The control is appended to the end of the this.children
array.
Control
the container
Adds a new dynamic child control.
The child is added to the end of the this.children
array.
object defining the control to create. The
specification should be minimal information that enables the creation of
a complete control. An id
is mandatory, and any other information is
optional. The specification must be serializable or convertible to a
serialized form.
Determines if a child control can handle the request.
From the candidates that report canHandle = true
, a winner is selected
by this.decideHandlingChild(candidates)
.
The selected "winner" is recorded in this.selectedHandlingChild
.
Input
Determines if a child control can take the initiative.
From the candidates that report canTakeInitiative = true
, a winner is selected
by this.decideInitiativeChild(candidates)
.
The selected "winner" is recorded in this.selectedInitiativeChild
.
Input
Create a Control from a DynamicControlSpecification
Purpose:
Usage:
Decides a winner from the canHandle candidates.
The candidates should be all the child controls for which
canHandle(input) = true
Default logic:
this.children
array.Remarks:
The child controls that reported canHandle = true
Input
Decide a winner from the canTakeInitiative candidates.
The eligible candidates are child controls for which
canTakeInitiative(input) = true
.
Default logic:
this.children
array.The child controls that reported canTakeInitiative = true
Input
Evaluate an APL document/data source prop.
act
The input object
Constant or function producing a map of key:value pairs
Evaluate a boolean prop.
Constant or function producing boolean
The input object
Evaluate a prompt prop.
act
Constant or function producing String or List-of-Strings
Input object
Calls canHandle on each child control to determine the candidates for delegation.
Input
Calls canTakeInitiative on each child control to determine the candidates for delegation.
Input
Gets the Control's state as an object that is serializable.
Only durable state should be included and the object should be
serializable with a straightforward application of
JSON.stringify(object)
.
Default: {return this.state;}
Usage:
.state
variable
and only store simple data.Framework behavior:
JSON.stringify
.control.setSerializableState(serializedState)
.Serializable object defining the state of the Control
Delegates handling of the request to the child control selected during canHandleByChild.
Input
Response builder.
Determines if the Control's value is ready for use by other parts of the skill.
Note:
isReady === !canTakeInitiative
because isReady
implies
that no further discussion is required and thus there is no need to take the
initiative.Input object.
true
if the control has no further questions to ask the user such as
elicitation, clarification or confirmation.
Removes a dynamic control.
The control is removed from this.children
and the specification is
removed from this.state.dynamicChildSpecifications
Add response APL component by this control.
This is intended to be used to provide APL rendering component for a control to process inputs, provide feedback, elicitation etc through touch events on APL screens.
Input
Response builder
Add response content for a system act produced by this control.
This is intended to be used with the default ControlManager.render() which implements a simple concatenation strategy to form a complete response from multiple result items.
System act
Input
Response builder
Sets the state from a serialized state object.
Default: {this.state = serializedState;}
Usage:
Framework behavior:
Serializable object defining the state of the Control
Delegates initiative generation to the child control selected during canHandleByChild.
Input
Response builder.
Merges the user-provided props with the default props.
Any property defined by the user-provided data overrides the defaults.
A ContainerControl that adds/removes child controls during a session.
Purpose:
A
DynamicContainerControl
delays and perhaps avoids the addition of child controls that are only needed occasionally. By adding controls on-demand the control tree remains compact and easy to reason about. The alternative is to include all possible child controls and set them to be inactive until needed; this alternative may be simpler when there are few potential controls but is less convenient when there are many.A potential purpose is to support an unbounded number of child controls of a certain type (e.g. add-another-phone-number, add-another-address). By adding additional controls the user can refer to any of the children at any time (via each control's target prop) and each control can have its own durable state. However, if the user will only discuss one 'active' item at a time it may be simpler to use a regular container control that manages the list data directly and which reconfigures static child controls whenever the active item changes (see the FruitShop demo skill for an example of this approach).
Details:
The tricky part of managing a
DynamicContainerControl
is the re-initialization of the control at the start of each turn. To accomplish this,DynamicContainerControl
introduces new conventions:this.state.dynamicChildSpecifications
.this.addDynamicChildBySpecification(specification)
callsthis.createDynamicChild(specification)
to actually instantiate the control.this.addDynamicChildBySpecification()
also records that the child was created inthis.state.dynamicChildSpecifications
.Usage:
implement the abstract function
createDynamicChild(specification)
to create a control from a specification object.in
handle()
, usethis.addDynamicChildBySpecification(specification)
to add a dynamic child andthis.removeDynamicChild(control)
to remove a dynamic child.Example:
class ContactInfoControl extends DynamicContainerControl: { handle(input, resultBuilder){ // adding a new child control during handling. if(userWantsToAddFaxNumber){ this.addDynamicChildBySpecification({id: 'faxNumber'}) } } createDynamicChild(spec: DynamicControlSpecification): Control { switch(spec.id){ case 'faxNumber': return new ListControl( ...propsForFaxNumber...) default: throw new Error('unknown child info'); } } }
Q & A: Why is all this necessary?
The problem being solved by
ControlManager.createControlTree()
andControl.reestablishState()
is to recreate a tree of controls in which each control includes both configuration props and state. The first complication is that the configuration props are generally not serializable as they may contain functions and deep references. Dynamic controls further complicate matters as we cannot know which controls to rebuild until we have reestablished some state.By rebuilding controls statically (normal case) and from POJO specifications (dynamic case) we can limit the information that must be tracked and still rebuild controls with all their complex props and state. Overall, these patterns allow for arbitrarily complex props while ensuring that only the critical information is tracked between turns.
The dynamic-control pattern is standardized in
DynamicContainerControl
to reduce the need for developers to reinvent the wheel.