Consolidate widget expression and css docs into their own pages (#2189)

* Add new pages for CSS and widget expressions

Signed-off-by: Florian Hotze <florianh_dev@icloud.com>

* Merge widget expression docs into a single page

Signed-off-by: Florian Hotze <florianh_dev@icloud.com>

* Fix links

Signed-off-by: Florian Hotze <florianh_dev@icloud.com>

* Move CSS section to its own page

Signed-off-by: Florian Hotze <florianh_dev@icloud.com>

* Minor typo

Signed-off-by: Florian Hotze <florianh_dev@icloud.com>

* Address review

Signed-off-by: Florian Hotze <florianh_dev@icloud.com>

---------

Signed-off-by: Florian Hotze <florianh_dev@icloud.com>
pull/2191/head
Florian Hotze 2023-12-27 21:22:39 +01:00 committed by GitHub
parent 005217a522
commit 44fe6b46cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 318 additions and 268 deletions

View File

@ -145,7 +145,7 @@ This will open a form with customization options.
##### Basic Settings
Instead of using the Item's Label and parent Group as the Title and Subtitle of the card, these can be overridden and manually set.
[Expressions]({{base}}/ui/building-pages.html#dynamically-configuring-components-with-expressions) can be used to make the Title and Subtitle change based on the states of Items or other conditions.
[Expressions]({{base}}/ui/widget-expressions-variables.html) can be used to make the Title and Subtitle change based on the states of Items or other conditions.
By default a background color is chosen based on the semantic tag.
This default can be overridden here.

View File

@ -16,7 +16,7 @@ One can also set the "Default Stand Alone Widget" and "Default Cell Widget" to c
Many portions of a widget can be configured to change dynamically based on the states of Items.
This can be a powerful way to combine multiple Items into one widget (e.g. an oh-label widget showing the current state of a garage door that sends a command to a Switch Item to trigger the garage door opener when the widget is clicked).
Common things one might use an expression for are to change an icon or color based on the state of an Item, to hide a widget entirely if an Item isn't in a given state, or to change the colors of the widget elements.
For full details on expressions see the [Expressions docs]({{base}}/ui/building-pages.html#dynamically-configuring-components-with-expressions).
For full details on expressions see the [Expressions docs]({{base}}/ui/widget-expressions-variables.html).
Note that when working with Units of Masurement, the state of the Item needs to be parsed into a number for comparisons.
For example -

View File

@ -111,7 +111,7 @@ However, it's important to know that there are limitations and sometimes editing
Sometimes it will be indicated somewhere when configuring the widget, or in the openHAB documentation itself, on the other hand some options won't be available for use (for instance, because they expect a callback function and you cannot define those in the widget's config) or need some transformation.
1. Sometimes you'll want to use an expression to configure the property, but the UI will get in your way - for instance, it will display an item picker while your intention is to set the prop value to be `=props.item1`.
See below to learn more about [expressions](#dynamically-configuring-components-with-expressions).
[Learn more about widget expressions.](widget-expressions-variables.html)
1. To quickly and efficiently duplicate similar widgets with only a few differences, it is always way easier to copy/paste the relevant YAML in the editor.
@ -119,12 +119,11 @@ However, it's important to know that there are limitations and sometimes editing
Besides, there are several options that virtually all widgets in layout pages, map pages and plan pages accept, all of which are not currently available in the config sheet:
- `visible`: you can specify a `false` boolean to this option to hide the widget. This powerful feature, combined with [expressions](#dynamically-configuring-components-with-expressions), allows you to dynamically show widgets or even entire sections (if you use it on layout widgets containing other widgets), depending on the state of your items
- `visible`: you can specify a `false` boolean to this option to hide the widget. This powerful feature, combined with [widget expressions](widget-expressions-variables.html), allows you to dynamically show widgets or even entire sections (if you use it on layout widgets containing other widgets), depending on the state of your items
Example: `visible: =items.TV_Powered.state === 'ON' && items.TV_Input.state === 'HDMI1'`
- `visibleTo`: this accepts an array of strings like `role:administrator`, `role:user`, or `user:<userid>`, allowing the widget to be only visible to specific users or those with a certain role.
Example: `visibleTo: ["user:scott", "role:administrator"]`
- `class` and `style` are [standard Vue.js attributes](https://vuejs.org/v2/guide/class-and-style.html) and can be used to either alter the CSS classes or add inline styling to the component.
[See "Styling" below](#techniques-for-styling-widgets).
- `class` and `style` are [standard Vue.js attributes](https://vuejs.org/v2/guide/class-and-style.html) and can be used to either alter the CSS classes or add inline styling to the component. Please refer to [Styling pages & widgets using CSS](css-pages-widgets.md).
### Types of Widgets
@ -144,70 +143,9 @@ See the [Component Reference](./components/) for details about the different lib
## Dynamically Configuring Components with Expressions
Virtually everywhere every time you need a config prop to be dynamically updated, you can use an expression to configure it.
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):
To dynamically configure components based on data changed on runtime, expressions can be used.
- `items` is a dynamic key/value dictionary allowing you to retrieve the state of items; the result of `items.Item1` will be an object like `{ state: '23', displayState: '23 °C' }` (`displayState` may be omitted). You can therefore use `items.Item1.state` to use the current state of Item1 in your expression, if it changes, it 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
- `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)
- 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
- `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()`](#dynamic-styling--positioning-using-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`).
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:
```yaml
footer: =@'Switch1'
```
is the same as
```yaml
footer: =items['Switch1'].displayState || items['Switch1'].state
```
Similarly, `@@` can be used as a shortcut for just the item state.
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.
### Examples
```js
=(items.Color1.state.split(',')[2] !== '0') ? 'On ' + '(' + items.Color1.state.split(',')[2] + '%)' : 'Off'
```
Translates the third part of the HSB state (brightness) of the Color1 item to On or Off.
```js
icon: =(items[props.item].state === 'ON') ? 'f7:lightbulb_fill' : 'f7:lightbulb'
```
Use a filled icon of a lightbulb but only if the state of the items passed in the prop `item` is ON.
```js
= (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'
```
Stacked ternary statements to translate a value to a description.
```js
=dayjs(items.DateItem.state).subtract(1, 'week').fromNow()
```
Substracts one week from the state of `DateTime` and return a relative time representation in the current locale ("3 weeks ago").
### Debugging Expressions
Expressions can be tested in the Widgets Expression Tester found in the Developer Sidebar
(<kbd>Shift+Alt+D</kbd>).
Please refer to [widget expressions](widget-expressions-variables.html) for more information.
## Actions
@ -310,109 +248,3 @@ slots:
</details>
:::
## Variables
Variables are a way to allow more complex scenarios in pages & personal widget development.
Variables can be used using several methods:
- 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 items if set.
The "item" parameter can
still be set to set the widget to the item's state, when
the variable has no value.
- the `vars`object available in expressions (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 version when clicked, after performing
the action.
This is useful when "validating" a variable e.g.
send a command to an item with the variable value then reset it.
## Techniques for Styling Widgets
### Predefined CSS Classes
As seen before, you can use CSS classes in the `class` property (as an array) or set CSS properties in the `style` property (as a dictionary) on your components.
You cannot define new CSS classes, but you can use classes from Framework7, for instance:
- [Typography](https://v5.framework7.io/docs/typography.html)
- [Color Themes](https://v5.framework7.io/docs/color-themes.html#apply-color-themes)
- [Hairlines](https://v5.framework7.io/docs/hairlines.html)
- [Elevation](https://v5.framework7.io/docs/elevation.html)
### CSS Variables
Another interesting technique is to override the many [CSS Variables](https://v5.framework7.io/docs/css-variables.html) defined by the framework to suit your particular component's needs.
The override will be applied to all descendants in the HTML DOM tree, in case of the Framework7 variables the underlying components which use them will use the new value.
It is recommended to use Framework7 CSS variables in your components too, when appropriate, so that way you'll be sure the properties change according to the current theme and dark mode setting.
To redefine a CSS variable for the component and its descendants, use this syntax:
```yaml
style:
--f7-button-border-color: rgb(255, 0, 0)
```
To reuse a CSS variable, use:
```yaml
border-color: var(--f7-button-border-color)
```
You can even define your own CSS variables and use them in your components:
```yaml
config:
style:
--my-color: =props.color
slots:
...
...
...
config:
style:
background-color: var(--my-color)
```
### Applying CSS Properties Directly
Applying CSS properties like `border-color` directly on components is sometimes enough; but contrary to CSS variables like `--f7-button-border-color` which will be inherited to descendants in the tree, either by your own components or by f7 components (or their OH derivatives) that make use of these variables, they will only work on the element where you put the style configuration.
There are hundreds of [CSS properties](https://www.w3schools.com/cssref/) you can use to design your widgets.
Use the resources at [W3Schools](https://www.w3schools.com/css/default.asp) or the [CSS-Tricks Properties Almanac](https://css-tricks.com/almanac/properties/) to learn more about CSS properties and techniques - these resources will provide code in HTML and classes definitions but you can most of the time adapt them for usage in components' YAML definitions.
While the Layout components (`oh-block`, `oh-grid-row`, `oh-grid-col`) can help you with the placement of your widgets, to lay out sub-components _within_ a widget, you shouldn't use them because they include design mode controls that you don't need.
While can use their `f7-block`, `f7-row` and `f7-col` equivalents instead, in many cases this is still "overkill": consider applying directly the Flexbox or Grid properties to the components.
These resources will help you with Flexbox and Grid:
- [A Complete Guide to Flexbox](https://css-tricks.com/snippets/css/a-guide-to-flexbox/)
- [justify-content "Play it"](https://www.w3schools.com/cssref/playit.asp?filename=playcss_justify-content&preval=flex-start) and others found in the reference
- [A Complete Guide to Grid](https://css-tricks.com/snippets/css/complete-guide-grid/)
- [Grid Tutorial on W3Schools](https://www.w3schools.com/css/css_grid.asp)
### Dynamic Styling & Positioning using CSS `calc()`
You can dynamically style and position elements by calculating their CSS properties with the `calc()` function.
The `calc()` function is able to perform math (`+`, `-`, `*` & `/`) on multiple CSS values, which can even have different units.
For example, to set the height of a component to the current page's maximum content height (without scrolling), use the following `calc()` statement:
```css
calc(96vh - var(--f7-navbar-height) - var(--f7-toolbar-height))
```
This subtracts the height of the navbar and the toolbar, which are stored in CSS vars, from 96% of the viewport's height.
These resources will help you with `calc()`:
- [mdn web docs_: calc()](https://developer.mozilla.org/en-US/docs/Web/CSS/calc)
- [CSS-Tricks: A Complete Guide to calc() in CSS](https://css-tricks.com/a-complete-guide-to-calc-in-css/)

87
ui/css-pages-widgets.md Normal file
View File

@ -0,0 +1,87 @@
---
layout: documentation
title: Styling pages & widgets using CSS
---
# Styling pages & widgets using CSS
## Predefined CSS Classes
As seen before, you can use CSS classes in the `class` property (as an array) or set CSS properties in the `style` property (as a dictionary) on your components.
You cannot define new CSS classes, but you can use classes from Framework7, for instance:
- [Typography](https://v5.framework7.io/docs/typography.html)
- [Color Themes](https://v5.framework7.io/docs/color-themes.html#apply-color-themes)
- [Hairlines](https://v5.framework7.io/docs/hairlines.html)
- [Elevation](https://v5.framework7.io/docs/elevation.html)
## CSS Variables
Another interesting technique is to override the many [CSS Variables](https://v5.framework7.io/docs/css-variables.html) defined by Framework7 to suit your particular component's needs.
The override will be applied to all descendants in the HTML DOM tree, in case of the Framework7 variables the underlying components which use them will use the new value.
It is recommended to use Framework7 CSS variables in your components too, when appropriate, so that way you'll be sure the properties change according to the current theme and dark mode setting.
To redefine a CSS variable for the component and its descendants, use this syntax:
```yaml
style:
--f7-button-border-color: rgb(255, 0, 0)
```
To reuse a CSS variable, use:
```yaml
border-color: var(--f7-button-border-color)
```
You can even define your own CSS variables and use them in your components:
```yaml
config:
style:
--my-color: =props.color
slots:
...
...
...
config:
style:
background-color: var(--my-color)
```
## Applying CSS Properties Directly
Applying CSS properties like `border-color` directly on components is sometimes enough; but contrary to CSS variables like `--f7-button-border-color` which will be inherited to descendants in the tree, either by your own components or by f7 components (or their OH derivatives) that make use of these variables, they will only work on the element where you put the style configuration.
There are hundreds of [CSS properties](https://www.w3schools.com/cssref/) you can use to design your widgets.
Use the resources at [W3Schools](https://www.w3schools.com/css/default.asp) or the [CSS-Tricks Properties Almanac](https://css-tricks.com/almanac/properties/) to learn more about CSS properties and techniques - these resources will provide code in HTML and CSS class definitions but you can most of the time adapt them for usage in components' YAML definitions.
While openHAB's layout components (`oh-block`, `oh-grid-row`, `oh-grid-col`) can help you with the placement of your widgets, to layout sub-components _within_ a widget, you shouldn't use them because they include design mode controls that you don't need.
Though you can use their `f7-block`, `f7-row` and `f7-col` equivalents instead, in many cases this is still "overkill":
Consider directly applying the Flexbox or Grid properties to the components.
These resources will help you with Flexbox and Grid:
- [A Complete Guide to Flexbox](https://css-tricks.com/snippets/css/a-guide-to-flexbox/)
- [justify-content "Play it"](https://www.w3schools.com/cssref/playit.asp?filename=playcss_justify-content&preval=flex-start) and others found in the reference
- [A Complete Guide to Grid](https://css-tricks.com/snippets/css/complete-guide-grid/)
- [Grid Tutorial on W3Schools](https://www.w3schools.com/css/css_grid.asp)
## Dynamic Styling & Positioning using CSS `calc()`
You can dynamically style and position elements by calculating their CSS properties with the `calc()` function.
The `calc()` function is able to perform math (`+`, `-`, `*` & `/`) on multiple CSS values, which can even have different units.
For example, to set the height of a component to the current page's maximum content height (without scrolling), use the following `calc()` statement:
```css
calc(96vh - var(--f7-navbar-height) - var(--f7-toolbar-height))
```
This subtracts the height of the navbar and the toolbar, which are stored in CSS vars, from 96% of the viewport's height.
These resources will help you with `calc()`:
- [mdn web docs: calc()](https://developer.mozilla.org/en-US/docs/Web/CSS/calc)
- [CSS-Tricks: A Complete Guide to calc() in CSS](https://css-tricks.com/a-complete-guide-to-calc-in-css/)

View File

@ -59,6 +59,7 @@ This is accomplished by adding a `config` section to the component's YAML.
```
Some of the available configuration parameters are specific to a certain component while others, such as `visible` or `class` are available in most components.
[Expressions](widget-expressions-variables.html) can be used to dynamically set configuration parameters depending on dynamic, changing data, e.g. Item states.
### Component slots
@ -102,7 +103,7 @@ There are several subsets of OH components, each with different uses and strengt
In addition to being the basis for the OH components, the F7 components themselves are available as options in the widget editor.
As a general rule, the F7 components will have more configuration and style flexibility than their OH counterparts.
So, their use is recommended when there is something about the component that needs to be configured in a way different than what is set in the OH version.
Of course, the F7 components do not have the OH specific functions available, so while they can have values based on Items using the expression system, they cannot easily be used to trigger rules, update Items or variables, etc.
Of course, the F7 components do not have the OH specific functions available, so while they can have values based on Items using the [widget expression system](widget-expressions-variables.html), they cannot easily be used to trigger rules, update Items or variables, etc.
The most commonly used F7 components will likely be `f7-block`, `f7-row`, and `f7-col`.
These all generate a simple `<div>` element with one base F7 class (`block`, `row`, and `col` respectively).
@ -138,7 +139,7 @@ If, however, there is need for the popup or popover to be built-in with a single
### Label and Content
There are two special components that are not derived from any other specific library, the `Label` and the `Content` component.
These two are similar in their simplicity of configuration, primarily taking only a `text` property (which can be an expression).
These two are similar in their simplicity of configuration, primarily taking only a `text` property (which can be a [widget expression](widget-expressions-variables.html)).
The `Label` component renders the value given by the `text` property inside it's own `<div>` element.
For example:
@ -242,96 +243,8 @@ Renders to the HTML:
</div>
```
## The Expression Syntax
## Styling
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 of the basic function is provided along with some more advanced features.
Personal widgets very likely require some customized styling using CSS.
### 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.
Here an arrow function is used in conjunction with the `.find()` method to locate the item object in an array of items (such as is returned by a `oh-repeater`) with a particular name.
The label of the found item is then used as the title of a component.
```yaml
title: =someItemList.find( (x) => x.name=="KitchenSwitch" ).label
```
#### 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 `${variable}` syntax.
Here the value of the widget property `props.page` is included in the text of a component by a string template.
```yaml
text: =`This button opens the ${props.page} page`
```
#### 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/`).
Here a widget property containing an Item name is searched using regex and the first capture (in this case all characters between two underscores) is returned as a component label.
```yaml
label: =props.item.match(/_(.*)_/)[1]
```
#### Objects
The variable action allows components in widgets to pass information back and forth when there is user interaction.
Often this informtation 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 system can create objects in two different ways.
Objects can be defined within the expression system using the standard JavaScript syntax: `{'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.
:::
Here a variable is set to an object with `name` and `selected` keys using the object expression.
```yaml
actionVariable: myObject
actionVariableValue: ={'name':props.item,'selected':true}
```
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`.
The object expression can also be used to simulate a `switch` control statement.
The most common flow control statement in the expressions is the [conditional (ternary) operator](building-pages.html#dynamically-configuring-components-with-expressions) 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 string 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 fall back option if the item has some other state, e.g. `null`).
```yaml
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 and following that with a simple `OR` statement will provide the fallback condition if the object reference is undefined.
```yaml
background: =({'heat':'orange','cool':'blue','auto':'green','off':'white'})[@@hvacModeItem] || 'red'
```
Please read [CSS for Pages & Widgets](css-pages-widgets.html) to learn more about using CSS.

View File

@ -0,0 +1,218 @@
---
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 of the basic function 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', type: 'Decimal' }` (`displayState` 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 so you can 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 using several methods:
- 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.
## 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:
```js
footer: =@'Switch1'
```
is the same as
```js
footer: =items['Switch1'].displayState || items['Switch1'].state
```
Similarly, `@@` can be used as a shortcut for just the Item state.
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:
```js
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:
```js
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:
```js
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 informtation 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 ommited: `{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 fall back option if the Item has some other state, e.g. `null`).
```js
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.
```js
background: =({heat:'orange',cool:'blue',auto:'green',off:'white'})[@@hvacModeItem] || 'red'
```
## Examples
Translates the third part of the HSB state (brightness) of an `Color` Item to "On" or "Off":
```js
=(@@'Color1'.split(',')[2] !== '0') ? 'On ' + '(' + @@'Color1'.split(',')[2] + '%)' : 'Off'
```
Use a filled icon of a lightbulb but only if the state of the Item passed in the prop `item` is ON:
```js
icon: =(@@props.item === 'ON') ? 'f7:lightbulb_fill' : 'f7:lightbulb'
```
Stacked ternary statements to translate the state of Item `xxx` to a description:
```js
=(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:
```js
={0:'Off',1:'Heat',11:'Economy Heat',15:'Full Power',31:'Manual'}[@@xxx] || 'Not Set'
```
Substract one week from the state of `DateTime` and return a relative time representation in the current locale ("3 weeks ago"):
```js
=dayjs(items.DateItem.state).subtract(1, 'week').fromNow()
```
## Debugging Expressions
Expressions can be tested in the Widgets Expression Tester found in the Developer Sidebar
(<kbd>Shift</kbd><kbd>Alt</kbd><kbd>D</kbd>).