Building Pages in OH 3 - Custom User Defined Widgets

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 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 - Custom Widgets | v3 Documentation Preview.


Under Developer Tools there is a “Widgets” option. From this menu there is a tool to develop custom widgets. These are “generic” widgets that allow you to define and set a property to, for example, identify the Items that will populate the widget. Custom widgets are great for:

  • building a complicated widget to share with other openHAB users
  • building a complicated widget that will be reused across multiple similar devices in your home automation (e.g. a player widget for all your chromecasts)
  • building a simple widget that will be reused across multiple similar devices (e.g. configure how you want all your light switches to look and work once and reuse it across all of your light switches.

This tutorial is not going to be a complete tutorial for building widgets. That topic is too large for a getting started tutorial. Instead, it will demonstrate a couple of simple use cases to give a taste of what is possible.

Simple Example

This example will show how to create and reuse a list widget for all your light switches. Once created, the custom widget will appear in the list of options for the default list widget where it can be selected and configured for that specific light switch.

Create the Widget

First navigate to the Developer Tools and select Widgets.

Click the + icon.

You will be presented with a split screen view of your widget code and a live preview of how the widget looks. It will be prepopulated with a number of fields.

uid: widget_5c7a60b74f
props:
  parameterGroups: []
  parameters:
    - name: prop1
      label: Prop 1
      type: TEXT
      description: A text prop
    - name: item
      label: Item
      type: TEXT
      context: item
      description: An item to control
tags: []
component: f7-card
config:
  title: '=(props.item) ? "State of " + props.item : "Set props to test!"'
  footer: =props.prop1
  content: =items[props.item].displayState || items[props.item].state
Element Purpose
uid Unique identifier for the widget. I recommend replacing this with a meaningful name.
props A place to define properties that will be used by the widget. The props are referenced by name through an expression using the props Object. If you create a new widget, you will start with an examplenatory YAML that shows how props are define (segment props:) and referenced(segment config:). You start with 2 text props (prop1 and item) and referenced as (props.prop1 and props.item). When selecting the widget for use, a configuration form will be presented to allow users to define the props.
tags A way to add a little extra info to a widget to describe what it is and what it’s for.
components This is where the actual widget starts to be defined.
config This is the configuration for the widget.

Now that we have an idea of what all these fields are for, let’s create a list widget to use for all our light switches.

Unfortunately, the widget builder does not have a form to easily create a widget. So I recommend opening another tab and creating the widget the way described in the previous page of this tutorial. Make sure to select the card type instead of accepting the default and to select the Item so that all the fields are present in the code tab.

value: oh-toggle-item
config:
  icon: f7:lightbulb
  iconColor: '=(items.FrontPorchLight.state == "ON") ? "yellow" : "gray"'
  color: '=(items.FrontPorchLight.state == "ON") ? "yellow" : "gray"'
  title: Front Porch Light
  item: FrontPorchLight

We will use this YAML information to build our custom widget.

I’ll set the uid to “light_list_widget”

To better identify it I’ll add “list” and “light” to the tags.

Looking at our YAML from the widget we defined above we see that the component type is oh-toggle-item.

Copy everything under config from the widget already created and paste it under config in the widget builder.

Note, apparently this type of component is not renderable in the preview.

Next we want to set the properties. In this case we want to be able to customize the title and the Item that is used. So we define a property for each.

Finally we need to update the configuration to use the props and save.

The resultant YAML is:

uid: light_list_widget
tags:
  - list
  - light
props:
  parameters:
    - description: The label for the widget
      label: Title
      name: title
      required: false
      type: TEXT
    - context: item
      description: The light switch Item
      label: Item
      name: item
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Jan 28, 2021, 4:27:14 PM
component: oh-toggle-item
config:
  icon: f7:lightbulb
  iconColor: '=(items[props.item].state == "ON") ? "yellow" : "gray"'
  color: '=(items[props.item].state == "ON") ? "yellow" : "gray"'
  title: '=props.title'
  item: '=props.item'

Now return to the model and select a light switch Item and add a Default List Item Widget. Select the Widget type and this time select your newly created widget from the bottom of the list. You might need to refresh the page if it doesn’t show up.

You will see that the form changed to ask for the properties you defined. And because we gave the Item context field (see the yaml above) MainUI was smart enough to select the current Item for you. However I found that I had to reselect it to get the toggle to render correctly.

Verify that the widget rendered as expected. If not you may need to update or fix a problem (sometimes a refresh of the page is needed for the preview to show the correct state).

value: widget:light_list_widget
config:
  title: Family Room Lamp 2
  item: PeanutPlug4_Switch

Now that you have a working widget, you can reuse it for all of your light instead of needing to repeat the same setting changes over and over again. But an even bigger advantage is that when you update the widget under Developer Tools, all the Items that use it will instantly update too. So you can change how all the Items that use this widget are rendered in one place.

This will save you a ton of effort when customizing how your Items appear in the automatically generated cards.

TODO: Add creating a stand alone card (chromecast widget)
TODO: Create a card for text and date time input
TODO: Create an accordion list example

Next → Building Pages in the OH3 UI: documentation draft (2/3) (replace this with the next page in the tutorial)
Previous ← Building Pages in OH 3 - Item Customization on Auto Generated Pages

Next → Rules - Simple
Previous ← Pages - Item Widgets

7 Likes

Hey,

Thanks a lot for the support and tutorials! Really helpful :slight_smile:

One design question: Is the new widget system completely visual or is it possible to implement some kind of logic? The example I started looking for as an exercise was an appointment highlighter (the lower the duration between now() and item.state, the brighter the color).

I could see the charm of shifting this into the rules as it separates logic from visualization but I’m not sure … any input and thoughts are highly appreciated!

On the previous page there is a section titled “Expressions”. That documents all the logic you can use for pretty much any configuration field for a widget. Yannick kindly added a bunch to that section not too long ago including information about the day.js library which will give you access to date and time operations and comparisons. Couple that with the ternary operator and you should be able to achieve what you are after.

thanks rich, these little tutorials are very useful. what do you use to edit the yaml? is there an external editor?

when I have cut and pasted code into the oh3 GUI I have a nightmare sorting out the tabs.

I use the code tabs built into MainUI. I don’t use any external editors. But I do edit YAML for other stuff like Ansible roles and playbooks and I use VSCode for that. PyCharm also has a pretty good YAML plug-in.

I’m always very diligent to only use spaces though when I code so perhaps that has protected me from these sorts of problems. Most editors worth anything will automatically convert tabs to spaces too.

Once again I’m only left with saying “thank you”. Although I read that part but for some reason it didn’t click for me.

In case others have a similar question here’s a solution which starts highlighting the date beginning four days before the event:

                      - component: Label
                    config:
                      style:
                        --rgbValue: '=(dayjs(items.appointmentDate.state).diff(dayjs(), "day") < 5) ? 
                          145+36*(4-dayjs(items.appointmentDate.state).diff(dayjs(), "day")) : 
                          145'
                        color: rgb(var(--rgbValue),var(--rgbValue),var(--rgbValue))
                      text: =items.appointmentDate.displayState 

Cheers!

If you’ve any suggestions for what I could change to make it more apparent or easier to understand I’ll gladly make them.

Glad you got it working!

Honestly, I’m not sure if that is fixable via text. I can at least in hindsight explain what went wrong for me though:
I got stuck with trying to define a variable using the explained var concept. I had the association that I could use the var() basically as an intro into regular JS. After reading it again after your post I can’t even reconstruct why though. My guess is that because of the ‘variable’ wording and ‘system wide’ something in my head just said “that must be your backdoor to regular JS!”.

Frankly, I just read it again and I’m quite sure that there’s nothing which could solve this wrong association.

I think the examples are really helpful. Something that would help me would be a dedicated collection of examples (similar to what we have for Habpanel).

This is out of scope for your awesome introduction though! <3

Keep in mind when I wrote it my docs for var1 was basically ???`. Yannick came in earlier today and added all the good information that is there now. So perhaps it was just a time of check kind of problem.

Thanks for the feedback!

Look at the Add-ons UI category. Several examples have been posted by Yannick and Rainer and others. More examples are being posted all the time. It’s new so it will take some time to get to a good sized library like we have with HABPanel. In particular Yannick’s keypad widget and Rainer’s weather widgets are super impressive and informative.

1 Like

Those are really good points! May I suggest adding the search quick link into the tutorial?
If I’m not mistaken this should be the correct combination of tags and category:
https://community.openhab.org/search?q=tag:openhab3,widget%20%20%23add-ons:uis

Thanks again, both for the tutorials as well as the Q&A!

Unfortunately, the widget builder does not have a form to easily create a widget. So I recommend opening another tab and creating the widget the way described in the previous page of this tutorial. Make sure to select the card type instead of accepting the default and to select the Item so that all the fields are present in the code tab.

Hi Rich Im having a go at this and hitting some beginners problems - I don’t understand this part above - what is the previous page.

I tried just adding a new widget by clicking on the + sign in the widget builder and copying in the code from your example. I added the widget as the default widget to a light item then try to add this to a page it does not work. I select the item but it does not keep the selection. Do I need to add this to a page in a specific way - through a list item etc…It seems to be working in the prebuilt Home pages but I was assuming these widgets can be used in the page builder ui?

previous page of the tutorial that discussed item customization.

The example here only works as a list widget. It has to be an element in a list and won’t render by itself. You can’t just put it on a page.

You have to create a standalone widget for that. They use different building blocks.

Hey rlkoshak, I never worked on a wiki entry, but I read your initial statement as an invitation to just try to improve.
So I had initial struggles understanding the props purpose and have rewritten this believing my version is easier to understand.
Here is old followed by new:

Hi @rlkoshak
I am currently trying to create a widget and are struggling a little bit.
I want to create one “element” for a tv channel with the current show and the upcoming show. When I click anywhere in the “element” then an Item should receive the value of the channel.
I am not able to create the element to be like “one”. Instead I needed 4 oh-label-cards within a row. But then There is a lot of space where nothing will happen when I click on it.

image

Is it possible to create a widget where I can click anywhere in the element and then a command will be sent?

My current widget looks as follows:

uid: Fernsehprogramm
tags: []
props:
  parameters:
    - description: Title for the widget
      label: Static Title
      name: title
      required: false
      type: TEXT
    - context: item
      label: Select a 'Fernsehprogramm Group Item'
      name: prefix
      required: true
      type: TEXT
  parameterGroups: []
timestamp: Apr 10, 2021, 1:58:19 AM
component: f7-card
config:
  title: =items[props.prefix+"_Sender"].state
  action: command
  actionItem: Fernsehsender
  actionCommand: =items[props.prefix+"_Sender"].state
slots:
  default:
    - component: f7-row
      config:
        title: =items[props.prefix+"_Sender"].state
        action: command
        actionItem: Fernsehsender
        actionCommand: =items[props.prefix+"_Sender"].state
        class:
          - justify-content-center
      slots:
        default:
          - component: oh-label-card
            config:
              action: command
              actionItem: Fernsehsender
              actionCommand: =items[props.prefix+"_Sender"].state
              label: =items[props.prefix+"_Start"].displayState
              fontSize: 14px
    - component: f7-row
      config:
        class:
          - justify-content-center
      slots:
        default:
          - component: oh-label-card
            config:
              action: command
              actionItem: Fernsehsender
              actionCommand: =items[props.prefix+"_Sender"].state
              label: =items[props.prefix+"_Titel"].state
              fontSize: 14px
    - component: f7-row
      config:
        class:
          - justify-content-center
      slots:
        default:
          - component: oh-label-card
            config:
              action: command
              actionItem: Fernsehsender
              actionCommand: =items[props.prefix+"_Sender"].state
              label: =items[props.prefix+"_Start_Next"].displayState
              fontSize: 14px
    - component: f7-row
      config:
        class:
          - justify-content-center
      slots:
        default:
          - component: oh-label-card
            config:
              action: command
              actionItem: Fernsehsender
              actionCommand: =items[props.prefix+"_Sender"].state
              label: =items[props.prefix+"_Titel_Next"].state
              fontSize: 14px

I am not an expert in this stuff but I imagine if you put them all into an oh-card then you should be able to apply an action to the whole card instead of just one element. But beyond the basics I’m not an expert.

@rlkoshak
Is oh-card the right card? For me it doesn’t look like this is working. I can’t also find a documentation:

As I said, I’m not expert. But you need something basic to contain everything. Maybe an f7-card will do.

So maybe do you know how to create a “line break” within one label item? So I could use just one label with multiple lines of text in it instead of creating a widget with multiple lable items

Frankly, if it’s not already posted above or in one of my tutorials or it’s not in the docs I don’t know it. Seriously, all I’ve done are the merest basics with custom widgets. I’ve done nothing fancy and what little bit I’ve done outside of the very simple took help from others.