# Vue Application development
# Starter projects
- See a
ChatMode
starter project here: https://codesandbox.io/s/vue-upil-chat-mode-example-t4zxp - See a
FormMode
starter project here: https://codesandbox.io/s/vue-upil-form-mode-example-lkf2c - See a
WizardMode
starter project here: https://codesandbox.io/s/vue-upil-wizard-mode-example-j3qfs
# Introduction
Generally, a Vue application developer will setup an instance of UPILCore
, and then pass that instance to a component which will translate an array of VisualNode
nodes[]
into visual elements, and handle user input.
There are two different modes that a UPILCore
can use to interpret a UPIL script. A chatbot-mode and a form-mode.
You can use the same script for both, utilizing the chatbot-mode to gather data initially, and then a form-mode to allow for displaying gathered data and editing saved values. The mode can be set as an option when
starting a UPILCore
instance.
# Widgets
Widgets are used to visualize nodes[]
objects generated by a UPILCore
instance.
# UPILCore
In order to consume UPIL scripts in your Javascript application, an instance of UPILCore
is used. UPILCore
contains the UPIL script parser, and the UPIL interpreter. In general,
a developer will create a UPILCore
instance and setup runtime hooks, and then pass it to a component which will handle the rest.
# The UPILCore
API
The API provides the following capabilities for communications to/from UPIL:
- A method to load a UPIL script as a string and start the interpreter.
- An array of
nodes[]
that can be represented as text or user input in your application. - A
consume
method used to send user input back to the UPIL engine. - Lifecycle and event listeners to run custom code in various situations such as:
- Scenario start
- Scenario end
- User input
- Preload state key-values
- Alternative modes:
- The default bot-mode that waits for user input before exposing subsequent nodes in the
nodes[]
array. - A form-mode which continues as far as possible in the script given that the variables required for interpretation already exist.
- The default bot-mode that waits for user input before exposing subsequent nodes in the
- The ability to get the current key-value pairs representing the state of the instance.
- The ability to subscribe to state or event updates.
As your application interacts with the UPIL engine via the consume
method, the UPILCore
instance will update nodes[]
appropriately based on the instructions in the UPIL script.
User-input can either be handled with the appropriate hook as variables are updated in UPILCore
, or the application can query UPILCore
for all variables' current state at any time.
Finally, state can be pre-loaded into a UPILCore
instance to influence the UPIL script's behavior, or to pre-populate values for text substitution or form-mode.
# Instantiation
Use the following pattern to instantiate a new UPILCore
instance:
import { UPILCore } from "@appsocially/userpil-core";
const upilInstance = new UPILCore();
2
Generally, the provided Vue.js components require the developer to instantiate their own UPILCore
instance and pass it as a prop to the component.
# Starting a scenario
Once our listeners and hooks are setup, we can pass in a scenario and start our upilInstance
using the startRaw
method.
The method takes two parameters, a string containing a UPIL script, and an optional settings object.
Example with default chatbot-mode:
upil.startRaw(scenarioString);
Example with form-mode activated in the settings object:
upil.startRaw(scenarioString, {
mode: "form",
resetOnInputUpdate: true,
});
2
3
4
# UPIL State
It is common to need access to the state being tracked by a UPIL instance. To retrieve the state, use:
const { input, nodes } = upilInstance.UpilStore.getState();
input
- An object containing the state of variables and their values as key-value pairsnodes[]
- An array ofVisualNode
. These are stored in the array in the order that they have been interpreted in the script. Each node is meant to convey information to a user. The Vue.js components use this array to build the UI.
TIP
Why do we say that the nodes[]
are composed of VisualNode
? There are other non-visual nodes used by UPIL such as actions, dialogs, groups, and boolean expressions. These exist in the event stream, but are filtered for your convenience so that it is easy to focus on mapping a UPIL script to a UI.
As a user interacts with UPIL, the interpreter will continue to save state, and update its nodes[]
. In order to be notified of these changes, use the upilInstance.UpilStore.subscribe
method, and pass in a function that you
want to be called each time the state changes.
upilInstance.UpilStore.subscribe(() => {
const { input, nodes } = upilInstance.UpilStore.getState();
// ...do something with the state
});
2
3
4
# Variable types
ChatMode, FormMode, and WizardMode can take advantage of variable types to validate user-input. This is implemented in the default template-widgets. For example, user-input validation when expecting an email address:
DIALOG getEmail
TEMPLATE
{
formText: "Full Name"
}
"What's your full name?"
>>name
/TEMPLATE
TEMPLATE
{
formText: "Email address"
}
"${name} please tell us your email address."
>>email:email
/TEMPLATE
TEMPLATE "We'll send a verification email to you at ${email}, thanks!"
/DIALOG
RUN getEmail
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Types are applied to variables with the :typeName
added to the end of the variable name as seen in the above script. Types are passed to either ChatMode or FormMode using the types
prop, which accepts an object with named arrays of rule-functions:
// Vue sfc component
<template>
<ChatMode
:types="types"
//... rest of props
/>
</template>
<script>
import { UPILCore } from "@appsocially/userpil-core";
import { ChatBot } from "@appsocially/vue-upil";
const { ChatMode } = ChatBot;
// Array of rule-functions
const emailValidationRules = [
value => (value && value.length > 0 ? true : 'Required'),
value => (email.validate(value) ? true : 'Invalid email address'),
]
// Pass the array of email rule functions as the 'email' type
const types = {
email: emailValidationRules,
}
export default {
components: {
ChatMode,
// rest of components....
},
data() {
return {
types
// ...
};
},
// ...
};
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
Each rule-function in a rules-array should return true if the rule is passing, or a string with the error-message if the value is invalid. The value is the current user-input.
If a custom-widget is being used, then that widget will receive the rules-array via the rules
prop. The widget may then implement its own validation-display and behavior using those rules if it chooses to.
# ChatMode Component
The ChatMode is a component that maps a scenario to a standard-looking chat application UI, where a user will be chatting with a 'bot' which is being driven by a UPIL scenario.
Basic example of ChatMode
component usage:
// Vue sfc component
<template>
<ChatMode
v-if="ready"
:listeners="listeners"
:override="override"
:overrideCurrent="overrideCurrent"
:upil="upil"
:avatar="Logo"
:botTypingDurationInMsPerMessage="400"
:types="types"
/>
</template>
<script>
import { UPILCore } from "@appsocially/userpil-core";
import { ChatBot } from "@appsocially/vue-upil";
const { ChatMode } = ChatBot;
import Logo from "./logo.png";
export default {
components: {
ChatMode,
// rest of components....
},
data() {
return {
Logo,
upil: new UPILCore(),
// ...
};
},
// ...
};
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
The listeners
prop expects an object whose keys are event names, and values are handler functions. The override
and overrideCurrent
props are functions which map between VisualNodes
and Vue components or widgets
. The upil
props is a upilInstance. The avatar
prop is an image which represents the chatbot's avatar in the Chat UI.
# Widget mapping in override functions
Each entity in a UPIL scenario is represented by a VisualNode
with various properties associated with it. These properties include:
- The entity type:
TEMPLATE
,SELECT
,MULTI_SELECT
- The text associated with the entity
- Entity input metadata if the entity expects user input. This metadata includes the variable name, variable type, and a list of options in the case of a select-based entity.
- The label assigned to an entity
- Whether the node is a reply-node or not
Vue components that represent a VisualNode
in the UI are called widgets
. By default, a VisualNode
is represented by a widget based on its entity-type. Using the override functions, nodes can be freely mapped to widgets based on any of the available node-properties. In general, when creating a custom widget for chat-mode, there are three node-types to consider (Standard, Reply, and Current):
# override vs overrideCurrent
The override
and overrideCurrent
props that are passed to the ChatMode
are funcions with identical signatures that receive a node, and can choose to map a Vue component to that node to be displayed in the Chat UI. The override
function is meant to define widget
mapping in the chat history (Standard and Reply nodes). The overrideCurrent
function is meant to map the current node to a special widget that lives at the bottom of the Chat UI and acts as the user-input. In this way, the UI can handle custom input types such as dates, colors, shapes, images, videos, etc. with custom widgets, in addition to standard input such as text and list-selection.
# Standard vs Reply nodes
Standard nodes appear on the left-side of the chat as if they are coming from the 'bot'. Reply nodes are shown on the right-side of the chat as coming from the user of the application.
If information other than text such as video, images, or complex or dynamic visualizations needs to be displayed as coming from a bot, then creating a custom widget for a Standard Node is appropriate.
If a user is replying to the bot in a form that can't easily be represented as text, then a custom widget for a reply node is appropriate. If the value of a variable is encoded, and needs to be decoded or specially formatted as text, then consider using a transformReplyVariables
function hook instead.
The a Reply Node's widget will receive almost identical props as the original Standard Node that the reply is in response to. The only differences being that the top-level reply
property will be true
instead of undefined
, and the Node's event.value
will store the reply value passed to upilCore
.
# Transform Reply Variables
The transformReplyVariables
function hook allows the developer to transform a variable's value before it's displayed as a reply from a user. The return value must be formatted as text. If more advanced visualization methods are required, consider using a custom widget to replace the built-in text-based reply instead.
The function signature of transformReplyVariables
:
transformReplyVariables({ node, upil, state, locale })
The transformReplyVariables
function receives an object with four properties: node
, upil
, state
, locale
:
node
: This is the same node object that custom widgets recieveupil
: The instance ofUpilCore
being used to drive the scriptstate
: The object representing theUpilCore
's current statelocale
: The current locale
Example transformReplyVariables
function to add a minutes-suffix to the range's raw reply (which is a Number
by default):
const calculateUnit = (locale) => {
switch (locale) {
case 'ja':
return '分'
default:
return ' minutes'
}
}
const transformReplyVariables = ({
node: {
label,
event: { value },
},
locale,
}) => {
if (label === 'range') {
const unit = calculateUnit(locale)
return `${value}${unit}`
} else {
return value
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Transform Text Variables
The transformTextVariables
is a function hook that lets you format variables that are substituted into the script. This allows you to add units to numbers, format dates, or transform objects into meaningful text to the user.
The function signature of transformTextVariables
:
transformTextVariables({ value, key, locale })
The transformTextVariables
function receives an object with three properties: value
, key
, and locale
:
value
: This is the raw value stored in UPIL state of the variable to be transformedkey
: This is the name of the variable in UPIL state. More accurately, this is the text used by the scriptwriter when adding${key}
style variable substitution expressions in the script.locale
: The current locale, may be undefined.
Example transformTextVariables
function which formats all dates:
const transformTextVariables = ({ value, locale }) => {
if (isDate(value)) {
return formatDateTimeString(value, locale)
} else {
return value
}
}
2
3
4
5
6
7
Example transformTextVariables
function which adds locale-specific minutes-suffix to the minutes
variable:
const calculateUnit = (locale) => {
switch (locale) {
case 'ja':
return '分'
default:
return ' minutes'
}
}
const transformTextVariables = ({ value, key: variableName, locale }) => {
if (variableName === 'minutes') {
const unit = calculateUnit(locale)
return `${value}${unit}`
} else {
return value
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Override function signature
function (_, node, component) {...}
The node
argument is the VisualNode
-representation of the entity that the UPIL engine is asking to map to a widget. The component
argument is the default component.
An example override function:
export function override(_, node, component) {
if (node.reply === true && node.label) {
switch (node.label) {
case "interviewTime":
return () => import("./timeReply");
case "qrCode":
return () => import("./qrCode");
case "dynamicListBuilder":
return () => import("./dynamic-list-builder");
default:
return component;
}
} else if (node.reply !== true && node.label) {
switch (node.label) {
case "qrCode":
return () => import("./qrCode");
default:
return component;
}
} else {
return component;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
The above function is mapping widgets based on nodes' labels, and whether the node is a reply node or not.
WARNING
If you do not handle a node, ensure that the default component
parameter is returned. This will guarantee that all nodes are mapped to a widget.
# ChatMode Widgets
Widgets are Vue components that represent a VisualNode
in the Chat UI. The ChatMode
component will dynamically instantiate widgets in the appropriate place in the Chat UI using the override function mapping. Each override widget receives the VisualNode
properties, and the upilInstance
as top-level props. In addition, nodes which expect user-input receive a sendInput
function to send user-input to the UPIL Core.
Some useful props from VisualNode
include:
text
- The text that a UPIL script has associated with a nodelabel
- The label assigned to a node in the UPIL scriptoptions
- The options given to aSELECT
orMULTI_SELECT
nodeinput
- The metadata surrounding an expected user-input such as the variable name and typeevent
- The context for this Node's creationreply
- Whether this node is a reply node or notargs
- Additional metadata passed to this node in the UPIL script
Other props:
sendInput
- A function that expects to receive the input from a user.upil
- TheupilInstance
# SendInput function
Widgets receive a sendInput
function as a prop. This function is used to update the variable associated with the widget's underlying VisualNode
. It accepts a single parameter. This function can be called zero or more times, and will cause an input-update
event to be fired each time it is called.
template
- A template-widget can pass any object or primitve it wants via thesendInput
function. If the widget passes aString
to the function, then the default reply-node template widget will be able to display it. Otherwise, the developer should supply their own reply-node widget to properly format the contents stored in the input-state.select
- A select-widget receives an array of options that the users can choose from. Each option is an object with the signature{text, value}
. When a user chooses an option, its correspondingvalue
should be passed into thesendInput
funciton.multi-select
- A multi-select widget works similarly to a select-widget. But instead of passing a single value, it should pass an array ofvalue
into thesendInput
function.
# Injected state
Chat mode widgets can access the upilInstance
state using injection:
import { state } from "@appsocially/vue-upil";
export default {
inject: {
state,
},
computed: {
// access a variable called 'jobLink' in the upil input state
jobLink() {
return state.inputState.jobLink;
},
},
// ...
};
2
3
4
5
6
7
8
9
10
11
12
13
14
# Form Mode
As opposed to the default Chat Mode, form mode is an alternative way to present a UPIL script. Form mode presents a script as a completely dynamic form, that follows the business logic in the UPIL script to decide which information to present. A great usecase for form mode is to allow information that was gathered from a chat bot to be edited afterwards. Because form mode is merely an alternative way to interpret a UPIL script, the same script can be used for both chat mode and form mode.
You can start a upilInstance in form mode by passing in the mode
and resetOnInputUpdate
as follows:
upil.startRaw(scenarioString, {
mode: "form",
resetOnInputUpdate: true,
});
2
3
4
Bewlow is an example comparison between form mode and chat mode. Notice form mode ignores entities with no user input, and dynamically updates itself if the user goes back and changes values:
DIALOG mainDialog
TEMPLATE "Welcome to our job application bot!"
TEMPLATE
{
formText: "First name"
}
"What's your first name?"
>>firstname
/TEMPLATE
TEMPLATE
{
formText: "Last name"
}
"What's your last name?"
>>lastname
/TEMPLATE
SELECT
"${firstname} ${lastname}, what job do you want to apply for?"
-"Janitor"
-"Chef"
>>selectedJob
/SELECT
IF selectedJob == "Janitor"
TEMPLATE
{
formText: "Favorite vacuum"
}
"What is your favorite brand of vacuum?"
>>favoriteVacuum
/TEMPLATE
ELSE
TEMPLATE
{
formText: "Favorite dish"
}
"What is your favorite dish to make?"
>>favoriteDish
/TEMPLATE
/IF
TEMPLATE "Thank you for your application!"
/DIALOG
RUN mainDialog
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# Differences in behavior between form mode and chat mode
There are two primary differences between the two modes:
- Form mode attempts to show as many
VisualNode
as possible, whereas chat mode only shows a single node at a time. Form mode generally waits at a boolean expression in a script until the data which the boolean expression requires exists in the UPIL Core's state. - Form mode is non-linear, meaning it's possible to update user-input after it's already been set. The UPIL Core will reinterpret the script at that point, and update the
node[]
as necessary.
Other differences:
- In form mode, recursion is generally prevented by only allowing a
DIALOG
to be executed a single time. In chat mode, recursion can be used to return to earlier parts of a script if necessary. - Chat mode widgets generally send input to the UPIL Core when a user performs an explicit action like pressing the 'send' button. Form mode widgets don't have an explicit send button, so they should send input to the UPIL core implicitly such as while a user is typing, on blur, or when some other final input condition is met.
# The RawFormBot component
The RawFormBot
component provides a minimally styled form which can be embedded in an application. It also communicates useful information to it's parent context via synced props such as whether its upilInstance is loading, and if there are any nodes with missing values. The RawFormBot
component hides nodes that don't accept user input.
<RawFormBot
:upil="upil"
:isMissingValue.sync="isMissingValue"
:initializingUpil.sync="initializingUpil"
/>
2
3
4
5
Props:
upil
- A form mode initializedupilInstance
isMissingValue
- A boolean that is true if there are nodes which are missing user inputinitializingUpil
- True if theupilInstance
is loading
# Preloaded data
Form mode is often used to edit or update answers originally gathered in chat mode. This means that the upil state will often be preloaded with gathered input. It's recommended to use the preload-input hook to accomplish this.
# Form mode widgets
In form mode, there is no concept of a 'current' node like there is in chat mode. Currently, widget mapping occurs in the component itself:
function `calculateComponent` ({ type, label }) {
const byTypeComponent = componentByType({ type }, null)
return componentByLabel({ label }, byTypeComponent)
}
2
3
4
To add a new mapping, it should be added to componentByLabel
, or a new function in case nodes need to be overriden on a property other than its label.
The props signature is also different from the widgets in chat mode:
node
- TheVisualNode
being mapped to this widgetstate
- The input state of theupilInstance
upil
- TheupilInstance
User input is passed back to the upilInstance
using its consume method. The consume function accepts two parameters: the VisualNode
event and the user's input:
upilInstance.upil.consume(node.event, userInput);
Form mode widgets should be created with the assumption that the user-input they are requesting may already have been pre-loaded. This differs from chat mode 'current' widgets which generally don't need to handle pre-loaded values.
# Form mode specific text in UPIL scripts
By default, the node.text
value passed to widgets is the same as a chat mode widget would receive. However, a script writer can use entity meta-data to pass form-mode-specific text to form mode widgets. The formText
property allows node text to be overridden:
DIALOG mainDialog
TEMPLATE "Hi there!"
TEMPLATE
{
formText: "First name"
}
"What's your first name?"
>>firstname
/TEMPLATE
TEMPLATE
{
formText: "Last name"
}
"What's your last name?"
>>lastname
/TEMPLATE
/DIALOG
RUN mainDialog
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Wizard Mode
Wizard mode is an alternative method of visualizing a form. It takes form mode components, but displays them one at a time, like a traditional wizard.
Since wizard mode is a re-skinning of form mode, it also behaves nearly identically. It filters out non-input nodes. It will show as many nodes as it can based on variable-dependencies (in the wizard-header at the top). Since wizard mode uses form mode widgets, they automatically send input to the UPILInstance without waiting for user-confirmation. Finally, a user can move forward and backwards through the wizard to edit their responses.
# Differences with form mode
Aside from showing a single widget at a time, the other major difference between wizard mode and form mode is that it shows both the formText
and the default text to a user. The generally more-compact formText
is used in the wizard's top header-bar. The default text is shown above the currently displayed widget. If there is no formText
, the default text will be shown in the header-bar instead.
DIALOG mainDialog
TEMPLATE "What is your name?"
TEMPLATE
{
formText: "First name"
}
"What's your first name?"
>>firstname
/TEMPLATE
TEMPLATE
{
formText: "Last name"
}
"What's your last name?"
>>lastname
/TEMPLATE
SELECT
"${firstname} ${lastname}, what job do you want to apply for?"
-"Janitor"
-"Chef"
>>selectedJob
/SELECT
IF selectedJob == "Janitor"
TEMPLATE
{
formText: "Favorite vacuum"
}
"What is your favorite brand of vacuum?"
>>favoriteVacuum
/TEMPLATE
ELSE
TEMPLATE
{
formText: "Favorite dish"
}
"What is your favorite dish to make?"
>>favoriteDish
/TEMPLATE
/IF
TEMPLATE "Thank you for your application!"
/DIALOG
RUN mainDialog
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# Listeners
Lifecycle hooks and event handlers, which we call listeners
in UPIL, allow an application to respond to UPIL events. This can include preparing the UPIL instance's state or performing side effects in response to user interaction.
See an example project which uses ACTION
nodes and an action
listener to call an external API and return data to the script: Listeners Example Project
Developers can register a hook using the upilInstance.on(event, handler)
method. The event
is a string which identifies which event to subscribe to, the handler
is a function.
Alternatively, an object containing listeners can be passed to the setupListeners
convenience function to register several at once:
import { setupListeners } from "@appsocially/vue-upil";
import { UPILCore } from "@appsocially/userpil-core";
const listeners = {
// ...listeners such as external, action, etc.
};
const upil = new UpilCore();
setupListeners({ listeners, upil });
2
3
4
5
6
7
8
9
10
The following events can be subscribed to:
preload-input
- Expects a handler which returns an object containing key-values to preload the state of theupilInstance
scenario-start
- Runs at the beginning of a scenario. Ignores the handler's return object.scenario-end
- Runs at the end of a scenario. Ignores the handler's return object.scenario-end
- Runs at the end of a scenario.input-update
- Runs whenever theconsume
function of aupilInstance
is called.external
- Runs whenEXTERNAL
nodes are found in a scenario.EXTERNAL
nodes are used to request data be loaded into pre-defined variables before a scenario starts.action
- Runs whenACTION
nodes are found in a scenario.ACTION
nodes are used to perform side-effects, or load data into variables mid-script.
The handler
functions receive two arguments:
async function(payload, preventDefault)=>{...}
The payload
contains a context
property, which lets you get the current state of the upilInstance
, and an event
property, which allows you to get the event type, and the node that triggered the event. A breakdown of the
payload
argument:
const {
context: { store },
event: {
node: {
text,
type,
input: { name },
label,
},
},
} = payload;
const { mode } = payload.context.settings(); // Whether botmode or formmode
const state = store.getState();
const { input } = state; // Object containing key-value pairs for variables in the upilInstance
const inputValue = input[name]; // In an `input-update` event, get the user-input that triggered the event
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# External listener
The external handler will be called when a script requires variables to be loaded before running. Example externals in UPIL script and matching listener:
EXTERNAL weather
EXTERNAL currentTime
DIALOG example
TEMPLATE "The current weather is ${weather}"
TEMPLATE "The current time is ${currentTime}"
/DIALOG
RUN example
2
3
4
5
6
7
8
9
const listeners = {
async external(payload, preventDefault) {
preventDefault();
const { topic } = payload;
switch (topic) {
case "weather":
return resolveWeather();
case "currentTime":
return resolveCurrentTime();
}
},
// ... other listeners
}
2
3
4
5
6
7
8
9
10
11
12
13
WARNING
You must call preventDefault
in the external
listener
Using the topic variable in the payload, we can return a value based on the tag of the external being fulfilled. The external
listener will be called once for each EXTERNAL
node in the script.
# Input-Update listener
The input-update
listener is called each time a the upilInstance is sent a new value for a variable. Using this listener, we can respond to user-input immediately. This can be useful for performing side-effects such as saving information on the fly, or calling analytics or other services:
const listeners = {
'input-update': async (payload, preventDefault) => {
preventDefault()
/**
* name - The name of the variable which was updated, triggering this input-update call
* label - The label of the node from the script, if any, whose variable was updated
* text - The text contained in the node whose variable was updated
* type - The type of the node whose variable was updated
**/
const { context: { store }, event: { node: { text, type, input: { name }, label } } } = payload
const state = store.getState()
const { input } = state
// Value of user-input
const inputValue = input[name]
doSomethingWithUserInput(inputValue)
}
// ... other listeners
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
WARNING
You must call preventDefault
in the input-update
listener
# Preload-Input listener
The preload-input
listener dumps an object's key-value pairs as variables and their values, into the state of a upilInstance. It is useful when populating the state of a upilInstance in conjunction with form-mode, so that the form is pre-completed. This prevents the need to include an EXTERNAL
for each variable in the script.
const listeners = {
'preload-input': async () => {
return {
variableName: "variableValue"
}
},
// ... other listeners
}
2
3
4
5
6
7
8
# Action listener
An action
listener has an identical signature to an external
listener. One difference in usage is that ACTION
nodes often receive arguments (EXTERNAL
nodes cannot pass arguments):
DIALOG example
TEMPLATE
"What currency do you want me to get the exchange rate for (to USD)? (JPY, etc)"
>>inputCurrencyCode
/TEMPLATE
ACTION getExchangeRate
{
inputCurrencyCode
}
>>rate
/ACTION
TEMPLATE "The exchange rate is ${rate}"
/DIALOG
RUN example
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const listeners = {
async action(payload, preventDefault) {
preventDefault()
const {
// Get the label of the ACTION node
event: {
node: { label }
},
/**
* args - Arguments passed from an ACTION node can be accessed in the payload's 'args' property.
**/
args: { inputCurrencyCode }
} = payload
switch (label) {
case "getExchangeRate":
return getExchangeRate(inputCurrencyCode)
case "calculateExchange":
return calculateExchange(payload)
}
},
// ... other listeners
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
WARNING
You must call preventDefault
in the action
listener
# Internationalization
By passing metadata into a component, we can add text for alternative locales:
DIALOG getName
TEMPLATE
{
formText: "Name",
i18n: {
ja: {
formText: "名前",
text: "お名前は?"
}
}
}
"What's your name?"
>>name
/TEMPLATE
TEMPLATE
{
i18n: {
ja: {
text: "${name}、いらっしゃい!"
}
}
}
"Welcome ${name}"
/TEMPLATE
/DIALOG
RUN getName
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# i18n UPIL script metadata keys
For each locale you wish the script to support, pass an object whose key is equal to the local string you will pass in to the Vue component (ChatMode, FormMode, or WizardMode). The object's properties can include:
text
: string - For adding translations of a node's textformText
: string - For adding translations of a node's formTextoptions
: object - For translatingSELECT
orMULTI_SELECT
options' text
# i18n options
object
The options
object allows SELECT
options to provide locale-appropriate options to the user when they are making a selection:
DIALOG favColor
SELECT
{
formText: "Favorite Color",
i18n: {
ja: {
formText: "一番好きな色",
text: "一番好きな色を選んでください",
options: {
red: "赤",
blue: "青",
green: "緑"
}
}
}
}
"Please choose your favorite color"
-("Red", "red")
-("Blue", "blue")
-("Green", "green")
>>color
/SELECT
TEMPLATE
{
i18n: {
ja: {
text: "${color}はいい色だね!",
options: {
color: {
red: "赤",
blue: "青",
green: "緑"
}
}
}
}
}
"\${color} is a great color!"
/TEMPLATE
/DIALOG
RUN favColor
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
When used in a SELECT
or MULTI_SELECT
node, the options
object will be used to translate the options's display text:
SELECT
{
formText: "Favorite Color",
i18n: {
ja: {
formText: "一番好きな色",
text: "一番好きな色を選んでください",
options: {
red: "赤",
blue: "青",
green: "緑"
}
}
}
}
"Please choose your favorite color"
-("Red", "red")
-("Blue", "blue")
-("Green", "green")
>>color
/SELECT
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
In this case, the value of the options (lowercase "red", "blue", "green") are used to lookup the alternative locale text ("赤", "青", "緑") when the locale is set to "ja". Even when the locale is set to "ja" however, the value stored in the variable will still be "red", "blue", or "green".
This means that if a scriptwriter attempts to use this value later using variable-substitution, then the untranslated value will be displayed. To display translated options in a script, we must place the options object in whichever node we wish to display the text in:
TEMPLATE
{
i18n: {
ja: {
text: "${color}はいい色だね!",
options: {
color: {
red: "赤",
blue: "青",
green: "緑"
}
}
}
}
}
"\${color} is a great color!"
/TEMPLATE
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
In this case, the plugin will find that there is a both a color
variable to be substituted, and look for a corrosponding key in the options
object. Then it will use the value of the color
variable to lookup which text it should substitute for the current locale ("red" => "赤", "blue" => "青", "green" => "緑"). If it can't find a value, or if there's no appropriate locale or options
object, it will display the variable's raw value.
# Widget localization and default-text overrides
Each of the three modes accepts an i18n
prop for overriding default widget placeholder text based on node-type and locale. The prop should be in the form of an object with the following structure:
i18n: {
ja: {
missingValue: '未記入',
templateInputPlaceholder: '入力してください',
selectInputPlaceholder: '選んでください',
multiSelectInputPlaceholder: '選んでください'
}
}
2
3
4
5
6
7
8
The missingValue
key is for overriding the alert text at the top of FormMode
widgets which haven't yet received user input. The other three keys are for overriding default placeholder text in ChatMode
's current-template, current-select, and current-multi_select widgets.
# Custom widget i18n
Custom widgets receive the current locale
as a prop, which enables them to react to changes in a user's locale.