339 lines
15 KiB
Markdown
339 lines
15 KiB
Markdown
---
|
|
layout: documentation
|
|
title: Widget Expressions & Variables
|
|
---
|
|
|
|
# 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](https://github.com/6utt3rfly/jse-eval).
|
|
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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator), 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](#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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math))
|
|
- The JavaScript `Number` object (see [mdn web docs: Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number))
|
|
- The JavaScript `JSON` object to parse or produce JSON (see [mdn web docs: JSON](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON))
|
|
- `dayjs` to build instances of the [day.js library](https://day.js.org/docs/en/parse/now) 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`](https://developer.mozilla.org/en-US/docs/Web/API/Screen) 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()`](css-pages-widgets.html) 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:
|
|
|
|
```javascript
|
|
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.
|
|
|
|
```yaml
|
|
- 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:
|
|
|
|
```yaml
|
|
- 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:
|
|
|
|
```javascript
|
|
footer: =@'Switch1'
|
|
```
|
|
|
|
is the same as
|
|
|
|
```javascript
|
|
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:
|
|
|
|
```javascript
|
|
footer: =#'Temperature1'
|
|
```
|
|
|
|
is the same as
|
|
|
|
```javascript
|
|
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:
|
|
|
|
```text
|
|
[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:
|
|
|
|
```javascript
|
|
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 (<code>\`string template\`</code>) instead of single- or double-quotes.
|
|
Inside string templates, variable values can be inserted with the `${variable}` syntax.
|
|
|
|
Example:
|
|
|
|
```javascript
|
|
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:
|
|
|
|
```javascript
|
|
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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions).
|
|
When creating a regex, consider using tools like [regex101](https://regex101.com) 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:
|
|
|
|
```yaml
|
|
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.
|
|
|
|
```yaml
|
|
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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator), 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`).
|
|
|
|
```javascript
|
|
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.
|
|
|
|
```javascript
|
|
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:
|
|
|
|
```yaml
|
|
- 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:
|
|
|
|
```yaml
|
|
- 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":
|
|
|
|
```javascript
|
|
=(@@'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:
|
|
|
|
```javascript
|
|
icon: =(@@props.item === 'ON') ? 'f7:lightbulb_fill' : 'f7:lightbulb'
|
|
```
|
|
|
|
Stacked ternary statements to translate the state of Item `xxx` to a description:
|
|
|
|
```javascript
|
|
=(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:
|
|
|
|
```javascript
|
|
={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"):
|
|
|
|
```javascript
|
|
=dayjs(items.DateItem.state).subtract(1, 'week').fromNow()
|
|
```
|
|
|
|
## Debugging Expressions
|
|
|
|
Expressions can be tested in the Widgets Expression Tester found in the [Developer Sidebar]({{base}}/mainui/developer/sidebar.html#developer-sidebar)
|
|
(<kbd>Shift</kbd><kbd>Alt</kbd><kbd>D</kbd>).
|