Bixby Developer Center

References

Expression Language Reference

Bixby's Expression Language (EL) is used in conditionals and strings in the *.bxb format.

Strings can include EL functions via the #{%expression%} (or ${%expression%}) notation.

Concept values can be bound to an EL expression by wrapping the expression in the $expr() construct, optionally prefixing it with a type to coerce the value to.

// untyped expression
value: $expr(brand)

// typed expression
value: Distance$expr(calculateDistance($user.currentLocation, point, 'Miles'))

The following list describes EL function parameters:

  • A node in an EL function is a concept or action model with values set, such as a Business with its name, address, categories, etc. A node can be either a structure or a primitive unless otherwise specified.
  • A type is the kind of model a node represents, whether structure or primitive: for example, integer, text, enum, Business, HotelRoom.
  • Parameters can be more specific, such as datetime for a viv.time.DateTime concept (or another concept convertible to it); unit for a string indicating a measurement unit; point for a viv.geo.GeoPoint (or another concept convertible to it). Read the function's description for context.
  • Optional parameters to EL functions are denoted in [brackets].

You can use the various functions listed in this reference in conjunction with the EL Operators, such as ternary conditionals and logic, as well as accessors.

Geospatial Functions

These functions provide support for location-based GIS functions. Most of them rely on concepts in the viv.geo library.

  • calculateDistance(point1, point2, units): Calculate the distance between two geopoints.

    In this example, calculateDistance() is used in a selection strategy to find points of interest within 25 miles of the current user location:

    selection-strategy {
    id (user-near-named-point)
    match: geo.NamedPoint (this)

    named-advice ("near-current") {
    advice ("${calculateDistance($user.currentLocation, this.point, 'Miles')}")
    advise-for { lowerBoundClosed(0) upperBound(25.0) }
    }
    }
  • within(point, shape): Return true if a geopoint is within a certain geospatial region, false if not.

    selection-strategy {
    id (user-in-search-region)
    match: SearchRegion (this)
    named-advice ("user-in-search-region") {
    advice ("${within($user.currentLocation, this.shape) ? 1.0 : 0.0}")
    advise-for { lowerBound(1.0) upperBoundClosed(1.0) }
    advise-against { lowerBound(0.0) upperBoundClosed(0.0) }
    }
    }

Date/Time Functions

These functions operate on DateTime values. They utilize concepts that must be imported from the viv.time Core Capsule.

  • isFuture(datetime): Return true if the datetime occurs in the future, false if not.

    activity-support {
    match: Receipt (this)
    states {
    if (exists(this.relevantDateTime) && isFuture(this.relevantDateTime)) {
    ...
    } else-if (exists(this.relevantDateTime) && isPast(this.relevantDateTime)) {
    ...
    }
    ...
    }
    }
  • isPast(datetime): Return true if a datetime occurs in the past, false if not.

  • isLastDayOfMonth(datetime): Return true if the day of the datetime is the last day of the month. If it is not, or if any of the month, day, or year fields are not set, return false.

  • durationBetween(datetime1, datetime2): Return the duration between two datetimes, as a time.DurationPeriod. Duration is returned in absolute time, regardless of which date is before the other.

    In this example, durationBetween() and addDuration are used to set the initial value of a duration-picker in an input view:

    input-view {
    match: MyDuration (duration)

    message ("How long of a duration?")

    render {
    duration-picker {
    initial-value("#{durationBetween(addDuration(now(), 'PT1H'), now())}")
    type (MyDuration)
    }
    }
    }
  • durationInUnit(datetime1, datetime2, unit): Return the number of specified date/time units present between two datetime nodes. The unit can be any valid chronounit enum.

    In this example, durationInUnit() is used to provide special handling for businesses that are open 24 hours a day:

    dialog (Value) {
    match: OpenHours (this)
    if ("#{durationInUnit(this.start, this.end, 'MINUTES') < 2 || durationInUnit(this.start, this.end, 'MINUTES') > 1438}") {
    template("24H") {
    speech ("24 hours")
    }
    } else {
    template("#{dateTime(this.start, 'h:mm a')} - #{dateTime(this.end, 'h:mm a')}") {
    speech ("from #{dateTime(this.start, 'h:mm a')} to #{dateTime(this.end, 'h:mm a')}")
    }
    }
    }

    Note that chronounit enums must be all caps, such as 'DAYS', 'HOURS', 'SECONDS'.

  • addDuration(datetime, period) and subtractDuration(datetime, period): Add or subtract time duration. Takes a date/date-time and a duration, either as an ISO 8601 duration format, a viv.time.DurationPeriod, or a viv.core.BasePeriod.

    See durationBetween() above for an example of addDuration() in use.

  • now([timezone]): Return the current DateTime as a viv.core.BaseDateTime. (The viv.time.DateTime model is an extension of this core structure.) Uses the default user timezone, unless one is provided. Use now().date to return the date only.

    See durationBetween() above for an example of now() in use.

Formatting Functions

These functions are intended to be used in template strings to aid in formatting output. This can be especially useful in dialog.

  • concept(node[, feature]): Format the supplied node as a concept dialog fragment. You can optionally pass an argument for the concept feature: Definite ("the concept"), Indefinite ("a concept"), or Proximal ("these concepts").

    dialog (Input) {
    match: SearchCriteria (this)
    if (size(this) == 0) {
    template("")
    } else-if (size(this) == 1) {
    template ("with #{concept(this, 'Definite')}")
    } else {
    template ("with #{concept(this, 'Indefinite')}")
    }
    }
  • action(node): Format the supplied node as an action dialog fragment.

    dialog (NoResult) {
    match {
    _(this) {
    from-property: Recipe(recipe)
    }
    }
    template("I couldn't #{action(this.pre())}.")
    }
  • input(node): Format the supplied node as an input dialog fragment.

  • value(node): Format the supplied node as a value dialog fragment. If a value fragment is not defined for the node's concept, the node's value will be rendered as a string (like the raw() EL function).

  • event(node, dialogMode): Format the supplied node as a specific dialog event mode. In addition to providing the node to render, you also must provide the event mode to render the node as.

    dialog {
    template ("#{event(weather, 'Result')}")
    }
  • macro(macro-id[, param1, param2, param3, ...]): Invoke a macro with the specified parameters. For example, suppose this macro has been defined with macro-def:

    macro-def (Bookmarks) {
    params {
    param (number) {
    type (example.book.Bookmarks)
    max (One) min (Required)
    }
    }
    content: template ("#{value(number)} bookmarks")
    }

    You can invoke that macro in EL with the macro command. This lets you include templates within other templates:

    template ("You have #{macro('Bookmarks', 23)}.")

    The macro ID is specified as a string in the first parameter. Parameters must be specified in the order they are specified in the macro definition; parameters can be omitted at the end if they are Optional or have a default value.

  • list(node, format): Format the individual values of the specified node in the specified format, then join those values as a conjunctive list ("value1, value2, and value3"). The format argument must be one of the fragment template functions: concept, action, value, or event.

    if (exists(alarms.repeatDays) && size(alarms.repeatDays) < 7) {
    dialog-template ("#{list(alarms.repeatDays.day, 'value')}")
    } else {
    dialog-template ("")
    }

    If alarms were set on Monday, Wednesday, and Friday, the resulting string from list would be "Monday, Wednesday, and Friday".

  • listWithLimit(node, format, limit): Format up to limit individual values of the specified node in the specified format, then join those values as a conjunctive list ("value1, value2, and value3"). The format argument must be one of the fragment template functions. If limit is exceeded, include the number of omitted values in the formatted list, for example, "John, Cindy, and 3 others".

  • joinAs(format, node1[, node2, node3, ...]): Render the specified nodes in the specified format, then join those formatted strings as a conjunctive list ("value1, value2, and value3"). The format argument must be one of the fragment template functions. (The list and listWithLimit functions operate on values of a single node; joinAs operates on multiple nodes that contain single values.)

    dialog (Result) {
    match {
    BusinessCategory (this) {
    from-property: Business (business)
    }
    }
    template("#{value(business.name)} has #{joinAs('value', this)}.")
    }
  • flattenAs(format, node1[, node2, node3, ...]): Flatten the specified nodes, regardless of cardinality, into a single list, format them in the specified format, then join those formatted strings as a conjunctive list ("value1, value2, and value3"). The format argument must be one of the fragment template functions.

    Note

    The difference between joinAs() and flattenAs() is how they handle nodes with max (Many) cardinality and multiple values. The joinAs() function will render each multi-cardinal node as separate conjunctive lists and then join those in a final list, which can lead to awkward constructions such as "lemons, apples, and pears and butter, milk, and cheese". Using flattenAs in this situation will flatten the lists in each node together before joining them, resulting in the correct construction: "lemons, apples, pears, butter, milk, and cheese".

  • raw(node): Render the single, primitive data value in the specified node as a string, if one exists. Unlike value(), the raw() function does not use a value dialog fragment.

    result-view {
    ...
    paragraph {
    value ("#{raw(this)}")
    }
    ...
    }
  • integer(value): Format the specified value as an integer in the locale of the current rendering context. A value of 1000.1 would be rendered as "1,000" in the en-US locale.

  • percent(value): Format the specified value as an percentage in the locale of the current rendering context. A value of 1.1 would be rendered as "110%" in the en-US locale.

  • scientific(value): Format the specified value as an scientific number in the locale of the current rendering context. A value of 1000.1 would be rendered as "1.0001E3" in the en-US locale.

  • ordinal(value): Format the specified value as an ordinal number in the locale of the current rendering context. A value of 2 would be rendered as "2nd" in the en-US locale.

  • spell(value): Format the specified value as words in the locale of the current rendering context. A value of 32 would be rendered as "thirty-two" in the en-US locale.

  • number(value[, formatString]): Format the specified value as a number either in the locale of the current rendering context or using the optionally supplied DecimalFormat string. Example: number(airfare.total.price, '#,##0')

  • dateTime(node[, formatString]): Format the specified node as a date/time number either using the default format of the locale of the current rendering context or using the optionally supplied Joda DateTimeFormatter string. Example: dateTime(log.timeStampInfo, "h:mm a")

Pagination Functions

These functions are intended for use within page-marker blocks for Hands-Free List Navigation:

...
page-content (page) {
page-marker {
if (isFirstNavPage(page)) {
template ("The first #{size(page)} items are")
} else-if (isLastNavPage(page)){
if (size() == 1) {
template ("The last item is")
} else {
template ("The last #{size(page)} is")
}
} else {
template ("The next #{size(page)} items are")
}
}
}
...
  • isFirstNavPage(node): returns true if the user is on the first page of items, false if not.

  • isLastNavPage(node): returns true if the user is on the last page of items, false if not.

Collections Functions

These functions work on a collection of items where EL functions are supported.

  • contains(node, value): Return true if the specified node contains the specified value, false if it does not.
render {
form {
elements {
if (contains(requiredFields, 'structuredName')) {
...
}
}
}
}
  • indexOf(node1, node2): If node2 is a member of node1, returns the integer index value of the position of node2. If node2 is not a member of node1, then -1 will be returned.

  • subtract(node1, node2): Remove elements in node1 that are found in node2, then return the resulting subset of node1.

  • intersect(node1, node2): Remove elements in node1 that are not found in node2, then return the resulting subset of node1 (the intersection of the two nodes).

  • dedupe(node): Remove elements in node that are equivalent, returning a subset with only unique values. Read Merging and Equivalence for details and a usage example.

  • macroEach(macro-id, node[, index, param1, param2, ...]): Invoke a macro with the specified parameters for each element in a multiple-value node, such as a property of a structure concept with a max (Many) cardinality. The macroEach function will return a new multiple-value node that replaces each value of its input node with the results of the invoked macro. This can be used, for instance, to apply the plural() EL function to individual values within a node.

    • macro-id: the name of the template macro to invoke (as a string)
    • node: the multiple-value node to iterate through
    • index: the 0-based index of the macro parameter where each element should be injected (optional if the macro has only one parameter)
    • param1, param2, etc.: other parameters to use when invoking the macro

    Example 1

    dialog (Result) {
    match: MyStruct (this)
    // if the macro has only one parameter, index can be omitted
    template("Result #{list(macroEach('pluralize-thing', this.thingName), 'value')}")
    }

    macro-def (pluralize-thing) {
    params {
    param (thingName) {
    type (ThingName)
    min (Required)
    max (One)
    }
    }
    content {
    template ("#{value(thingName.plural('Many'))}")
    }
    }

    Example 2 (specifying parameters)

    dialog (Result) {
    match: MyStruct (this)
    // this macro has two parameters, so the index specifies the
    // second parameter (0-based) as the one being replaced and passes
    // the first one (thingType) to the macro as a parameter
    template("Result #{list(macroEach('pluralize-thing', this.thingName, 1, 'X'), 'value')}")
    }

    macro-def (pluralize-thing) {
    params {
    param (thingType) {
    type (ThingType)
    min (Required)
    max (One)
    }
    param (thingName) {
    type (ThingName)
    min (Required)
    max (One)
    }
    }
    content {
    template ("#{value(thingName.plural('Many'))} of type #{thingType}")
    }
    }

    The elements returned by macroEach are always of type viv.core.MultiModalString, a simple structure containing two text properties: text and speech.

  • getPreviousPage(node): Returns the collection from the previous selection context. This is intended for use in intent blocks.

    In the following example, filterTerm is used to filter a set of businesses, retrieved in the computed-input block using getPreviousPage('Business').

    action (SelectBusiness) {
    type (Search)
    collect {
    input (filterTerm) {
    type (BusinessFilterTerm)
    min (Optional) max (One)
    }

    //select from items in this list
    computed-input (business) {
    type (Business)
    min (Required) max (Many)
    compute {
    intent {
    goal: Business
    value-set: Business {$expr("getPreviousPage('Business')")}
    }
    }
    }
    }
    output (Business) {
    on-empty {
    flag-as-off-topic
    }
    }
    }
    Note

    When enhanced list navigation is enabled and either the show-ordinals-on-list view override or show-ordinals-on-list capsule runtime override are also enabled, then getPreviousPage() will return the full list of results. To return only the filtered items even under those conditions, use the getPreviousFocus() function.

  • getPreviousFocus(node): Returns the collection from the previous selection context. This is intended for use in intent blocks.

    The getPreviousFocus() command functions identically to getPreviousPage(), but always returns the filtered items, regardless of whether either show-ordinals-on-list override is enabled. See the note at getPreviousPage().

User and Device Information Functions

These functions return information about the user or their device.

  • getLocale(locale): Get locale-related information for a given locale identifier. Returns a node with the following properties:

    • country: ISO 3166-1 alpha-2 country code (for example: "US", "KR")
    • iso3Country: ISO 3166-1 alpha-3 country code (for example: "USA", "KOR")
    • language: ISO 639-1 language code (for example: "en", "ko")
    • iso3Language: ISO 639-2 language code (for example: "eng", "kor")
    • currencyCode: ISO 4217 currency code (for example: "USD", "KRW")
    • currencySymbol: currency symbol (for example: "$", "₩")
    • measurementSystem: one of USC, Imperial, or Metric

    In this example, getLocale is used to determine the user's measurement system. The calculateDistance geospatial function is also used.

    action (GetCurrentDistance) {
    type (Fetch)

    collect {
    input (point) {
    type (GeoPoint)
    min (Required)
    plan-behavior (Always)
    }
    }

    output (Distance) {
    evaluate {
    if (getLocale($user.locale).measurementSystem == 'USC') {
    Distance$expr(calculateDistance($user.currentLocation, point, 'Miles'))
    } else {
    Distance$expr(calculateDistance($user.currentLocation, point, 'Kilometers'))
    }
    }
    }
    }
  • permissionGranted(permission): Return true if the passed permission has been granted for the capsule. The available permission values are listed in the capsule.permissions reference key.

    output (Something) {
    evaluate {
    if("#{permissionGranted('device-location-access')}") {
    ...
    } else {
    ...
    }
    }
    }

    For more information, see the Grant Capsule Permissions section of Preparing Your Capsule in the developer's guide.

    Note

    The permissionGranted() function cannot check for permissions granted via imported library capsules, such as bixby.contact.

Matching Functions

  • min(node[, property]): Return the minimum value of the given node (using an optional property for comparison, if provided).

  • max(node[, property]): Return the maximum value of the given node (using an optional property for comparison, if provided).

    action (maxWithPropertyInAction) {
    type(Search)
    description (returns the employee with the max int)
    collect {
    input (structureOfNameIntBool) {
    type (StructureOfNameIntBool)
    min (Optional) max (Many)
    default-init{
    intent{
    goal: GetEmployees
    }
    }
    }
    }
    output (StructureOfNameIntBool){
    evaluate{
    $expr(max(structureOfNameIntBool, structureOfNameIntBool.name))
    }
    }
    }
  • compareAll(node1, node2): Compare two specified nodes, using default natural ordering. Returns a negative integer when node1 is less than node2, a positive integer when node1 is greater than node2, or zero if node1 and node2 are equal.

    For purposes of comparison, non-null is less than null and non-empty is less than empty. When comparing two nodes that have a different number of values, the one with fewer values is less than the one with more. Comparison of multi-value nodes of an equal number of values is made on a per-value basis. For example, with the following nodes:

    node1 = null/empty node
    node2 = ["yyy"]
    node3 = ["aaa", "bbb"]
    node4 = ["ccc", "ddd"]

    The following comparisons result:

    if (compareAll(node1, node1) == 0)   // both are null/empty
    if (compareAll(node1, node2) == 1) // null/empty > non-null/non-empty
    if (compareAll(node2, node3) == -1) // node2 has fewer values
    if (compareAll(node4, node3) == 1) // same length, node4's values > node3's
    if (compareAll(node3, node3) == 0) // same length, equal values
    Note

    The compareAll() function replaces the now-deprecated older compare function, which cannot handle empty or multi-value nodes. You should update any existing capsule code that uses compare() to use compareAll().

  • compareSemVer(ver1, ver2) : Compares SemVer versions using the default natural ordering of version strings. The ver1 and ver parameters are different semantic version strings. Returns a negative integer when ver1 is less than ver2, a positive integer when ver1 is greater than ver2, or zero if ver1 and ver2 are equal.

    if (compareSemVer(this.version, '2.0.0')) >= 0 {
    template (version2)
    } else {
    template (version1)
    }
  • regexAllMatch(node, regex): Return true if (and only if) all values in the node match the specified regular expression, false if any values do not match.

  • regexAnyMatch(node, regex): Return true if any values in the node match the specified regular expression, false if no values match.

    if (regexAnyMatch(this.name, 'Les? .+')) {
    template ()
    } else {
    template ("à #{value(this.name)}")
    }

String Functions

  • lower(string): Format all characters to lowercase in the supplied string.

    dialog (Result) {
    match {
    Receipt (receipt)
    }
    template ("#{value (receipt.order)} is #{lower(value (receipt.orderState))}!")
    }
  • upper(string): Format all characters to uppercase in the supplied string.

  • title(string): Convert the first character of the supplied string to title case.

  • truncate(string, length): Truncate the supplied string to the desired maximum length.

  • length(string): Return the length of a string. On a MultiModalString structure as returned by the macroEach() function, it will return the length of the text property. length() should only be given single-value arguments; if it is given a multi-cardinal argument with more than one result, it will return 0.

  • similarity(string1, string2): Return the 3-gram edit distance between two strings.

    This strategy uses similarity to help find localities the user is searching for:

    selection-strategy {
    id (locality-name-similarity)
    match {
    NamedPoint {
    from-output: ConstructNamedPointFromRegion {
    from-input: Locality (locality) {
    from-output: FindLocality {
    from-input: LocalityName (find)
    }
    }
    }
    }
    }
    named-advice ("rank") {
    advice ("${ similarity(locality.name, find) }")
    advise-against { lowerBoundClosed (0.0) upperBoundClosed (0.5) }
    advise-for { lowerBoundOpen (0.5) upperBoundClosed (1.0) }
    }
    }
  • editDistance(string1, string2): Return the Levenshtein distance between two strings.

Node Evaluation Functions

  • test:instanceOf(node, type): Return true if the value(s) of the specified node are instances of the specified type, false if they are not.

    if (test:instanceOf(region, 'viv.geo.LevelOneDivision')) {
    // region is an instance of viv.geo.LevelOneDivision
    }
  • test:assignableFrom(type1, type2): Return true if type1 is a valid type identifier and can be assigned from type2, false otherwise.

  • typedValue: Assign a concept type to a value and return it as a new node.

    template ("#{typedValue('contact.EmailType', 'home')}")
  • size(node): Return the number of values contained in a node. A single-item node has a size of 1; a multi-item node, such as a result list, has a size of the list's length. This is often used in result views to choose between displaying a summary of items or the details of an item.

    result-view {
    match {
    RideShare (rideShare)
    }
    message ("Here's your ride[ from #{value(rideShare.sourcePoint)}][ to #{value(rideShare.destinationPoint)}].")
    render {
    if (size(rideShare) == 1) {
    layout-match (rideShare) {
    mode (Details)
    }
    }
    }
    }
  • exists(node): Return true if the node has a value, false if it does not.

  • empty(node): Return true if the node has no value, false if it does. This function is the inverse of exists(): when one is true, the other is false.

  • isElement(node): Return true if the node is an element of an array. This occurs when the node is being accessed with an array-style iterator such as for-each and where-each, or when the user navigates from a summary layout to a detail layout. Note that isElement() always returns true if the node being tested is an array element, even if it is the only element in the array.

  • relaxed(node): Return true if one or more of the node's search constraints have been relaxed, false if they have not. See Relaxation for more about constraints and relaxation.

  • groupRelaxed(node): Return true if the node or any of its upstream input nodes have had any of their search constraints relaxed, false if none have been. See Relaxation for more about constraints and relaxation.

    Note

    The distinction between relaxed() and groupRelaxed() is that relaxed() only tests the specified node, while groupRelaxed() tests the upstream nodes that were part of the specified node's input.

  • plural(node): Returns the Unicode Common Locale Data Repository plural category of cardinal type for this node's value based on pluralization rules for the current locale. You can use this to choose whether and how to pluralize a concept name based on its bound value.

    For languages that have only two forms such as English, only two return values are possible, One or Other. Other languages, though, might return other values: the complete set of possible return values are Zero, One, Two, Few, Many, or Other. You must check the standard for your language to determine its possible values.

    In this example, "restaurant" or "restaurants" is chosen depending on the value of the Restaurant node, which will be One or Other. Note the return values are enums, not strings.

    dialog (Concept) {
    match: Restaurant (this)
    switch (plural(this)) {
    case (One) {
    template (restaurant)
    } default {
    template (restaurants)
    }
    }
    }
    Note

    The plural function cannot pluralize individual values within a multiple-value node. However, you can use the macroEach function to apply plural to each value within such a node.

  • concept.plural(node) and concept.plural('string'): A form of plural() that overrides the pluralization logic built into Bixby's dialog system, explicitly forcing it to choose a specified pluralization, such as "restaurants" vs. "restaurant" as defined in the example above.

    You can pass either a string that corresponds to one of the enumerated values listed for plural(), or a value to use as the basis for pluralization.

    // the current locale's "Many concepts" pluralization, or the
    // closest analogue if "Many"'" does not apply to that locale
    template ("#{value(concept.plural('Other'))}")

    // the current locale pluralization for "2 concepts"
    template ("#{value(concept.plural(2))}")

    You can use this form to force one concept to take on the pluralization of another concept. For instance, a restaurant search capsule could have both a Restaurant model and a RestaurantStyle model with styles like pub, taqueria, cafe, and so on. To choose a pluralization for RestaurantStyle based on the value of Restaurant, you could write:

    template ("Searching for #{concept(restaurantStyle.plural(plural(restaurant)))}...")
  • contextual(node): Returns true if the node is copied over from a previous request, preserving the earlier request's context.

    If a user asks a capsule "find sushi restaurants in Miami" and then follows up with "what about Chicago", the type of restaurant, "sushi," is contextual: it was given in the previous request. In this example, contextual() is used to handle the case where the continuation search yields no results: the restaurantType is dropped and the search is performed just with the requested location.

      action (MyRestaurantSearch) {
    type (Search)
    collect {
    input (location) {
    type (TheLocation)
    min (Required) max (One)
    }
    input (restaurantType) {
    type (RestaurantType)
    min (Optional) max (One)
    }
    }

    output (Restaurant) {
    on-empty {
    // if we find no results, and the `restaurantType` input is
    // `contextual`, drop it and just search by location
    if (contextual (restaurantType)) {
    drop (restaurantType)
    }
    }
    }
  • state(node): Returns the execution state of this node, one of Pre (the node has not yet been evaluated), Mid (the node is currently being evaluated), or Post (the node has already been evaluated). This can be used to test or indicate Bixby's processing state. For instance, in an action that computes the distance between two points, state() could be used to customize dialog to indicate current progress:

    dialog (Action) {
    match: Distance (this) {
    from-output: GetDistance (action)
    }
    if (state(this) == 'Pre') {
    template ("compute the distance[ between #{value (action.point)} and #{value (action.relativePoint)}][ in #{value (action.unit.plural(2))}]")
    } else-if (state(this) == 'Mid') {
    template ("calculating the distance[ between #{value (action.point)} and #{value (action.relativePoint)}][ in #{value (action.unit.plural(2))}]")
    }
    }
  • pre(node), mid(node), post(node): Return a copy of the node with the evaluation state set to Pre (the node has not yet been evaluated), Mid (the node is currently being evaluated), or Post (the node has already been evaluated). This can be used to affect Bixby's processing state. For instance, a No Result dialog event might use pre(this) on an action node to reset its evaluation state. Some default templates for dialog events use pre():

    // default for dialog (NoResult)
    template ("I couldn't #{action(pre(this))}.")
  • typeOf(node): Return the type of the node. The return value is the fully qualified node name: in a capsule with a capsule ID of example.shoe, the Accessory model has a fully qualified name of example.shoe.Accessory.

  • cast(node, type), node.cast(type): Return a copy of the node cast to the specified type.

    computed-input (message) {
type (message.MessageInfo)
min (Required)
max (One)
compute {
intent {
goal: message.ComposeMessage
value: $expr (phoneNumber.cast('message.PhoneNumber')) //I am casting this because the end user capsule might not want to extend message.PhoneNumber
value: $expr (recipientName.cast('message.RecipientName')) //I am casting this because the end user capsule might not want to extend message.RecipientName
value: $expr (messageText)
}
}
}

View cf05aad on GitHub

Special EL Variables

There are several variables available in EL that can be used to access contextual information about the user and device.

Note

These variables are read-only. You cannot modify these variables to change user information, environment information, or sorting.

  • $handsFree: set to true if the device is operating in hands-free mode.

  • $sortOrder and $sortType: provides information about the current action's sorting. The sort order and type could be specified within the action or by the ordering of concepts being passed to the action. These variable is intended to be used if your action is handling sorting output internally. See sort and sort-orderings for more details.

    • $sortOrder: set to one of ascending, descending, or unspecified
    • $sortType: set to one of natural, compound, binding, explicit, or undefined.
  • $user: contains several properties with more information about the user and/or device:

    • $user.currentLocation: the device's current location, as a GeoPoint. Access to this variable requires the device-location-access permission; if your capsule does not request it or the user does not grant access, this will be null.
    • $user.timeZoneId: the device's current timezone. This may be null if the timezone is not set.
    • $user.locale: contains the structure returned by getLocale() for the device's current locale (for example, $user.locale.country). See User and Device Information Functions for the complete list. Note that this is distinct from $can.locale, below, which is derived from the capsule target identifier string.
    • $user.is24HourFormat: boolean indicating whether the device is set to display time in 24-hour format (true) or AM/PM format (false).
  • $can: contains several properties with more information about the environment, all derived from the capsule target identifier string:

    • $can.id: the identifier string for the device Bixby is running on. This is the same as the target string set in the simulator, and begins with a bixby prefix, followed by a device identifier such as mobile, followed by a locale. Examples: bixby-mobile-en-US or bixby-watch-ko-KR.
    • $can.device: the device identifier string without the locale. Examples: bixby-mobile or bixby-watch.
    • $can.locale: the current locale's identifier string with no device information. Examples: en-US or ko-KR.
    • $can.language: just the two-character language identifier. Examples: en or ko.

These EL variables can be used, for example, as computed inputs to actions. Suppose you wanted to set a concept's value to the user's current location. You could set a myLocation concept to that value by creating an action file that takes this computed-input block:

computed-input (myLocation) {
min (Required) max(One)
type (geo.CurrentLocation)

compute {
if (exists($user.currentLocation)) {
intent {
goal: geo.CurrentLocation
value-set: geo.CurrentLocation { $expr($user.currentLocation) }
}
}
}
}