UPDATE: The most current information on this topic is now available at: User Interface Design Overview | openHAB. The following is kept for archival purposes only!
Configuring widgets
No matter which type of page you’re editing, the designer will feature black buttons beside most widgets, that open a contextual menu for the widget in question:
These menus will in most cases allow you to:
- configure the documented parameters with a config sheet (“Configure Widget” or similar);
- open the part of the page’s YAML representation pertaining to the widget (“Edit YAML”), that is, a subgraph of the page’s component tree with that widget as its root component;
- perform cut/copy/paste operations, if supported (you can only do that in the same page currently, to copy or move widgets between pages, you may use the YAML view);
- reorder the widget within its parent slot (these options are usually named “Move Up” and “Move Down” although you may occasionally encounter other labels, like “Move Left”/“Move Right”);
- remove the widget.
Configuring the widget with the config sheet is of course more user-friendly than editing the YAML, as the options are usually organized logically in groups and feature a description:
However, it’s important to know that there are limitations and sometimes editing the YAML directly will be best, because:
- not all options are described, since widgets are often wrappers for a similar concept in the library it’s based on, either Framework7, ECharts, Leaflet, or other specialized libraries. This means that as a rule, these underlying concepts will usually be passed the key/values of the (openHAB) widget component’s config so that more parameters can be accepted than those which are documented in the widget’s definition. 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.
- 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. - 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.
- Share the widgets with others in the forum.
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 afalse
boolean to this option to hide the widget. This powerful feature, combined with expressions (see below), 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 likerole:administrator
,role:user
, oruser:<userid>
, allowing the widget to be only visible to specific users or those with a certain role (as mentioned previously in part I, this is not a security feature).
Example:visibleTo: ["user:scott", "role:administrator"]
class
andstyle
are standard Vue.js attributes and can be used to either alter the CSS classes or add inline styling to the component.
The system & standard libraries, default widgets (standalone, list items & cells), adding widgets from the model
To help you define usable pages, there are a number of built-in widgets that you can use - most of which will be in layout pages. Those built-in widgets revolve around several libraries:
- the System library includes “crude” controls that you cannot add with the designers - for instance
oh-button
,oh-slider
,oh-colorpicker
. Instead, you’re more likely use them within some container (card, list item…) provided by a widget of the Standard library; but when designing a personal widget with a complex layout you may want to use one or several of them directly. You may also use them in a slot of another widget, for those which define some, in order to add additional controls to that widget. - the Standard library, which has several classes of widgets:
- layout widgets, examples:
oh-block
,oh-masonry
,oh-row
,oh-col
that you usually add with the designer to a layout page; - standalone widgets, examples:
oh-label-card
,oh-slider-card
,oh-player-card
- usually not much more than widgets from the System library wrapped in a card; - list item widgets, examples:
oh-list-item
,oh-stepper-item
,oh-toggle-item
- widgets that are thinner than the standalone ones, which you can only add a part of a list (oh-list
oroh-list-card
); - cell widgets, examples:
oh-cell
,oh-knob-cell
,oh-colorpicker-cell
: these widgets are fixed-size cells that you can only add to aoh-cells
container widget immediately below aoh-block
in a layout page - they will either perform an action - switching a light on & off - or expanding to reveal additional controls; - page-specific widgets, for instance map pages have
oh-map-marker
oroh-map-circle-marker
, charts have different types of widgets than the rest to represent axes, series etc.
- layout widgets, examples:
Of all these classes, these three are the most important: standalone, list items and cells; that’s because they can be used to provide representations of items.
Item Default Widgets
You may have noticed in various parts of the administration pages of the UI that you can operate your items without ever having configured a widget for them: indeed it will try to figure out a “default representation” of this item (either a standalone version, or a list item version, or a cell version), using the item’s type, semantic class & property, state & command description and other means.
You may want to alter this representation though, if the auto-generated one is not optimal or plain wrong; for that, you can use the 3 metadata namespaces widget
, listWidget
, cellWidget
to configure the default representation as you would configure the widget on a page. You can do it for instance from the Model page or the item’s details page:
Then you can either choose another widget type of the same class from the Standard library if you prefer, or use a personal widget (see below). As a general rule, widgets controlling an item feature an item
property; when configuring the default representation, you shouldn’t configure this property because it will be automatically replaced with the name of the item being represented. However, you’ll still have to specify other item properties needed by the widget beside the “main” item (for instance for a oh-player-card
widget, the album/track items if relevant).
Having this concept of a default representation also helps you add several items at once on a layout page: when asked to choose which widget you want to add, select “Add from Model…”:
You will then be presented with a tree view of your semantic model of items, and be able to choose several of them at once to add to the parent container - depending on the nature of that container, it will add default standalone representations, or list items, or cells. You can then modify them individually locally for the page.
Important: the widgets added to pages from the model will instantiate the default representation at the time of the addition, either the auto-generated one or the overridden one in metadata, but it will not reflect further changes you make to the default representation by modifying the metadata again.
Finally, these default representations are used to show the Items that are part of the model in the automatically generated tabs of the Overview Page. You only need refresh the page to pick up changes on those tabs.
Widget actions
When configuring a widget, you will encounter options related to actions (usually, when something is clicked or touched, but there might be several actions configured for a single widget, for instance, clicking on different parts or a long tap with a touch device); regardless, they will all have the same options:
These action options allow you to configure the interactivity within your pages, as well as the relation between them, in an extensive way. You can navigate to another page, display additional controls, popups and other modals, send commands to items every time a widget allows you to choose an action. For instance, a floor plan marker might either open another page in a popup, or toggle an item.
Configuring the action type reveal more options in the action sheet:
The name of options related to a certain action are all prefixed with the same string (usually action
for the default action) to distinguish them from the other options of the widget, or those related to another action.
8. Expressions
Virtually everywhere - with the notable exception of chart pages - 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, as well as the following objects (subject to evolutions):
items
is a dynamic key/value dictionary allowing you to retrieve the state of items; the result ofitems.Item1
will be an object like{ state: '23', displayState: '23 °C' }
(displayState
may be omitted). You can therefore useitems.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;- the JavaScript
Math
object (so your can use _Math.floor(…), Math.round(…) and the like; - the JavaScript
JSON
object to parse or produce JSON; theme
which holds the current theme:ios
,md
oraurora
;themeOptions
anddevice
allow to use the relevant objects that you can see in the About page, Technical information, View details, underclientInfo
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 of expressions:
=(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.
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.
= (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.
Expressions can be tested in the Widgets Expression Tester found in the Developer Sidebar
(Shift+Alt+D).
Personal widgets: development & usage
You can extend the library of widgets you have at your disposal by creating personal ones, either by yourself, or copy-pasting from examples by the community; then you can reuse them on pages, multiple times if need be, simply configuring their props to your needs.
To add a new personal widget, as an admin, click on Developer Tools then Widgets. Use the ‘+’ button to create a new one:
The view features a code (YAML) editor and a live preview, you can change the orientation with the button in the center of the bottom toolbar.
Don’t forget to change the uid
right away because you won’t be able to alter it afterwards.
Sometimes the live preview will fail to update, you may want to hit the Redraw button or Ctrl-R/Cmd-R regularly when designing your widget.
To actually see how the config sheet would look like, and specify props for your widget for the live preview, click on Set props (Ctrl-P) and configure them as needed:
After saving the widget, you will have it as an option (under “Personal widgets”) to add it to a layout page, or display in a modal like a popover, or use it as the default representation of an item.
Note the special widget:<uid>
syntax for the component type to specify “use this personal widget here”, the config
being the passed as props to the widget:
component: widget:widget_0a26c10a4d
config:
prop1: Test
item: Color1
To efficiently build widgets, you will have to rely often on the style
and class
properties, in conjunction with Framework7 typography classes, CSS variables, and Vue components, notably the layout grid, and others.
Example of a personal widget:
(exercise for the reader: replace the hard coded values with expressions sourcing data from items using the prefix
prop and string concatenation)
uid: weatherforecast
tags: []
props:
parameters:
- description: Prefix for the items with the data
label: Item prefix
name: prefix
required: false
type: TEXT
parameterGroups: []
component: f7-card
config:
title: Weather Forecast
slots:
default:
- component: f7-card-content
slots:
default:
- component: f7-row
config:
class:
- display-flex
- justify-content-flex-end
- align-content-stretch
- align-items-center
slots:
default:
- component: Label
config:
text: Heavy Rain
style:
fontSize: 24px
- component: f7-row
config:
class:
- display-flex
- justify-content-space-between
- align-content-stretch
- align-items-center
slots:
default:
- component: f7-icon
config:
f7: cloud_rain
color: blue
size: 50
class:
- margin-horizontal
- float-left
- component: Label
config:
text: 7°C
style:
fontSize: 36px
fontWeight: 600
- component: f7-row
config:
class:
- display-flex
- justify-content-space-between
- align-content-stretch
- align-items-center
slots:
default:
- component: f7-col
config:
class:
- display-flex
- flex-direction-column
- align-items-center
slots:
default:
- component: f7-icon
config:
f7: cloud
color: blue
size: 24
class:
- margin-top
- component: Label
config:
text: 6°C
- component: f7-col
config:
class:
- display-flex
- flex-direction-column
- align-items-center
slots:
default:
- component: f7-icon
config:
f7: cloud_drizzle
color: blue
size: 24
class:
- margin-top
- component: Label
config:
text: 7°C
- component: f7-col
config:
class:
- display-flex
- flex-direction-column
- align-items-center
slots:
default:
- component: f7-icon
config:
f7: cloud_sun
color: blue
size: 24
class:
- margin-top
- component: Label
config:
text: 5°C
- component: f7-col
config:
class:
- display-flex
- flex-direction-column
- align-items-center
slots:
default:
- component: f7-icon
config:
f7: sun_max
color: blue
size: 24
class:
- margin-top
- component: Label
config:
text: 8°C
- component: f7-col
config:
class:
- display-flex
- flex-direction-column
- align-items-center
slots:
default:
- component: f7-icon
config:
f7: cloud_heavyrain
color: blue
size: 24
class:
- margin-top
- component: Label
config:
text: 7°C
- component: f7-card-footer
slots:
default:
- component: Label
config:
text: Paris, France