This page is part of archived documentation for openHAB 4.1. Go to the current stable version
# Building Pages: Components & Widgets
# UI Components: introduction and structure
UI components are the basic building blocks for many UIs in openHAB. The Main UI Pages and Personal Widgets are notable UI components, but Sitemaps that were created in the UI and HABPanel dashboards are as well.
These structures make up hierarchies that notably define the pages in their entirety, and are relatively simple. Each component has:
- A type
- Most of the time, a set of configuration properties
- Optionally, a set of named slots which hold collections of more components. By putting components into slots, we therefore define a hierarchy.
Slots are optional, and have a name; usually when there’re slots involved there’s a
default
slot but not always.
The semantics of both the config properties and the slots depend on the component type, as well as the allowed sub-component types in the slots. The Component Reference provide details on what you can put in a certain component's config & slots.
Sometimes, the slots can be seen as different placeholders within a component where new components may be added. For instance, cell widgets have a header
slot which represents the part of the cell when not expanded.
The default
slot is the space in the cell which becomes visible when the cell is expanded.
In various parts of the UI page designers can "focus" on a particular component usually by selecting the Edit YAML item in a black context menu.
Below is a typical component as represented in YAML:
component: oh-type
config:
prop1: value1
prop2: value2
prop3: =expression
...
slots:
default:
- component: ...
config: ...
slots: ...
- component: ...
anotherSlot:
- component: ...
...
...
# Root Components & Props
At the top of the component tree we can find a root component. Pages are examples of root components. They have additional attributes:
uid: component1
props:
parameterGroups:
- name: group1
label: Property group
...
parameters:
- name: prop1
label: Prop 1
type: BOOLEAN
groupName: group1
description: What prop1 does
- name: prop2
label: Prop 2
type: TEXT
context: item
description: Choose an item for this prop
- name: prop3
type: INTEGER
advanced: true
...
tags: ["tag1", "tag2"]
component: ...
config: ...
slots: ...
- a uid (its Unique IDentifier)
- a props structure describing its own properties; props define parameters and parameter groups following a subset of the configuration description schema found in bindings, services and throughout openHAB: see Configuration Descriptions
- a set of tags
# Widgets: Definition & Usage
Widgets are nothing more than discrete components that can be added to a Page.
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 in these cases, 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.
The YAML is the best way of sharing complete or partial component structures like pages or 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, 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. Example:visibleTo: ["user:scott", "role:administrator"]
class
andstyle
are standard Vue.js attributes (opens new window) and can be used to either alter the CSS classes or add inline styling to the component. See "Styling" below.
# Types of Widgets
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 to 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-grid-row
,oh-grid-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 as 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 anoh-cells
container widget immediately below anoh-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:
See the Component Reference for details about the different libraries of components.
# 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 (opens new window), 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 reevaluatedprops
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 widgetconfig
is a dictionary of the key/values of the configuration of the current component/widgetvars
is a dictionary of variables that are available in the component's contextloop
is a dictionary containing iteration information when you're repeating components from a source collection, it is defined only when in the context of anoh-repeater
component- the JavaScript
Math
object (so you can useMath.floor(...)
,Math.round(...)
and the like) - the JavaScript
Number
object (see mdn web docs_: Number (opens new window)) - the JavaScript
JSON
object to parse or produce JSON dayjs
to build instances of the day.js library (opens new window) that you can use to parse or manipulate date & timetheme
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
screen
returns theScreen
(opens new window) object. This allows you to access various information about the current screen, e.g. the available width and height. The two propertiesviewAreaWidth
andviewAreaHeight
are added on top. It's recommended to use CSScalc()
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:
footer: =@'Switch1'
is the same as
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
=(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.
=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 (Shift+Alt+D).
# 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:
# Types of Actions
Action | What it does |
---|---|
Navigate to page | Opens a different 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. |
TIP
In your own personal widgets (see below code snippet), you can define a parameter group with an action
context to automatically define implicit props that you can pass "en masse" to built-in components that accept actions with the actionPropsParameterGroup
property:
props:
parameterGroups:
- name: myaction
label: My Action
context: action
...
component: oh-button
config:
actionPropsParameterGroup: myaction
How the implicit props are named
The property names will be in the following format: {groupName}_{actionPropName}
The list of actionPropNames
can be found in the Component Reference (they’re always the same), for instance in the oh-button reference.
⚠️ The groupName
prefix is the name of the parameter group, but occurrences of the word "action" will be removed from it.
If you name the parameter group action
there won't be a prefix anymore so the underscore will be removed too.
Examples:
Group Name | Prop Name Examples |
---|---|
action | action, actionItem, actionCommand, actionCommandAlt |
tapAction | tap_action, tap_actionItem, tap_actionCommand, tap_actionCommandAlt |
sceneOne | sceneOne_action, sceneOne_actionItem, sceneOne_actionCommand, sceneOne_actionCommandAlt |
You can dump the props
objects in JSON to verify the names like in the following example (or just use =JSON.stringify(props)
wherever you can display text in your widget):
uid: dump_props_as_json
props:
parameterGroups:
- name: sceneOne
context: action
label: Scene One
- name: sceneTwo
context: action
label: Scene Two
- name: tapAction
context: action
label: Tap Action
- name: action
context: action
label: Tap Action
parameters:
- name: prop1
label: Prop 1
type: TEXT
description: A text prop
tags: []
component: f7-row
config:
tag: pre
slots:
default:
- component: Label
config:
text: =JSON.stringify(props, null, 4)
# 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 anoh-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 variablevar1
). - 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 (opens new window)
- Color Themes (opens new window)
- Hairlines (opens new window)
- Elevation (opens new window)
# CSS Variables
Another interesting technique is to override the many CSS Variables (opens new window) 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:
style:
--f7-button-border-color: rgb(255, 0, 0)
To reuse a CSS variable, use:
border-color: var(--f7-button-border-color)
You can even define your own CSS variables and use them in your components:
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 (opens new window) you can use to design your widgets. Use the resources at W3Schools (opens new window) or the CSS-Tricks Properties Almanac (opens new window) 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 (opens new window)
- justify-content "Play it" (opens new window) and others found in the reference
- A Complete Guide to Grid (opens new window)
- Grid Tutorial on W3Schools (opens new window)
# 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:
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() (opens new window)
- CSS-Tricks: A Complete Guide to calc() in CSS (opens new window)