NewDash Dots are hereMini apps in Dash, with payments built inLearn more

Products · Developer guides

Expressions

Compare, calculate and choose values inside $(...) with operators and built-in functions.

Anything inside $(...) that is more than a plain reference is an expression. Expressions use the Apache Commons JEXL syntax. They only compute a value: they can't loop, assign or change anything.

$(rc.display_name)                               a plain reference
$(page.enter_amount.value * 2)                   a number
$(page.enter_amount.value >= 100)                true or false
$(strings.upper(rc.display_name))                text
$(rc.is_member == 'true' ? 'Welcome back' : 'Join today')

A $(...) is a plain reference when it starts with page., session., data., rc. or sc. and has only letters, digits, dots and underscores. Anything else is read as an expression. For where you can use $(...), see Variables.

Operators

KindOperatorsExample
Arithmetic+, -, *, /, %$(page.ticket_count.value * 50)
Comparison==, !=, <, <=, >, >=$(page.enter_amount.value >= 100)
Logic&&, ||, !$(rc.is_member == 'true' && !strings.is_blank(sc.chosen_term))
Choice? :$(rc.tier == 'gold' ? 'Gold' : 'Standard')
Default for null??$(rc.display_name ?? 'friend')
Pattern match=~, !~$(page.phone.value =~ '232[0-9]{8}')
Join text+$('Hi ' + rc.display_name)

=~ checks the whole value against the pattern, not part of it. To check how a value starts, use strings.has_prefix.

Built-in Functions

Numbers

FunctionReturnsExample
max(a, b)The larger number$(max(page.enter_amount.value * 1, 10))
min(a, b)The smaller number$(min(page.enter_amount.value * 1, 10000))
abs(a)The number without its sign$(abs(sc.balance_change * 1))

These take numbers. Values read with $(...) are text, so multiply them by 1 first, as above.

strings

FunctionReturnsExample
strings.upper(s)s in capitals$(strings.upper(page.code.value))
strings.lower(s)s in small letters$(strings.lower(page.code.value))
strings.title(s)Each word capitalised$(strings.title(page.enter_name.value))
strings.truncate(s, n)The first n characters plus ..., if s is longer than n$(strings.truncate(rc.display_name, 20))
strings.is_blank(s)true if s is unset or empty. Spaces count as text.$(strings.is_blank(rc.display_name))
strings.has_prefix(s, p)true if s starts with p$(strings.has_prefix(page.phone.value, '23276'))
strings.has_suffix(s, p)true if s ends with p$(strings.has_suffix(page.meter_number.value, '01'))
strings.contains(s, p)true if p is anywhere in s$(strings.contains(page.enter_name.value, 'Kamara'))
strings.split_index(s, d, i)The piece at position i (from 0) after splitting s on d, or null$(strings.split_index(rc.display_name, ' ', 0))

datetime

FunctionReturnsExample
datetime.now()The current time in UTC, as an ISO 8601 timestamp$(datetime.now())
datetime.extract(t, part)One part of timestamp t as a number. Pass null for now.$(datetime.extract(null, 'HH'))

part is one of:

  • yyyy year
  • MM month, 1 to 12
  • d day of the month
  • E day of the week, 1 (Monday) to 7 (Sunday)
  • HH hour, 0 to 23
  • mm minute
  • ss second

Any other value stops the flow with an error.

subscriber

FunctionReturnsExample
subscriber.is_one_of(list)true if the subscriber's number is in list, a comma-separated list of numbers, each starting with +232$(subscriber.is_one_of('+23276000001,+23277000002'))

Use it to run a page only for test phones:

JSON
{
  "id": "mark_test_phone",
  "component": { "type": "side_effect" },
  "options": {
    "skip": "$(!subscriber.is_one_of('+23276000001,+23277000002'))"
  },
  "on_done": {
    "action": "next",
    "session_context": { "mutations": { "is_test_phone": true } }
  }
}

Text, Numbers and Missing Values

  • Every value read with $(...) is text, even one you saved as a number or true.
  • Arithmetic turns number-like text into numbers: page.enter_amount.value * 2 works.
  • + joins two pieces of text, so page.fee.value + page.tax.value gives 5010 for 50 and 10. Multiply one side by 1 to add them: page.fee.value * 1 + page.tax.value.
  • To test a saved true, compare with text: rc.is_member == 'true'.
  • An rc or sc key that was never saved is null. Give it a default with ?? before you compare or calculate with it: (rc.lifetime_spend ?? 0) < 500.
  • Empty text counts as false. Use strings.is_blank(x) to test for "unset or empty".

Missing page, data and session values behave differently; see Missing Values.

Example: Tiered Menu

A menu that offers more options as a subscriber spends more. Your server, or another page, keeps rc.lifetime_spend up to date.

JSON
{
  "id": "select_option",
  "component": {
    "type": "menu",
    "label": "Welcome $(rc.display_name ?? 'friend'). Pick an option:",
    "items": [
      {
        "label": "Usual (SLE $(rc.usual_amount ?? 50))",
        "value": "$(rc.usual_amount ?? 50)"
      },
      {
        "label": "Premium (SLE 100)",
        "value": "100",
        "hide": "$((rc.lifetime_spend ?? 0) < 500)"
      },
      {
        "label": "Gold (SLE 250)",
        "value": "250",
        "hide": "$((rc.lifetime_spend ?? 0) < 2000)"
      }
    ]
  },
  "on_done": {
    "action": "next",
    "recurring_context": {
      "mutations": { "usual_amount": "$(page.select_option.value)" }
    }
  }
}

A new subscriber sees only "Usual (SLE 50)". Premium appears once lifetime spend reaches SLE 500, and Gold at SLE 2000. The amount chosen becomes the next visit's "Usual".

Limitations

Expressions can't:

  • loop (for, while)
  • create variables (var x = ...)
  • define functions (x -> ...)
  • create objects (new)
  • change any value. Saving happens only through on_done mutations.
  • index a list with a computed position (list[i])

When you need any of these, do the work on your server through an exchange and read the result with $(data.KEY).

Keep reading