This page is part of archived documentation for openHAB 4.3. Go to the current stable version

# Widget Expressions & Variables

When designing pages and widgets, you might want a config property of a widget (on pages) or of components (inside widgets) to be dynamically updated. - Expressions allow you to that.

Variables are a way to allow more complex scenarios in pages & personal widget development.

The widget expression system uses a JavaScript-like expression parser, jse-eval (opens new window). In order to remain light-weight and responsive, this is not a complete JavaScript library, but nearly all the basic functionality is provided along with some more advanced features.

# Expressions Overview

Expressions are string literals beginning with the symbol = and everything after it is evaluated using a syntax very similar to JavaScript. You can use arithmetic or string operations etc., the conditional (ternary) operator (opens new window), as well as the following objects (subject to evolutions):

  • items is a dynamic key/value dictionary allowing you to retrieve the state of any Item: The result of items.Item1 will be an object like { state: '23', displayState: '23 °C', numericState: 23, unit: '°C', type: 'Decimal' } (displayState, numericState and unit may be omitted). You can therefore use items.Item1.state to use the current state of Item1 in your expression, and if the state changes, the expression will be reevaluated.
  • props is a dictionary of the key/values of self-defined props for the current personal widget, or page (pages, like any root UI components, may indeed have props). It is indispensable to use props in expressions when developing a personal widget to pass configuration from the page to the widget.
  • config is a dictionary of the key/values of the configuration of the current component/widget.
  • vars is a dictionary of variables that are available in the component's context
  • loop is a dictionary containing iteration information when you're repeating components from a source collection. It is defined only when in the context of an oh-repeater component.
  • The JavaScript Math object (so you can use Math.floor(...), Math.round(...) and the like) (see mdn web docs: Math (opens new window))
  • The JavaScript Number object (see mdn web docs: Number (opens new window))
  • The JavaScript JSON object to parse or produce JSON (see mdn web docs: JSON (opens new window))
  • dayjs to build instances of the day.js library (opens new window) that you can use to parse or manipulate date & time
  • theme which holds the current theme: ios, md or aurora
  • themeOptions and device allow to use the relevant objects that you can see in the About page, Technical information, View details, under clientInfo
  • screen returns the Screen (opens new window) object. This allows you to access various information about the current screen, e.g. the available width and height. The two properties viewAreaWidth and viewAreaHeight are added on top. It's recommended to use CSS calc() for dynamic positioning and styling.
  • user returns an object with information about the logged-in user: The name (user.name) and an array of the assigned roles for the user (user.roles).

# Variables

Variables can be used in several ways:

  • The variable config parameter of an oh-gauge (read-only), oh-input, oh-knob, oh-slider, oh-stepper, oh-toggle will accept a variable name and control it instead of sending commands to an Item if set. The item parameter can still be set to set the widget to the Item state if the variable has no value.
  • The vars object available in expressions (as mentioned above). For example =vars.var1 will evaluate to the value of the variable var1.
  • The variable action allows to set a fixed or computed (using an expression) value to a variable.

oh-button & oh-link have a special parameter clearVariable, which allows to unset a variable when clicked, after performing the action. This is useful when "validating" a variable, e.g. send the variable value as command to an Item and then reset the variable.

# Default Variable Values

The standard variables defined using interactive components do not exist until the first time they are given a value by a component. This means that it is not possible to set a default value for a variable. To provide a default value when a variable does not exist a simple OR construction can be used in expressions that require the variable:

text: =(vars.selectedNumber || '0')

Expressions such as the example above will return the value of the variable if it exists and if not will return the second value instead.

If a variable is used extensively in a widget, instead of using OR statements in many different expressions, an oh-context component can be used to define variables that have default value at widget creation.

- component: oh-context
  config:
    variables:
      setBrightness: 75
  slots:
    default:
      - component: oh-slider
        config:
          variable: setBrightness
      - component: Label
        config:
          text: =vars.setBrightness

The oh-context can also create more complex default variable values such as arrays or objects:

- component: oh-context
  config:
    variables:
      lightArray:
        - ON
        - OFF
        - OFF
        - OFF
      userObject:
        user: Guest
        color: red
        timeout: 5

# Variable Scope

Standard variables are also global in scope with one exception:

Changes to the variable's value made by the child components do not affect the parent component that defines the variable.

The scope of variables created using an oh-context component is restricted to only the children of that component. The value of an oh-context variable will never pass to the parent of the oh-context. Within the scope of an oh-context, however, variables are fully accessible even passing values from a custom widget to parent components.

Variables defined in an oh-context will take precedence over standard variables of the same name.

# Item Expression Shortcuts

The @ symbol can be used in front of an Item name string as a shortcut to the displayState from the items dictionary with a fallback to the raw state:

footer: =@'Switch1'

is the same as

footer: =items['Switch1'].displayState || items['Switch1'].state

Similarly, @@ can be used as a shortcut for just the Item state.

The hashtag # symbol is a shortcut to the numericState with no fallback:

footer: =#'Temperature1'

is the same as

footer: =items['Temperature1'].numericState

These shortcuts have two major benefits over directly accessing displayState, state and numericState: Expressions become shorter when they are used, and if the Item name is a prop which is undefined, these shortcuts avoid that a request with Item name undefined is sent to the server, which would cause this log message:

[WARN ] [se.internal.SseItemStatesEventBuilder] - Attempting to send a state update of an item which doesn't exist: undefined

Expressions are particularly useful in cases where one wants to combine the states of more than one Item, or use the state of more than one Item in a single widget element. For example, the icon of an Item can be based on the state of a different Item.

# Advanced Expression Features

# Arrow functions

Many standard JavaScript methods take a function as a parameter. The expression parser can parse arrow functions as the parameters of these methods.

Example:

title: =someItemList.find((x) => x.name === 'KitchenSwitch').label

In this example, an arrow function is used in conjunction with the .find() method to get the Item object from an array of Items (such as is returned by a oh-repeater) with a particular Item name. The label of the found Item is then used as the title of a component.

# String templates

String templates are a much more human-readable way of creating strings with incorporated dynamic values. String templates are surrounded by backticks (`string template`) instead of single- or double-quotes. Inside string templates, variable values can be inserted with the ${variable} syntax.

Example:

text: =`This button opens the ${props.page} page`

This example includes the value of the widget property props.page in the text of a component.

# Regular expressions

Regular expressions (regex) allow for complex search or replace string operations. Many of the JavaScript string methods accept regex parameters expressed as the regex string between two forward slashes (/regex here/).

Example:

label: =props.item.match(/_(.*)_/)[1]

In this example, a widget property named item and containing an Item name is searched using regex and the first capture (in this case all characters between two underscores) is returned and used as the component label.

To learn more about regular expressions (regex), refer to mdn web docs: Regular expressions (opens new window). When creating a regex, consider using tools like regex101 (opens new window) to test your regex.

# Objects

The variable action allows components in widgets to pass information back and forth when there is user interaction. Often this information is simple, such as a single string or input value. Sometimes, however, it is helpful to add more information to a variable and for these instances JavaScript objects are useful.

The widget expression system can create objects in two different ways:

  • JavaScript object syntax:

    Objects can be defined within the expression system using the standard JavaScript syntax: {'key1':'value1','key2':'value2'}. If a key doesn't contain special characters such as spaces, the quotes around keys can usually be omitted: {key1:'value1',key2:'value2'}.

    TIP

    Due to the special meaning of :[space] in yaml, it is best to have no spaces between the : and the value. If you have :[space] anywhere in your expression it will raise a YAML error unless you enclose the entire expression (= included) in another layer of quotes.

    Example:

    actionVariable: myObject
    actionVariableValue: ={'name':props.item,'selected':true}
    

    In this example, a variable is set to an object with keys name and selected using JavaScript object expression syntax.

  • YAML object syntax:

    The other way to create objects is to take advantage of the relationship between YAML and JSON and place the key:value pairs as YAML keys under the initial key.

    Here is a variable definition with the same results as the one above using the YAML syntax.

    actionVariable: myObject
    actionVariableValue:
      name: =props.item
      selected: =true
    

In both cases, the variable can now be referenced by other components as vars.myObject with keys vars.myObject.name and vars.myObject.selected.

# Using object expressions like a switch control statement

Object expression can also be used to simulate a switch control statement.

The most common flow control statement in expressions is the conditional (ternary) operator (opens new window), which is very efficient for selecting from two options based on a single boolean criterion. If you have a list of possible options, you can combine multiple ternary operators together, but this grows cumbersome very quickly. For example, if there is an HVAC with a mode Item that can be set to heat, cool, auto, and off modes, it requires 4 nested ternary operators to set a component's background color to match the current HVAC mode (with a fallback option if the Item has some other state, e.g. null).

background: =(@@hvacModeItem == 'heat')?'orange':(@@hvacModeItem == 'cool')?'blue':(@@hvacModeItem == 'auto')?'green':(@@hvacModeItem == 'off')?'white':'red'

To use an object instead, simply create an object with keys for each of the Item's expected states, and give each key the desired output value. Referencing that object using the Item's state will return the desired value. Adding a simple OR statement to that expression will provide the fallback condition if the object reference is undefined.

background: =({heat:'orange',cool:'blue',auto:'green',off:'white'})[@@hvacModeItem] || 'red'

# Constants

Named constants can be defined using an oh-context component in a similar way to defining variables. The significant difference is that if a constant's value is defined using an expression, that expression is evaluated only at the time the widget is rendered and will not change.

The example above using an object as a switch statement can be written even more clearly with the addition of a constant object:

- component: oh-context
  config:
    constants:
      modeColor:
        heat: orange
        cool: blue
        auto: green
        off: white
  slots:
    default:
      - component: Label
        config:
          text: =@@hvacModeItem
          style:
            background: =const.modeColor[@@hvacModeItem] || 'red'

# Custom Functions

There are times when a widget requires the same calculation in multiple locations and maintaining all the different locations can be a burden. In these instances, an oh-context component can be used to define named functions that are available to all children of the oh-context. Functions are defined using the arrow syntax and referenced with the fn object in expressions:

- component: oh-context
  config:
    functions:
      num2usd: =(x) => '$' + Number.parseFloat(x).toFixed(2)
  slots:
    default:
      - component: Label
        config:
          text: =fn.num2usd(3.1)

# Examples

Translates the third part of the HSB state (brightness) of an Color Item to "On" or "Off":

=(@@'Color1'.split(',')[2] !== '0') ? 'On ' + '(' + @@'Color1'.split(',')[2] + '%)' : 'Off'

Use a filled lightbulb icon but only if the state of the Item passed in the prop item is ON:

icon: =(@@props.item === 'ON') ? 'f7:lightbulb_fill' : 'f7:lightbulb'

Stacked ternary statements to translate the state of Item xxx to a description:

=(items.xxx.state === '0') ? 'Off' : (items.xxx.state === '1') ? 'Heat' : (items.xxx.state === '11') ? 'Economy Heat' : (items.xxx.state === '15') ? 'Full Power': (items.xxx.state === '31') ? 'Manual' : 'Not Set'

Do the same using an object and the Item state shortcut:

={0:'Off',1:'Heat',11:'Economy Heat',15:'Full Power',31:'Manual'}[@@xxx] || 'Not Set'

Subtract one week from the state of DateTime and return a relative time representation in the current locale ("3 weeks ago"):

=dayjs(items.DateItem.state).subtract(1, 'week').fromNow()

# Debugging Expressions

Expressions can be tested in the Widgets Expression Tester found in the Developer Sidebar (ShiftAltD).