Building Pages in the OH3 UI: documentation draft (2/3)

Hey @Tobi77

this was one of my first experiments with the YAML structure and wasn’t updated since - so there might be some missing parts in documentation as well as in function.

It was more a first proof-of-concept how standard components of the MainUI would fit into a custom widget. So please be indulgent :slight_smile:

You can enable/disable every control-element using the widget-configuration.
image

There’s no support for graphs in this widget. It could be added but wouldn’t fit the look & feel very well - The used oh-trend component isn’t very flexible regarding styling atm.

image

Setting the desired temperature is possible with one of the control-elements (like slider, stepper buttons and so on) after you’ve defined the item you want to control (also in the widget-configuration setting)

If I got you right you want to show 2 values in the widget at the same time - like for example current temperature and humidity - this also isn’t supported as it was planned as a very simplistic approch to control & show a single item (yet?!).

Added an example with the integration of the oh-trend component below

I updated the widget, that you don’t have to define the liquid-background anymore (as it’s implemented as an encoded inline-svg now)

You can set the path to the ‘hero-image’ (the one in the top-right corner) also inside the widget-configuration. The standard path would be /static/brightness_icon.svg which corresponds to the path /etc/openhab/html/in your OH-setup. But as said, you can set any url here.

What exactly you mean with ‘include’? Putting it inside the widget so you don’t need to load any images from another server? If you want to, you can customize the code so it loads the encoded svg as css-attirbute (this converter might help you with that)

But it remains - this widget still serves more a proof-of-concept approach (and maybe some small real-life usecases) as it’s not finished. Maybe I’ll find time to make it more configurable (and usable) in the future - but this isn’t my first priority right now tbh.

Nevertheless I updated the widget a bit to make it at least a bit more accessible and cleaned up the configuration section…

Widget YAML
uid: darkLiquid_card
tags:
  - small
  - dark
  - custom
  - liquid
props:
  parameters:
    - context: item
      description: Set an item which you want to control
      label: Item
      name: item
      required: false
      type: TEXT
      groupName: general
    - label: Value symbol
      description: The value symbol shown after the item-value
      name: valueSymbol
      required: false
      type: TEXT
      groupName: general
    - description: valueSymbol high or low
      label: Value symbol position
      name: valueSymbolPos
      required: false
      type: BOOLEAN
      groupName: general
      advanced: true
    - description: Set the lowest value, that your item can have. (<b>default:</b> 0)
      label: Items MIN value
      name: minValue
      required: false
      type: TEXT
      groupName: general
    - description: Set the highest value, that your item can have. (<b>default:</b> 100)
      label: Items MAX value
      name: maxValue
      required: false
      type: TEXT
      groupName: general
    - description: Show trend graph instead of liquid background
      label: Trend graph
      name: showTrend
      required: false
      type: BOOLEAN
      groupName: general
    - label: Headline text
      name: headline
      required: false
      type: TEXT
      groupName: header
    - label: Subheadline text
      name: subheadline
      required: false
      type: TEXT
      groupName: header   
    - label: Hero icon image url
      name: heroIcon
      required: false
      type: TEXT
      groupName: images
    - description: Activating Stepper element to control item which you selected above
      label: Show stepper buttons
      name: showStepper
      required: false
      type: BOOLEAN
      groupName: controls
    - description: Activating Toggle element to control item which you selected above
      label: Show toggle
      name: showToggle
      required: false
      type: BOOLEAN
      groupName: controls
    - description: Activating Slider element to control item which you selected above
      label: Show slider
      name: showSlider
      required: false
      type: BOOLEAN
      groupName: controls

  parameterGroups:
    - name: general
      label: General settings
      description: Most important widget settings
    - name: header
      label: Header area  
    - name: controls
      label: Control settings
      description: Controls to manipulate your item-state (only use one of them)
    - name: images
      label: Images
timestamp: Jan 9, 2021, 11:43:36 AM
component: f7-block
config:
  style:
    position: relative
    -ms-user-select: None
    -moz-user-select: None
    -webkit-user-select: None
    user-select: None
  class:
    - no-padding
    - no-margin
slots:
  default:
    - component: oh-image
      config:
        url: "=props.heroIcon ? props.heroIcon : ''"
        style:
          position: absolute
          top: -15px
          right: -10px
          width: 100%
          max-width: min(max(50px, 15vw), 80px)
          z-index: 99
    - component: f7-card
      config:
        style:
          max-height: 200px
          background-color: rgb(57,60,76)
          border-radius: 20px
          overflow: hidden
          position: relative
      slots:
        default:
          - component: oh-repeater
            config:
              visible: "=props.showTrend ? false : true"
              sourceType: range
              for: liquid
              rangeStart: "=!props.minValue ? 0 : Number(props.minValue)"
              rangeStop: "=!props.maxValue ? 100 : Number(props.maxValue)"         
            slots:
              default:
                - component: f7-block
                  config:
                    visible: =items[props.item].state == loop.liquid_idx
                    style:
                      background-image: url('data:image/svg+xml,%3C%3Fxml version="1.0" encoding="UTF-8" standalone="no"%3F%3E%3C!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"%3E%3Csvg width="100%25" height="100%25" viewBox="0 0 100%25 100%25" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"%3E%3Cg transform="matrix(1,0,0,1.16583,0,-41.4573)"%3E%3Cpath d="M2100,51C1877.85,82.213 1653.26,55.095 1427.19,70.645C1309.15,78.764 1091.86,41.538 882.9,59.37C605.267,83.062 300.648,21.379 0,51L0,250L2100,250L2100,51Z" style="fill:url(%23_Linear1);"/%3E%3C/g%3E%3Cdefs%3E%3ClinearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-2,-214.44,250,-1.71552,1149,250)"%3E%3Cstop offset="0" style="stop-color:rgb(255,136,0);stop-opacity:1"/%3E%3Cstop offset="1" style="stop-color:rgb(255,197,28);stop-opacity:1"/%3E%3C/linearGradient%3E%3C/defs%3E%3C/svg%3E%0A')
                      position: absolute
                      height: 100%
                      width: 100%
                      left: 0
                      bottom: "=Math.fround((Number(loop.liquid) - (!props.maxValue ? 100 : Number(props.maxValue))) * (100 / (!props.maxValue ? 100 : Number(props.maxValue) - (!props.minValue ? 0 : Number(props.minValue))))) + '%'"
                      z-index: 1
          - component: f7-card-content
            config:
              style:
                height: 100%
                display: flex
                flex-direction: column
                position: relative
                z-index: 999
            slots:
              default:
                - component: f7-row
                  config:
                    style:
                      z-index: 999
                  slots:
                    default:
                      - component: f7-col
                        slots:
                          default:
                            - component: Label
                              config:
                                propsParameterGroup: header
                                text: '=(props.headline) ? props.headline : "Headline"'
                                style:
                                  white-space: nowrap
                                  text-overflow: ellipsis
                                  overflow: hidden
                                  display: block
                                  width: 100%
                                  color: rgba(255,255,255,1)
                                  letter-spacing: .75px
                                  font-size: min(max(14px, 4vw), 21px)
                                  font-weight: 600
                            - component: Label
                              config:
                                actionPropsParameterGroup: header
                                text: '=(props.subheadline) ? props.subheadline : "Subheadline"'
                                style:
                                  white-space: nowrap
                                  text-overflow: ellipsis
                                  overflow: hidden
                                  color: rgba(255,255,255,.7)
                                  letter-spacing: .75px
                                  font-size: min(max(9px, 2.5vw), 12px)
                - component: f7-row
                  config:
                    style:
                      z-index: 999
                  slots:
                    default:
                      - component: f7-col
                        config:
                          class: '=props.valueSymbolPos ? props.valueSymbolPos : "align-items-flex-end"'
                          style:
                            display: flex
                            justify-content: flex-start
                            position: relative
                        slots:
                          default:
                            - component: Label
                              config:
                                text: =items[props.item].state
                                style:
                                  color: rgba(255,255,255,1)
                                  text-shadow: 2px 2px rgba(0,0,0,.25)
                                  font-size: min(max(32px, 9vw), 51px)
                                  font-weight: 600
                            - component: Label
                              config:
                                text: =props.valueSymbol
                                style:
                                  color: rgba(255,255,255,1)
                                  text-shadow: 2px 2px rgba(0,0,0,.25)
                                  font-size: min(max(21px, 5.75vw), 32px)
                                  font-weight: 600
                      - component: f7-col
                        config:
                          style:
                            display: flex
                            align-self: center
                            justify-content: flex-end
                            align-items: flex-end
                            flex-direction: column
                        slots:
                          default:
                            - component: oh-icon
                              config:
                                icon: power
                                visible: =items[props.item].state === "0"
                                action: command
                                actionOptions: COMMAND
                                actionItem: =props.item
                                actionPropsParameterGroup: action
                                actionCommand: ON
                                width: 20
                                height: 20
                                style:
                                  cursor: pointer
                                  filter: invert()
                            - component: oh-icon
                              config:
                                icon: power
                                visible: =items[props.item].state > "0"
                                action: command
                                actionOptions: COMMAND
                                actionItem: =props.item
                                actionCommand: OFF
                                width: 20
                                height: 20
                                style:
                                  cursor: pointer
                                  filter: invert(14%) sepia(99%) saturate(7416%) hue-rotate(0deg) brightness(94%) contrast(114%)
                - component: f7-row
                  slots:
                    default:
                      - component: f7-col
                        config:
                          class: '=props.alignActionLeft ? props.alignActionLeft : "justify-content-flex-end"'
                          style:
                            height: 28px
                            max-height: 28px
                            display: flex
                        slots:
                          default:
                            - component: oh-stepper
                              config:
                                class: '=props.showStepper ? props.showStepper : "display-none"'
                                raised: true
                                buttonsOnly: true
                                small: true
                                autorepeat: true
                                min: =props.minValue
                                max: =props.maxValue
                                step: 1
                                color: white
                                style:
                                  list-style-type: none
                                item: =props.item
                            - component: oh-toggle
                              config:
                                class: '=props.showToggle ? props.showToggle : "display-none"'
                                color: yellow
                                item: =props.item
                                style:
                                  align-self: flex-end
                            - component: oh-slider
                              config:
                                class: '=props.showSlider ? props.showSlider : "display-none"'
                                step: "1"
                                min: =props.minValue
                                max: =props.maxValue
                                item: =props.item
                                style:
                                  align-self: flex-end
                                  font-color: yellow
                            - component: oh-trend
                              config:
                                visible: "=props.showTrend ? true : false"
                                trendItem: =props.item
                                trendGradient:
                                  - "#FFE7DD"
                                  - "#FFD1BD"
                                style:
                                  position: absolute
                                  bottom: 0
                                  left: 0
                                  width: 100%
                                  height: 100%

@RGroll Thanks, this is close to what I thought about. But for thermostat control, I would be very happy to

  • see and change the desired temperature
  • see vaule and trend of the current temperature.

If I get it right, I can only adress one value in the widget for control and trend, right? Can this be split in the widget without causing too much work for you?

Sorry for putting so much work for you, but up to now, these proof-of-concepts are all that beginners like me have. A library (for dimmer, for thermostat) would be of really great help.

This is how i show the room temperatures and the humidity:
(Background is getting red, when humidity rises avove 60%)
grafik

component: oh-label-cell
config:
  header: Wohnzimmer
  item: HM_WZ_Heizung_ActTemp
  trendItem: HM_WZ_Heizung_ActTemp
  action: group
  actionGroupPopupItem: WZ_Klima
  icon: f7:thermometer
  subtitle: =items.HM_WZ_Heizung_ActProfile.displayState
  footer: = "Luftfeuchtigkeit " + items.HM_WZ_Heizung_Humidity.displayState
  expandable: false
  style:
    background: '=(items.HM_WZ_Heizung_Humidity.state > 60) ? "red" : ""'

I created a group item for the heating in every room, so profiles or set temperature can be changed by clicking on the cell:

4 Likes

I’ve been trying to work through you version of the Chromecast widget and make sure I totally understand what you’ve done and why. I think it makes sense so far. But I’m running into a problem.

I the 3rd row you defined a Label. But as far as I can tell, that doesn’t work with an Item. In this case, the app is a String held in a String Item. So I looked around and there is no oh-label. And when I tried to use =props.prefix+"_App" for the text in the Label nothing shows up. I also tried an oh-label-card (out of desperation) but it doesn’t show anything either (because it’s a card?).

I appreciate any pointers. Everything else about the widget makes sense to me so far. I’m not sure how I would have figured out to do it that way on my own but at least now I’m starting to get it and may be able to start getting that part of the tutorial filled out more.

My current attempt is as follows.

uid: chromecast_widget
tags: []
props:
  parameters:
    - description: How all the Items associated with this chromecast starts
      label: Item prefix
      name: prefix
      required: false
      type: TEXT
    - description: Title for the widget
      label: Static Title
      nameJS(secs2hours.js):%s: title
      required: false
  JS(secs2hours.js):%s    type: TEXT
  parameterGroups: []
timestamp: Dec 3, 2020, 8:57:58 PM
component: f7-card
config:
  title: =props.title
slots:
  default:
    - component: f7-row
      slots:
        default:
          - component: oh-image
            config:
              item: =props.prefix+"_Image"
              style:
                width: 100%
                height: auto
    - component: f7-row
      slots:
        default:
          - component: f7-col
            slots:
              default:
                - component: Label
                  text: =items[props.prefix+"__MediaArtist"].state
                  style:
                    font-size: 24px
          - component: f7-col
            slots:
              default:
                - component: Label
                  text: =items[props.prefix+"_MediaTitle"].state
                  style:
                    font-size: 24px
    - component: f7-row
      config:
        class:
          - padding-top
          - padding-bottom
      slots:
        default:
          - component: f7-col
            slots:
              default:
                - component: oh-player-controls
                  config:
                    item: =props.prefix+"_MediaControl"
          - component: f7-col
            slots:
              default:
                - component: oh-slider
                  config:
                    item: =props.prefix+"_Volume"
                    min: 0
                    max: 100
                    step: 10
                    unit: %
                    label: true
                - component: oh-button
                  config:
                    text: Stop
                    iconF7: stop
                    fill: true
                    color: red
                    action: command
                    actionCommand: ON
                    actionFeedback: Media Stopped
                    actionItem: =props.prefix+"_Stop"
                    class:
                      - margin-top
    - component: f7-row
      config:
        class:
          - padding-top
          - justify-content-center
        style:
          border-top: 1px solid lightgray
          border-bottom: 1px solid lightgray
        slots:
          default:
            - component: Label
              config:
                text: =items[props.prefix+"_App"].state

Hey @rlkoshak

The Label component is nothing documented I think (or at least I don’t know where) - I saw it in one of the Yannick’s widgets some time ago I think, so I just began to use it. It doesn’t have the oh- prefix.

This should also work.

I tried your code and didn’t found the error at first until I see the very small (but decisive) faux-pas - the slots: is not aligned with the config: of the f7-row component - the YAML structure is kinda evil. :slight_smile:

So the last part would look like this:

    - component: f7-row
      config:
        class:
          - padding-top
          - justify-content-center
        style:
          border-top: 1px solid lightgray
          border-bottom: 1px solid lightgray
      slots:
        default:
          - component: Label
            config:
              text: =items[props.prefix+"_App"].state

Yep, I just discovered that right before your reply. Now I’m just struggling with how to center justify the column. You showed how to do the row but that doesn’t appear to flow on down into the f7-cols. And justify-content-center doesn’t seem to do anything as a class under the config for the f7-col. But that’s more for my own knowledge and not something I’m trying to actively do.

The working widget (at least for now) is:

uid: chromecast_widget
tags: []
props:
  parameters:
    - description: How all the Items associated with this chromecast starts
      label: Item prefix
      name: prefix
      required: false
      type: TEXT
    - description: Title for the widget
      label: Static Title
      name: title
      required: false
  parameterGroups: []
timestamp: Jan 12, 2021, 3:25:58 PM
component: f7-card
config:
  title: =props.title
slots:
  default:
    - component: f7-row
      slots:
        default:
          - component: oh-image
            config:
              item: =props.prefix+"_Image"
              style:
                width: 100%
                height: auto
    - component: f7-row
      config:
        class:
          - justify-content-left
      slots:
        default:
          - component: f7-col
            slots:
              default:
                - component: Label
                  config:
                    text: =items[props.prefix+"_MediaArtist"].state
          - component: f7-col
            slots:
              default:
                - component: Label
                  config:
                    text: =items[props.prefix+"_MediaTitle"].state
    - component: f7-row
      config:
        class:
          - padding-top
          - padding-bottom
      slots:
        default:
          - component: f7-col
            slots:
              default:
                - component: oh-player-controls
                  config:
                    item: =props.prefix+"_MediaControl"
          - component: f7-col
            slots:
              default:
                - component: oh-slider
                  config:
                    item: =props.prefix+"_Volume"
                    min: 0
                    max: 100
                    step: 10
                    unit: "%"
                    label: true
                - component: oh-button
                  config:
                    text: Stop
                    iconF7: stop
                    fill: true
                    color: red
                    action: command
                    actionCommand: ON
                    actionFeedback: Media Stopped
                    actionItem: =props.prefix+"_Stop"
                    class:
                      - margin-top
    - component: f7-row
      config:
        class:
          - justify-content-center
      slots:
        default:
          - component: Label
            config:
              text: =items[props.prefix+"_App"].state

though it would be nice to know how to left, right, and center justify those labels when there is more than one column.

I will probably still play around with the padding but over all I’m satisfied I know enough now to write up how I did this.

class:
  - align-self-center
  - align-self-flex-start
  - align-self-flex-end

To align the text, use:

class:
  - text-align-center
  - text-align-left
  - text-align-right

Yannick posted a nice source if you’re interested to dive a bit deeper into the handling of flex-box.

2 Likes

Thanks! For clarity that is put under the config for the Label.

Hey @ysc

are there any news on that? Just asking, to be sure that I don’t miss anything while creating a widget where this feature would be usefull.

And one more questions on the same topic - you’ve introduced a feature some time ago, to set parameters for a selected widget within the action configuration.

image

Is there a way to use that function in custom widgets?

Thanks!

Yes you define a parameterGroup with a context: action in your props (this will replace the group props with the standard dynamic action form), and then instead of the action* propertes in widgets, you specify actionPropsParameterGroup instead and it will normally take all the action parameters from that group.

uid: widget_281d436ead
props:
  parameterGroups:
    - name: myaction
      context: action
      label: My Action
  parameters:
    ...
component: oh-link
config:
  actionPropsParameterGroup: myaction

  actionModalConfig:
    prop1: abc
    item: Item1

or am I misunderstanding something?

1 Like

Thanks for your quick answer - this is exactly what I’m looking for! Awesome, will start right away to implement this.

To be honest, I haven’t thought about, that this context:action could already include the external component configuration (which makes sense) and thought every action configuration setting has its own context parameter. The soloution like this is even better (and much easier to implement)

So, thank you, again!

1 Like

After searching a number of posts, I am still unable to determine if it is possible to change the default grid spacing and element size in the overview page, or any other pages such as location and equipment. For example if I want the standard element size to be only half as tall as the default, is this possible to do on a global or eleven element by element basis. Any help here is appreciated.

Only one more thing here…

Can I access the groupParameter like a normal parameter to hide the link-component if nothing is specified as action? (you seems to do that in all the standard widgets)

The obvious at least won’t work :slight_smile:

props.widgetAction

For the Overview page, it’s a layout so you can do a lot with CSS variables. For the other pages, you can only customize the order, grouping, header, but not the layout.

They are regular props with a little bit of logic to deal with prefixes etc. It’s a little complex to explain accurately.
Use something like this temporarily to inspect the props object:

component: f7-card
config:
  content: =JSON.stringify(props)
2 Likes

This is enough explanation for now - got it working! TY

Maybe we both misunderstood something here :slight_smile: - I can set the ‘actionModal’ parameters on the widget YAML manually, to open the widget with them, yes. :white_check_mark:

My naive expectation was, that the available parameters of the widget to open, would be loaded automatically after setting the widget/page - but it looks like this on my side…

This was my last question for today, I swear!

Oh, that’s probably a regression, good catch.

Is there a possibility to set color in rgb for an OH-CELL? For F7 cards it is working.

Working:

- component: oh-colorpicker-cell
  config:
    item: FloorLamp_Color
    action: toggle
    actionItem: FloorLamp_Color
    color: blue
    title: Color Picker
    icon: f7:paintbrush

Not working:

- component: oh-colorpicker-cell
  config:
    item: FloorLamp_Color
    action: toggle
    actionItem: FloorLamp_Color
    color: rgb(255, 219, 214)
    title: Color Picker
    icon: f7:paintbrush

Working:

- component: f7-card
  config:
    content: Color Picker
    style:
      border-radius: 20px
      background-color: rgb(255, 219, 214)

How to set a color for OH-CELLS that is not pre-defines as name?

I am trying to use an expression but I can’t get it to work. Here is my code:

component: oh-plan-marker
config:
  name: KellerBewegungsAlarmMarker
  item: MotionSensor1_MotionAlarm
  icon: f7:lightbulb_fill
  iconUseState: true
  coords: 581.1919724802726,1004.9085116169801
  tooltip:=items.MotionSensor1_MotionAlarm.state === 'ON' ? 'An': 'Aus'
slots:
  default: []

The problem is this line

tooltip:=items.MotionSensor1_MotionAlarm.state === 'Alarm' ? 'Einbruch': 'Aus'

Not only that it doesn’t work but when I leave the Yaml and come back it takes away the ‘’ from the last part of the ternary expression like so

tooltip:=items.MotionSensor1_MotionAlarm.state === 'Alarm' ? 'Einbruch': Aus

And why doesn’t even this work?

tooltip:=items.MotionSensor1_MotionAlarm.state + ' mytext'

Sorry if this sounds like a stupid question …

TIA
Stefan

Try this one:

tooltip: "=(items.MotionSensor1_MotionAlarm.displayState === 'Alarm' || items.MotionSensor1_MotionAlarm.state === 'Alarm') ? 'Einbruch' : 'Aus'"

1 Like

Ok, thanks a lot for the super fast answer! That does work.

Do I have to use the double quotes because the result of the expression is a string or does the expression always have to be in double quotes?

And why then do both of these not work?

icon: "=(items.MotionSensor1_MotionAlarm.displayState === 'Alarm' || items.MotionSensor1_MotionAlarm.state === 'Alarm') ? oh:motion : oh:camera"
icon:=(items.MotionSensor1_MotionAlarm.displayState === 'Alarm' || items.MotionSensor1_MotionAlarm.state === 'Alarm') ? oh:motion: oh:camera