[SOLVED] [OH3] UI Widget Extend existing card

Hi everybody,

i’m playing a bit with custom widget right know with OH3 M5.
Thanks to the tutorial in this forum, i’ve figured out how to create customer widgets, but wat I didn’t get is if there’s a possibility to extend the esisting widgets.

e.g. I really like the label card to display temerpatures:
image

i’d like ot extend the layout of this card with

  • Buttons to change the temperature
  • Buttons to switch the heating mode of the radiator

so something like this:
image

I know I can take the YAML from that card and use it with my custom widget which enables me to change some settings.
But i don’t see the possibility to add controls here since adding slots to the existing component does not work (and I believe this would be the wrong approach)

Long story short:
Is the complete YAML code for the existing widgest available somewhere so that I can take this as a basic example or is there some other possibility to extend existing widgets?

I’m not super great with widgets yet but I think you need to start with an F7 card (or maybe it’s a block) and then add the label and the buttons to that. See the response from @RGroll at [wiki] Building Pages in the OH3 UI: documentation draft (2/3)

One thing I would say though is you probably wouldn’t create such a widget as the default one for an individual Item as those buttons would be working with other Items. I would expect something like you describe to be build under Custom Widgets (see the Developer Tools section).

Hey @Sascha_Billian

I would call it recreating rather than extending - as you’re not able to extend the original code of the standard widgets without changing the vue-template and commit these to the git repo.

You could find the code for all the standard vue-components in the git-repo here - but they’re not represented as YAML there, so you have to abstract the vue-template-code and translate it into YAML for yourself.

It should be doable, as the names of the template components matches exactly the ones you need to use for your custom-widget - it’s just another structure.

Further more, it might be helpfull to have a look at the default custom widget code or the ones that are already posted here in the forum to learn more about the usage of the properties, which would make your cutom-widget way more dynamic and reusable - this is something you can’t realy abstract from the vue component code.

And as @rlkoshak already mentioned, you have to do all your custom widget creation in the Developer Tools section.

1 Like

@RGroll, @rlkoshak
Thanks to both of you for your tips.
Maybe i did express myself a bit wrong.
My goal was to create a customer widget. So yes, recreating would have been the correct word for it

I will have a look at the original widget definition from the repo and try to recreate it in YAML.

Thanks again
Sascha

Yes you can I am still playing around with an aircon widget

I using a binding that has set values for items if you create them all using a model so if you set it as a default widget for your thing you don’t need to enter any values into the prams to get it working. Also currently if you push on the temp button it will bring up analyser.

Yes it is a cool 27deg in bed room without the aircon on

Here is a the code for the one on the right.
Most of this is copied from others on the forum and yes it is ugly 290 lines and probably is full of stuff you don’t need.

uid: Sensibo_ACcontroller_two
tags: []
props:
  parameters:
    - description: The Label at the top of the card
      label: Friendly name of your aircon eg. Lounge
      name: title
      required: false
      type: TEXT
    - description: An item to control
      label: Item
      name: item
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Dec 7, 2020, 9:11:11 PM
component: f7-block
config:
  style:
    --f7-button-text-color: var(--f7-text-color)
    --f7-button-bg-color: var(--f7-card-bg-color)
    --f7-theme-color-rgb: var(--f7-color-blue-rgb)
  class:
    - no-padding
slots:
  default:
    - component: Label
      config:
        class:
          - margin
          - no-padding
        text: =props.title
        style:
          text-align: center
          height: auto
    - component: f7-row
      config:
        class:
          - margin
      slots:
        default:
          - component: f7-col
            slots:
              default:
                - component: oh-button
                  config:
                    action: toggle
                    actionItem: =(props.item + "_MasterSwitch")
                    actionCommand: ON
                    actionCommandAlt: OFF
                    icon-f7: power
                    iconSize: 28
                    iconColor: '=(items[props.item + "_MasterSwitch"].state === "OFF") ? "red" : "green"'
                    style:
                      --f7-button-bg-color: transparent
                      --f7-button-hover-bg-color: transparent
                      height: auto
                      font-size: 12px
          - component: f7-col
            config:
              style:
                text-align: right
            slots:
              default:
                - component: Label
                  config:
                    text: =items[props.item + "_CurrentTemperature"].state.displayState || items[props.item + "_CurrentTemperature"].state
                    style:
                      white-space: nowrap
                      text-overflow: elipsis
                      heigh: auto
                      font-size: 12px
                - component: Label
                  config:
                    text: =items[props.item + "_CurrentHumidity"].displayState || items[props.item + "_CurrentHumidity"].state
                    style:
                      white-space: nowrap
                      text-overflow: elipsis
                      heigh: auto
                      font-size: 12px
    - component: f7-row
      config:
        class:
          - margin
          - no-padding
      slots:
        default:
          - component: oh-label-card
            config:
              noBorder: true
              noShadow: true
              size: 270
              outline: true
              action: analyzer
              icon: oh:temperature
              trendItem: =(props.item + '_CurrentTemperature')
              item: =(props.item + '_CurrentTemperature')
              actionAnalyzerItems: =[props.item + '_CurrentHumidity', props.item + '_CurrentTemperature']
              class:
                - no-padding
    - component: f7-row
      config:
        class:
          - justify-content-center
      slots:
        default:
          - component: oh-gauge-card
            config:
              id: Targer Temperature https://framework7.io/vue/gauge.html#gauge-properties
              type: semicircle
              item: =(props.item + '_targetTemperature')
              size: 270
              bg-color: transparent
              border-bg-color: grey
              border-color: '=(items[props.item + "_mode"].state === "cool") ? "blue" : "gray"'
              borderWidth: 25
              value-text: text
              value-text-color: '=(items[props.item + "_mode"].state === "heat") ? "red" : "blue"'
              value-font-size: 20
              value-font-weight: 500
              label-text: =props.title
              label-text-color: black
              label-font-size: 18
              label-font-weight: 400
              noBorder: true
              noShadow: true
              outline: true
              class:
                - justify-content-center
    - component: f7-row
      config:
        class:
          - margin
      slots:
        default:
          - component: f7-col
            slots:
              default:
                - component: oh-button
                  config:
                    class:
                      - margin
                      - display-flex
                      - flex-direction-column
                    action: command
                    actionItem: =(props.item + '_mode')
                    actionCommand: ="auto"
                    icon-f7: arrow_2_circlepath
                    iconColor: '=(items[props.item + "_mode"].state === "auto") ? "red" : "gray"'
                    text: AUTO
                    textColor: '=(items[props.item + "_mode"].state === "auto") ? "red" : "gray"'
                    style:
                      --f7-button-bg-color: transparent
                      --f7-button-hover-bg-color: transparent
                      --f7-button-pressed-bg-color: transparent
                      font-size: 8px
                      height: auto
          - component: f7-col
            slots:
              default:
                - component: oh-button
                  config:
                    class:
                      - margin
                      - display-flex
                      - flex-direction-column
                    action: command
                    actionItem: =(props.item + '_mode')
                    actionCommand: ="heat"
                    icon-f7: thermometer_sun
                    iconColor: '=(items[props.item + "_mode"].state === "heat") ? "orange" : "gray"'
                    text: HEAT
                    textColor: '=(items[props.item + "_mode"].state === "heat") ? "black" : "gray"'
                    style:
                      --f7-button-bg-color: transparent
                      --f7-button-hover-bg-color: transparent
                      --f7-button-pressed-bg-color: transparent
                      font-size: 8px
                      height: auto
          - component: f7-col
            slots:
              default:
                - component: oh-button
                  config:
                    class:
                      - margin
                      - display-flex
                      - flex-direction-column
                    action: command
                    actionItem: =(props.item + '_mode')
                    actionCommand: ="dry"
                    icon-f7: drop
                    iconColor: '=(items[props.item + "_mode"].state === "dry") ? "yellow" : "gray"'
                    text: DRY
                    textColor: '=(items[props.item + "_mode"].state === "dry") ? "black" : "gray"'
                    style:
                      --f7-button-bg-color: transparent
                      --f7-button-hover-bg-color: transparent
                      --f7-button-pressed-bg-color: transparent
                      font-size: 8px
                      height: auto
          - component: f7-col
            slots:
              default:
                - component: oh-button
                  config:
                    class:
                      - margin
                      - display-flex
                      - flex-direction-column
                    action: command
                    actionItem: =(props.item + '_mode')
                    actionCommand: ="fan"
                    icon-f7: wind
                    iconColor: '=(items[props.item + "_mode"].state === "fan") ? "white" : "gray"'
                    text: FAN
                    textColor: '=(items[props.item + "_mode"].state === "fan") ? "black" : "gray"'
                    style:
                      --f7-button-bg-color: transparent
                      --f7-button-hover-bg-color: transparent
                      --f7-button-pressed-bg-color: transparent
                      font-size: 8px
                      height: auto
          - component: f7-col
            slots:
              default:
                - component: oh-button
                  config:
                    class:
                      - margin
                      - display-flex
                      - flex-direction-column
                    action: command
                    actionItem: =(props.item + '_mode')
                    actionCommand: ="cool"
                    icon-f7: thermometer_snowflake
                    iconColor: '=(items[props.item + "_mode"].state === "cool") ? "blue" : "gray"'
                    text: COOL
                    textColor: '=(items[props.item + "_mode"].state === "cool") ? "blue" : "gray"'
                    style:
                      --f7-button-bg-color: transparent
                      --f7-button-hover-bg-color: transparent
                      --f7-button-pressed-bg-color: transparent
                      font-size: 8px
                      height: auto
    - component: f7-row
      config: {}
      slots:
        default:
          - component: f7-col
            slots:
              default:
                - component: oh-button
                  config:
                    action: options
                    actionItem: =(props.item + '_fanLevel')
                    class:
                      - margin
                      - display-flex
                      - flex-direction-column
                    icon-f7: chart_bar
                    iconColor: yellow
                    size: 20
                    text: =items[props.item + '_fanLevel'].state
                    style:
                      --f7-button-bg-color: transparent
                      --f7-button-hover-bg-color: transparent
                      font-size: 12px
                      height: auto
          - component: f7-col
            slots:
              default:
                - component: oh-button
                  config:
                    action: options
                    actionItem: =(props.item + '_swingMode')
                    class:
                      - margin
                      - display-flex
                      - flex-direction-column
                    icon-f7: arrow_up_down_circle
                    iconColor: green
                    size: 20
                    text: =items[props.item + '_swingMode'].state
                    style:
                      --f7-button-bg-color: transparent
                      --f7-button-hover-bg-color: transparent
                      --f7-button-pressed-bg-color: transparent
                      font-size: 12px
                      height: auto

2 Likes

I found it easier to put a widget on a page and set it up using configure widget then edit widget code gives you the YAML

Just to be clear here - you sure can use the YAML of the standard widgets and put them inside your custom widgets.

The difference here is, that you’re limited to the given config parameters of that widget then and elements might not fit together as well as if you’re doing it with the smaller underlying vue-components.

If you’re happy with the given possibilites of a standard widget, theres no real downside doing it that way.

I guess I just figured out how to do it that way first.

The label card from the last pic helped me figure out the structure in my Aircon widget

          - component: oh-label-card
            config:
              noBorder: true
              noShadow: true
              size: 270
              outline: true
              action: analyzer
              icon: oh:temperature
              trendItem: =(props.item + '_CurrentTemperature')
              item: =(props.item + '_CurrentTemperature')
              actionAnalyzerItems: =[props.item + '_CurrentHumidity', props.item + '_CurrentTemperature']

1 Like

I do the same since I’ve no idea what I’m doing. When I’m happy I’ll move the YAML to a custom widget in the developer tools and modify it to use properties.

It didn’t occur to me to set it as the default widget for the Equipment Group though. That’s a really great idea!

He everybody,

ok, I give up :frowning:

I’ve tried two different approaches:

Option 1:
Try to recreate the oh-label-card.vue from the repository using YAML.
It somehow works, but adding actions when clicking on the item is not possible since the action configuration property is only available for the oh-* widgets. So with the standard f7 components it’s not working.

Option 2:
Try to wrap a F7 card around the oh-label-card:
Partly works, but the oh-label-card comes always with a padding / margin that i cannot change (i’ve just marked it in yellow for a better view). The button beyond does not have a border.

To eliminate this, I would need to change the margin via the config section in the YAML, but in order for this to work, there must be a corresponding config tag in the vue code or the oh-label-card. But there isn’t one

For completeness my YAML code:

uid: Thermostat
tags: []
props:
  parameters:
    - description: A text prop
      label: Prop 1
      name: prop1
      required: false
      type: TEXT
    - context: item
      description: An item to control
      label: Item
      name: item
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Dec 10, 2020, 4:03:01 PM
component: f7-card
config:
  outline: false
  noBorder: true
  padding: false
  noShadow: true
slots:
  content:
    - component: oh-label-card
      config:
        trendItem: ThermostatArbeitszimmerErdgeschoss_temperature
        action: analyzer
        actionAnalyzerItems:
          - ThermostatArbeitszimmerErdgeschoss_Solltemperatur
          - ThermostatArbeitszimmerErdgeschoss_temperature
        item: ThermostatArbeitszimmerErdgeschoss_Solltemperatur
        title: Arbeitszimmer
        icon: oh:temperature
        actionAnalyzerCoordSystem: time
        vertical: false
          
    - component: oh-button
      config:
        text: my button

So in the end:

  • I’m not able to create my customer thermostat widgest based on the oh-label-card
  • I’ve learned a lot about how the widgets work and also quite a lot about vue and f7

Thanks a lot for your help so far and your discussions.
I’ll keep you posted if I proceed here. :wink:

You’re right - forgot to mention that you should exchange the f7-action components with their oh-pendant. oh-link, oh-button are available and this is everything you need, if your plan is still the one from your initial post.

You can change most of the pre-defined styles by using the f7-style variables that are listed in the f7-docs on a component level.

In your case it’s the safe-area that messes up the layout - which you can remove by adding --f7-safe-area-left / right: 0 as a style to the standard component. (this one sadly isn’t mentioned clearly in the documentation - but you could use the chrome dev-tools (F12) in those cases and inspecting the element, you want to change)

The standard oh-button does not come with a background or outline - but you could add both with a config parameter.

All available paramters shows up as a tooltip while you’re writing your code, if not you can trigger it with ‘CTRL + Space’

image

I’m still not a fan of nested cards - but if this is what you want, I attached a small example which might help you coming a step closer to what you would like to archieve:

uid: Thermostat
tags: []
props:
  parameters:
    - description: A text prop
      label: Prop 1
      name: prop1
      required: false
      type: TEXT
    - context: item
      description: An item to control
      label: Item
      name: item
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Dec 10, 2020, 7:31:08 PM
component: f7-card
config:
  outline: false
  noBorder: true
  padding: false
  noShadow: true
  style:
    --f7-safe-area-right: 0
    --f7-safe-area-left: 0
slots:
  content:
    - component: oh-label-card
      config:
        noShadow: true
        trendItem: ThermostatArbeitszimmerErdgeschoss_temperature
        action: analyzer
        actionAnalyzerItems:
          - ThermostatArbeitszimmerErdgeschoss_Solltemperatur
          - ThermostatArbeitszimmerErdgeschoss_temperature
        item: ThermostatArbeitszimmerErdgeschoss_Solltemperatur
        title: Arbeitszimmer
        icon: oh:temperature
        actionAnalyzerCoordSystem: time
        vertical: false
    - component: f7-row
      config:
        class:
          - padding
      slots:
        default:
          - component: f7-col
            slots:
              default:
                - component: oh-button
                  config:
                    text: my button 1
                    fill: true
          - component: f7-col
            slots:
              default:
                - component: oh-button
                  config:
                    text: my button 2
                    fill: true
          - component: f7-col
            slots:
              default:
                - component: oh-button
                  config:
                    text: my button 3
                    outline: true
          - component: f7-col
            slots:
              default:
                - component: oh-button
                  config:
                    text: my button 4
                    outline: true
3 Likes

I have read many of your tutorials so I know this is not true. I am in the same boat the majority of that widget is not my work.

So I Created the items for this using create equipment from thing in the mode page and saw that the item names had a prefix.

Items

BedroomAircon_MasterSwitch
BedroomAircon_mode
BedroomAircon_targetTemperature
...

Its the Sesibo binding so all the item names follow the same pattern. The “group” equipment name is BedroomAircon

So when you follow the default props the item value is this BedroomAircon

Looking at only the power button / _MasterSwitch it builds the actionItem

- component: oh-button
   config:
    action: toggle
    actionItem: =(props.item + "_MasterSwitch")
    actionCommand: ON
    actionCommandAlt: OFF
    icon-f7: power
    iconSize: 28
    iconColor: '=(items[props.item + "_MasterSwitch"].state === "OFF") ? "red" : "green"'

Only one parameter when adding the custom widget on the page. You can select add from model then you don’t need to set the item. I leaving it in to allow the user multiple way to add a widget to pages.

I only have 2 parameters now instead of 8

props:
  parameters:
    - description: The Label at the top of the card
      label: Friendly name of your aircon eg. Lounge
      name: title
      required: false
      type: TEXT
    - context: item
      description: Aircon Group
      label: Item
      name: item
      required: false
      type: TEXT
  parameterGroups: []
2 Likes

Many thanks for your advice here. I will try it asap.
I also wasn’t keen to put a card into a card so with the hint about the action, I think I will proceed with my orignal idea :wink:

Ok. almost there. Thanks again for the great help here.
I just need a hint for the following things:

1: How can I make a multiple item selection in the config? I tried searching, but couldn’t find anything

2: what is the syntax if I want to add items from the config to the actionAnalyzerItems list?
I know that for a single setting it is e.g. item: =props.ItemName
But for a YAML list, i’m stuck

actionAnalyzerItems:
   - //<what needs to be here if the item from the config can be adressed via props.ItemName

I’ve been planning to rewrite the cards as I did with the cells, that is, make an oh-card component that you can customize with slots (header, footer, beforeContent, afterContent) and make oh-label-card and friends based on oh-card instead of f7-card directly. When that’s done you will be able to add content to every card by putting components in slots.

5 Likes

actionAnalyzerItems: =[props.item]

1 Like

Hi Sascha, I have been looking around to find a good tutorial in this forum but couldn’t find any so far. Do you mind pointing out one where I can learn and understand writing yaml widgets? (I have seen the “building-pages-in-the-oh3-ui” thread but it is hard to start with).

TIA
Stefan

Hi Stefan,

i’ve made quite some progress with checking how the existing cards are realized in the GitHub repository

I always thought that I would need to create some widgets for my needs, but in the end I stumbled upon this thread and it kind of opened my eyes what is already possible without creating my own widgets.


So i skipped creating my thermostat widget since i don’t need it.

2 Likes