Building Pages in OH 3 - Item Customization on Auto Generated Pages

I’m taking a cut at expanding the Getting Started tutorial for Pages. I’m going to insert them here for public comment before submitting them as a PR. I don’t think the existing three pages are sufficient for getting started so I’m largely rewriting them but will leave them inline at the end until I’m certain that all their contents are covered elsewhere.

As usually, this is a Wiki (for now) so please correct anything wrong and add any content you think is missing and add a reply describing what you changed. I’ll convert it back to a regular post and close it once it has been migrated to github.

This page has moved to the official docs. Please see the open PR at Getting started UI Pages by rkoshak · Pull Request #1503 · openhab/openhab-docs · GitHub and a preview at Pages - Item Widgets | v3 Documentation Preview.


Item customization

In addition to customizing the Page and the individual cards, the way that the individual Point Items are shown, or whether or not they are shown at all, can be customized as well. In general the steps will be to navigate to the Item in the Model tree, click on “Add Metadata” and select “Default List Widget”. Anything you define there will define how the Item appears in the cards.

Expressions

Before we get into specific customizations let’s explore expressions. Expressions are supported by most fields in a widget and they allow you to use the states of Items in the property or use the states of Items to choose between options for a property. For example, change the label and color of an icon based on the state of an Item.

Expressions all start with = to tell the Page that it’s an expression instead of a string literal. Everything after the = is interpreted as code that is very similar to JavaScript and a number of capabilities are available to you including the ternary operator, boolean and mathematical operations, and string manipulation.

Operator/Entity Purpose Examples
items A dictionary of all the defined Items. It carries two properties, state and displayState. The state is the default representation of the Item’s state. displayState is only defined when the Item’s State Description item metadata is defined and it represents the state using that configuration. For example to change the configuration of the displayState of an item, go to your item details in the model -> “Add Metadata”->“State Description”. Example =items.MyItem.displayState =(items.MyItem.displayState === undefined) ? items.MyItem.state : items.displayState
Math The JavaScript Math Object so you can do stuff like rounding. =Math.round(items.MyItem.state)
JSON The JavaScript JSON Object to parse and manipulate JSON. =JSON.parse(items.MyItem.state)
theme Represents the current theme, one of ios, md, aurora.
themeOptions and device Object that holds the information that you will see when you go to Help and About -> Technical Information
vars Object that holds the currently defined variables in the component’s context. Variables can be set with the variable action or as the target of many system widgets (e.g. a slider can set a variable instead of sending commands to items). =vars.var1
loop Object that will be available to descendants of the oh-repeater component and will include information on the iteration: current item, source collection, index. =loop.repeat1 (current item)
=loop.repeat1_src (source collection)
=loop.repeat1_idx (index)
Number Access to the JavaScript Number Object to parse strings to ints and floats =Number.parseFloat(items.MyItem.state.split(" ")[0])
dayjs() Access to an instance of the day.js library that you can use to parse or manipulate date & time =dayjs(items.DateItem.state).subtract(1, 'week').fromNow()
Ternary Operator A single line if/else expression. Ternary operations can be stacked to get if/else if/else type expressions. =(items.MyItem.state == "ON") ? "red" : "green"

An expression can be used in most fields and can refer to any defined Item. This can become a powerful way to combine the states of multiple Items into a single widget. For example, a garage door opener trigger Item might show the open/closed state of the door using different icons selected based on the state of the sensor Item using a ternary expression.

Common operations one will use expressions for:

f7:icons : Pages support traditional openHAB icons and f7 icons. When using f7 icons the icon will not change with the Item’s state like OH Items do so one can use a ternary expression to set the icon based on the Item’s state.

=(items.FrontDoorDeatbolt_DoorLock.state == "ON") ? "f7:lock" : "f7:lock_open"

and then choose the icon color using

=(items.FrontDoorDeatbolt_DoorLock.state == "ON") ? "green" : "red"

Units of Measurement: Unfortunately expressions do not support Items that carry units by default. You will need to strip off the units and parse the String before doing math with the Item’s state.

=(Number.parseFloat(items.MainFloor_Humidity.state.split(" ")[0]) < 35) ? "red" : (Number.parseFloat(items.MainFloor_Humidity.state.split(" ")[0]) < 75) ? "yellow" : "blue"

Note that the above is also an example of stacking ternary operators.

items: A widget can reference the states from multiple items. In this example, I have a Large_Garagedoor_Opener but I use the following to set the title of the widget:

=(items.Large_Garagedoor_Sensor.state == "OPEN" ? "close" : "open"

This lets the text of the widget indicate the direction the the door will move based on the sensor’s state.

When building anything but the simplest of expressions, the Widgets Expression Tester is an invaluable tool to see what an expression evaluates to as you type it, ensuring you get it right the first time. Open the sidebar by typing alt-shift-d and select the third tab.

Note that the Developer Sidebar will only appear if the screen is wide enough.

Visibility

Each custom defined widget has a Visibility and Visible to property. The Visibility option takes a boolean true or false (without quotes) or the result of a boolean expression to determine whether or not to render the widget. The Visibile to property controls which type of user can see the widget. Take heed of the warning, this is not a security feature.

Exclude the Item from the Model

There may be times where a piece of Equipment may have one or more Items that simply do not matter in the UI. One option is to simply exclude the Item from the model by removing the Item’s semantic tag. If the Item isn’t used anywhere else, consider removing the Item entirely.

Conditional Visibility

There may be times when certain Point Items are not relevant or interesting unless it is in a certain state or some other Item is in a certain state. For example, a smart plug may have a bunch of sensor readings indicating how much energy the device plugged in is using. Those Items are only interesting when the plug itself is ON. Therefore to hid it one can use an expression to only show it when the plug itself is ON.

Note, even if all of the Point Items for an Equipment are hidden, the Equipment will still show up on the Location and Equipment cards. You can see this in the above screen shots. cerberos is an Equipment but the points only show up when one of them is OFF.

Configure a Custom Widget

As stated above, the default widget used by the cards is the “default list widget”.

Widget Type

There are a number of widget types to choose from for a List Widget. This tutorial will not cover them all. It is usually intuitive which is the right widget to choose based on what the Item is.

Icon

Here is another example of a lock:

Notice how Front Door Lock uses the f7:lock icon when locked and the f7:lock_open icon when unlocked. Both of these are configured using an oh-toggle-item using an expression to set the color/icon of the widget. Here’s a screen shot of the lock and it’s YAML.

value: " "
config:
  icon: '=(items.FrontDoorDeadbolt_DoorLock.state == "ON") ? "f7:lock" :
    "f7:lock_open"'
  iconColor: '"orange"'

Note: to see the Icon Color field check “Show advanced”.

This is how to achieve dynamic icons for f7 icons (notice there is a link to all of the f7 icons under the icon entry in the configuration form. To do this identify the icon using oh:iconname, for example oh:garagedoor. If this is a dynamic openHAB icon, toggle on “Icon depends on state”.

value: " "
config:
  action: command
  actionCommand: ON
  item: Large_Garagedoor_Sensor
  actionFeedback: Triggered the large garage door
  actionItem: Large_Garagedoor_Opener

Pages also support openHAB icons.

Text

Most of the widgets will have a Title, Subtitle, and After field. This is a place where the text displayed on the widget to the right of the icon will be displayed. An expression can be used here to change the text based on the state of an Item. Omitting the text will leave that part of the widget blank.

State Representation

By default the state of the Item will be displayed on the right hand side of the widget using the default configuration. Sometimes the binding will provide hints on how to display the state but most of the time this default will be just the string from MyItem.state.toString().

Note: The label field of an Item’s definition in a .items file or the label set on the Item is not used here.

To customize the state of the Item the State Description metadata must be configured. This metadata lets you define the format and any transformations to apply to the Item’s state before it is displayed. When this metadata is defined, it will be used by default everywhere in MainUI.

Field What it does
Read Only A toggle. When set it tells MainUI that the Item is not controllable (e.g. a Switch used to represent a sensor state) so a text/label widget will be used instead of a toggle.
Pattern Defines the pattern used to display the state. This is where you will define the transformation and any other formatting information according to the same syntax as used by sitemaps. Everything that can go between the [ ] in that doc (excluding the [ ] themselves) can go here.
Min/Max/Step Hints to MainUI used for slider, setpoint, and knob type widgets.
Options Can be used with Actions (see below) to provide a mapping between the state of the Item and a command to issue.

There may be times when you want to suppress the state of the Item entirely. In those cases, enter (space) as the pattern and the Item’s state will not be shown.

Actions

There will be times when you want an entry in the card to perform some action when clicked on even when it’s a sensor value. For example, one might send a command to the Garage Door Opener Item when clicking on the Garage Door Sensor’s Item on the card (you can then hide or remove the opener’s Item from the model so it doesn’t show up at all, two for one).

By default the action will usually be “Analyze item(s)” which will open up a chart of the historic state of the Items (see previous chapter). But there are many other Actions that can be performed.

Action What it does
Navigate to page Opens a different Main UI Page with an optional transition.
Send command Issues a command to an Item.
Toggle Item Alternate an item between two states by sending commands (regular command if the item’s state is different, or an alternative command if the state is equal to the regular command). Typically used with ON/OFF.
Command options Issues a command to the configured Item based on a comma-separated locally-defined list of options, or on the Item’s State Description.
Run rule Trigger a rule directly.
Open popup Open a Page or personal widget in a popup which will be displayed fullscreen on phones and in a 630x630-pixel modal dialog on larger screens.
Open popover Open a Page or personal widget in a small “callout” comic-like bubble
Open sheet Open a Page or personal widget in a drawer appearing from the bottom of the screen.
Open photo browser Displays a full screen interface to view one of several images
Group details Used with Group items to open a popup with an automatically-generated list of the members of the group, represented by their default list item widget. For Groups with a base type like Switch, a standard card widget will also be shown for the Group itself.
Analyze Item(s) Opens the Analyzer window for the specified item(s) and period
External URL Open an external web page
Set Variable Set a variable that you can use in other parts of the page or widget.

Some example use cases might be to pop up a weather widget when clicking on the Item with the outside temp on a page, combining Items into one widget when the sensor and actuator are separate (e.g. garage door), etc.

Buttons

Some Switch Items represent a simple push button. One way to represent these is using a List Button. for the widget type choose a List Item. Set the title (all other fields will be ignored anyway. Toggle List Button to on and optionally set the color. This will render the Item just as text (the title) and when clicked the configured Action will be performed.

value: oh-list-item
config:
  actionCommand: ON
  listButtonColor: '"green"'
  action: command
  visibleTo:
    - role:administrator
  title: Reset Total kWh Used
  listButton: true

What if one has a ton of the same sort of Item that needs the same type of widget? That’s one of the uses for Custom Widgets in Developer Tools.

Next -> [Wiki] Building Pages in OH 3 - Custom User Defined Widgets
Previous <- Building Pages in the OH 3 UI - Automatically Generated Overview Tabs

Next -> Pages - Custom Widgets
Previous <- Pages - Overview Page

6 Likes

Looks great.

Variables are used internally by the widget via the vars object. For example, I have widget that replicates a remote capable of both long and short button presses. I’ve defined a toggle within the widget that is not linked to an item but sets a variable to determine whether a to send a long or short press:

    - component: oh-toggle
      config:
        class: margin
        variable: press
 

Then other expressions within the widget can access the state of this toggle:

actionCommand: "=(vars.press===true) ? '3.1' : '3.0'"

But I’ve been trying to figure out why they would be useful in these default widget configs. If the item is being associated with a component in a custom widget than that variable usage would be better off coded into that component. It’s possible that some of the built-in widgets have built-in variables that inform their function, but I don’t know.

1 Like

I was looking for exactly that option. Can elaborate slightly more on it, how (where) to apply a Visible setting to a single point of a certain equipment?

EDIT: I think I figured out myself… a default list widget needs to be assigned which then has the Visible option :wink:

What would be the preferred metod if i want a Point visible in model but not in UI?

Set the visibility option with false on a default list widget.

Excellent reference, thank you very much!

Is this the recommended way to deal with QuantityType – or is there a risk of comparing raw values without units? I wonder if more explicit approach is better?

=(Number.parseFloat(items.MainFloor_Humidity.state.toUnit("%").split(" ")[0]) < 35) ? "red" : (Number.parseFloat(items.MainFloor_Humidity.state.toUnit("%").split(" ")[0]) < 75) ? "yellow" : "blue"

Actually, you don’t need the split. JavaScript is smart enough to just parse the number up to the first character that isn’t part of the number. All quantity types will have a space between the number and the units so that behavior works. So

=(Number.parseFloat(items.MainFloor_Humidity.state) < 35) ? ...

would be sufficient. I learned that after writing the above and will be changing it when I move it to the actual docs.

If you prefer a more explicit approach that would be perfectly acceptable but you probably only need to worry about that in the unusual cases where the Item is carrying the value in an unexpected unit. Most of the time the unit carried by the Item will be based on the locale settings so it’s almost always what you expect.

1 Like

Hi Rich or anyone else,

i tried to use the visibility option to set items that should not be visible in the UI cards to false, but this does not seem to work. the points are still showing. AM i doing something wrong here or is it a bug? OH3.1.0 Snapshot

I am now using this on the points i want to hide in UI

 listWidget="widget:oh-list-item"[visible=false]

it works perfect but i still se the equipment in UI even it there is no Points (see Entre Väggbrytare)

Thanks Michael, your post has put me in the right direction:

if you enter false into the UI, this is how it looks like in the code:

image

I believe this is a minor bug. If you change it directly in the CODE to:

image

works as expected and the item disappears from the UI Card.

Very helpful tutorial, thanks. Regarding ACTIONS, is it possible to combine two different actions in one card entry, like e.g. trigger a rule AND open a popup?

Tried to put this on Equipment group but it did not help.

{listWidget="widget:oh-list-item"[visible=false]}

I find I frequently need to reload the page after changing an Items default widget metadata for the automatically rendered pages to pick up the changes.

I’ve successfully used false as well as expressions (so rows are only shown when Items are in a certain state) so I know it can work.

I’ve already filed an issue to not show equipment (or cards for that matter) when non-of it’s points are visibile. For now you can’t hide it,.

Please file an issue on openhab-webuis.

I don’t think so.

1 Like

Hello everyone,
thank you sharing this small guide @rlkoshak

I have a small (I thought simple, but its not) issue with the visible attribute. I try to make an icon visible depending on the state of my heating operation mode. If the heating is in mode MANUAL then the icon should be visible.

Unfortunately this is not working and I don’t know why

Screenshot from 2023-09-19 22-26-59

    - component: f7-block
      config:
        style:
          bottom: -58px
          display: flex
          flex-direction: row
          left: 80px
          position: absolute
      slots:
        default:
          - component: f7-icon
            config:
              f7: power
              size: 18
              style:
                color: rgb(255,0,0)
              visible: =items[props.operate_mode_item].state === "MANUAL"
uid: Cell_Temp_Card_1
tags: []
props:
  parameters:
    - description: Small title on top of the card
      label: Title
      name: title
      required: false
      type: TEXT
    - description: Icon on top of the card (only f7 icons (without f7:))
      label: Icon
      name: icon
      required: false
      type: TEXT
    - description: in rgba() or HEX or empty
      label: Background Color
      name: bgcolor
      required: false
      type: TEXT
    - context: item
      description: Set operate mode
      label: Heating operate mode
      name: operate_mode_item
      required: true
      type: TEXT
    - context: item
      label: Current Temperature
      name: temp_item
      required: false
      type: TEXT
    - context: item
      label: Set Temperature
      name: set_temp_item
      required: false
      type: TEXT
    - context: item
      label: Current Humidity
      name: humidity_item
      required: false
      type: TEXT
    - context: item
      description: on/off item
      label: Heating control item
      name: heating_item
      required: false
      type: TEXT
    - context: item
      description: Window item
      label: Window Item
      name: window_item
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Sep 19, 2023, 10:08:13 PM
component: f7-card
config:
  style:
    background-color: "=props.bgcolor ? props.bgcolor : ''"
    border-radius: var(--f7-card-expandable-border-radius)
    box-shadow: 5px 5px 10px 1px rgba(0,0,0,0.1)
    height: 120px
    margin-left: 5px
    margin-right: 5px
    noShadow: false
    padding: 0px
slots:
  content:
    - component: f7-block
      config:
        style:
          display: flex
          flex-direction: row
          left: 16px
          position: absolute
          top: -5px
      slots:
        default:
          - component: f7-icon
            config:
              f7: =props.icon
              size: 18
              style:
                margin-right: 10px
              visible: "=props.icon ? true : false"
          - component: Label
            config:
              style:
                font-size: 12px
                margin-top: 0px
              text: "=props.title ? props.title : ''"
    - component: f7-block
      config:
        style:
          bottom: -20px
          flex-direction: row
          left: 16px
          position: absolute
      slots:
        default:
          - component: Label
            config:
              style:
                font-size: 22px
                font-weight: 400
                margin-left: 0px
                margin-top: 0px
              text: "=items[props.temp_item].displayState ? items[props.temp_item].displayState : items[props.temp_item].state"
    - component: f7-block
      config:
        style:
          bottom: -40px
          display: flex
          flex-direction: row
          left: 12px
          position: absolute
      slots:
        default:
          - component: f7-icon
            config:
              f7: wind
              size: 18
              style:
                color: rgb(255,0,0)
              visible: "=(items[props.window_item].state === 'ON') ? true : false"
          - component: Label
            config:
              style:
                color: rgb(255,0,0)
                font-size: 12px
                margin-left: 5px
                margin-top: 0px
              text: OFFEN
              visible: "=(items[props.window_item].state === 'ON') ? true : false"
    - component: f7-block
      config:
        style:
          bottom: -63px
          display: flex
          flex-direction: row
          left: 12px
          position: absolute
      slots:
        default:
          - component: f7-icon
            config:
              f7: flame
              size: 18
              style:
                color: rgb(255,0,0)
              visible: "=(items[props.heating_item].state === 'ON') ? true : false"
          - component: Label
            config:
              style:
                color: rgb(255,0,0)
                font-size: 12px
                margin-left: 5px
                margin-top: 0px
              text: =items[props.heating_item].state
              visible: "=(items[props.heating_item].state === 'ON') ? true : false"
    - component: f7-block
      config:
        style:
          bottom: -58px
          display: flex
          flex-direction: row
          left: 80px
          position: absolute
      slots:
        default:
          - component: f7-icon
            config:
              f7: power
              size: 18
              style:
                color: rgb(255,0,0)
              visible: =items[props.operate_mode_item].state === "MANUAL"
              
    - component: oh-button
      config:
        action: options
        actionItem: =props.operate_mode_item
        actionOptions: OFF=OFF, AUTO=AUTO, AUTO_SOLAR=AUTO_SOLAR, MANUAL=MANUAL
        outline: true
        style:
          height: 27px
          position: absolute
          right: 90px
          top: 83px
          width: 120px
        text: =items[props.operate_mode_item].state
    - component: oh-button
      config:
        action: command
        actionCommand: =Number(items[props.set_temp_item].state.split(' ')[0]) + 0.5
        actionItem: =props.set_temp_item
        iconColor: red
        iconF7: arrow_up_circle
        iconSize: 35
        style:
          background: transparent
          height: 35px
          position: absolute
          right: 10px
          top: 12px
        visible: "=props.set_temp_item ? true : false"
    - component: oh-button
      config:
        action: command
        actionCommand: =Number(items[props.set_temp_item].state.split(' ')[0]) - 0.5
        actionItem: =props.set_temp_item
        iconColor: red
        iconF7: arrow_down_circle
        iconSize: 35
        style:
          background: transparent
          height: 35px
          position: absolute
          right: 10px
          top: 74px
        visible: "=props.set_temp_item ? true : false"
    - component: Label
      config:
        style:
          font-size: 12px
          position: absolute
          right: 15px
          top: 50px
        text: =items[props.set_temp_item].state
        visible: "=props.set_temp_item ? true : false"
    - component: f7-block
      config:
        style:
          height: 85px
          left: 15px
          position: absolute
          top: 15px
          width: "=props.set_temp_item ? 'calc(100% - 55px)' : '100%' "
      slots:
        default:
          - component: oh-trend
            config:
              style:
                --f7-theme-color-bg-color: transparent
                background: var(--f7-theme-color-bg-color)
                filter: opacity(40%)
                height: 80%
                left: 60px
                position: absolute
                top: 10px
                width: 80%
                z-index: 1
              trendGradient:
                - "#aa2b1d"
                - "#cc561e"
                - "#ef8d32"
                - "#beca5c"
              trendItem: =props.temp_item
    - component: oh-link
      config:
        action: analyzer
        actionAnalyzerChartType: day
        actionAnalyzerCoordSystem: time
        actionAnalyzerItems: "=props.set_temp_item ? [props.temp_item, props.humidity_item, props.set_temp_item, props.heating_item] : (props.humidity_item ? [props.temp_item, props.humidity_item] : [props.temp_item])"
        style:
          height: 80px
          left: 0px
          position: absolute
          top: 0px
          width: "=props.set_temp_item ? 'calc(100% - 55px)' : '100%' "

I don’t know whether the visibility property is recognized deep down in the widget hierarchy and I’m certain it’s not recognized by the base “f7” widgets. I’m pretty sure it only works with the “oh” widgets and not necessarily even all of the “oh” widgets.

Thank you for this hint. Changed it to oh-button but still not visible

    - component: oh-button
      config:
        action: command
        actionCommand: "=(items[props.heating_item.state === 'OFF') ? true : false"
        actionItem: =props.heating_item
        iconColor: red
        iconF7: power
        iconSize: 25
        style:
          background: transparent
          height: 35px
          position: absolute
          right: 210px
          top: 80px
        visible: =items[props.operate_mode_item].state === 'MANUAL'

:person_shrugging: I’m no expert on UI widgets. But like I said, I don’t know that the visibility property is used except in the outer widget.

visible should work on every component and your expression looks syntactically correct. That leaves “MANUAL” as the source of the issue. Are you sure that is the state of the item and not the display state? I know of several HVAC things that use all lowercase for the states but automatically convert those into all caps display states.

You can find your item in the list of items on the items setting page, that will show you the actual state instead of the display state. You can also open up the dev sidebar (Alt + Shft + D) go to the expression tester (the third button in the top button row) and enter items.real_name_of_item.state into the expression box.