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

@candiesdoodle @ysc any word on the list entries for a select type f7-input widget?

Also could the options inherit from commandDescription metadata by default? In essence both are k=v maps.


Working on the same widget, why doesn’t title: =items[props.item].label work? I’ve tried on an oh-input-card on a page and with a custom widget, but the title just remains blank as if it was not specified. Even specifying an item like title: =items.ITEM_NAME.label does the same thing. I see all over the place you can get item states like items.ITEM_NAME.state

EDIT: Added just a little bit of content to better explain some things.

that items object are not the complete items actually, just their state (and eventual displayState with the transformations applied). It’s retrieved from a completely different API than the items endpoint.

For now if you want an option list the only way to do it is configure an “options” action and select the item - it will open an action sheet when you click the widget.

So does that mean there is no way to pull the item label at all in the UI?

Ah hah! That’s what I was looking for. I was looking at the f7-input card but you’ve already made that functionality into an action.

@ysc, @RGroll, it’s becoming more and more clear to me that we will need some sort of step by step tutorials for creating a custom widget. I took a shot at it using what I could glean from what gets built when I create stuff on the Overview page but’s its clear to me that I don’t have a clue what I’m doing and not getting to where I need to be.

What I was thinking was showing how to create a Chromecast widget showing the now playing image, player controls, volume control, a stop button, and the currently playing app. Bonus points for having it be hidden when the Idle channel is ON.

I was able to build something close to what I was thinking but it’s not one coherent widget. It’s a block with three rows and three disjoint cards.

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
      type: TEXT
  parameterGroups: []
timestamp: Dec 3, 2020, 8:57:58 PM
component: oh-block
config:
  title: =props.title
slots:
  default:
    - component: oh-grid-row
      config: {}
      slots:
        default:
          - component: oh-grid-col
            config: {}
            slots:
              default:
                - component: oh-image-card
                  config:
                    item: =props.prefix+"_Image"
    - component: oh-grid-row
      config: {}
      slots:
        default:
          - component: oh-grid-col
            config: {}
            slots:
              default:
                - component: oh-player-card
                  config:
                    item: =props.prefix+"_MediaControl"
                    artistItem: =props.prefix+"_MediaArtist"
                    trackItem: =props.prefix+"_MediaTitle"
          - component: oh-grid-col
            config: {}
            slots:
              default:
                - component: oh-list-card
                  config: {}
                  slots:
                    default:
                      - component: oh-slider-item
                        config:
                          item: =props.prefix+"_Volume"
                          min: 0
                          max: 100
                          step: 10
                      - component: oh-label-item
                        config:
                          action: command
                          actionCommand: ON
                          actionFeedback: Media Stopped
                          icon: f7:stop
                          iconColor: red
                          actionItem: =props.prefix+"_Stop"
    - component: oh-grid-row
      config: {}
      slots:
        default:
          - component: oh-grid-col
            config: {}
            slots:
              default:
                - component: oh-label-card
                  config:
                    item: =props.prefix+"_App"

Even when I add this to my Overview page and set the parameters (very nice feature BTW) it ends up taking the full width of the page and it creates three separate rows of widgets. I was thinking that it would sit there nice and contained within the row and column I created and dropped it into.

I’m sure there is something simple I’m missing here. Maybe I misunderstand the differences between masonary, blocks, and cells or something. like that. And given this is a case where I want this to become a step-by step tutorial, I need to understand what I’m missing, or is this something that just can’t be done.

I really think we need some sort of ste-by-step for Pages like we have with the Things, model, and as of yesterday rules. Am I heading in the wrong direction? Or am I missing something simple?

2 Likes

Hey @rlkoshak,

I totaly agree and also trying to write down some notes of the steps I take while creating my widgets.
But I realized, that it’s kinda hard finding a balance between ‘basic’ custom widgets (without too many classes and styles, which might overwhelm people) and those that are fullfills a real life usecase AND looking good but mostly depends on the usage (and styling) of standard components.

Sounds like a good idea starting with a widget which would be useful for a lot of people and also fullfills the above mentioned limited complexity - what applies here I think.

You can set-up the width of a widget via the ‘Column Options’, individually for each page. The fact, that it creates 3 rows of widgets is related to the use of the component ‘oh-grid-row’.

But this isn’t anything to worry about at all, as the height of the row depends on the largest widget it holds.

The components ‘oh-block’, ‘oh-grid-row’ and ‘oh-grid-col’ are components which were intended to use specifically to match the layout pattern of the pages and so it`s by design that they might extend over multiple rows.

If you just don’t want you components ‘flying’ exchange you ‘oh-block’ root component with a ‘f7-card’-

Which is more arguable, is the usage of pre-made widget-components (like ‘oh-image-card’, ‘oh-player-card’ and so on) inside another widget, whom have styles and classes already applied to them and were build upon the idea that they works as a standalone widget. They are easy to set-up and can be used without fiddling around with the YAML-structure whatsoever.
You can do this, but might loose some flexibility and be forced to remove some of the already applied classes again to make them look nice inside a custom-widget. (like removing borders, shadows and so on)

All of the things I’m talking about here are based on my current knowledge and the experiences I made so far - so take them with a grain of salt. I’m sure @ysc could give you a much more profound answer here.

Another approach (which I personally prefer) would be to use ‘smaller’ components (which the pre-defined components are based on) and rearrange / style them to your needs.

As an example let’s take your widget-idea from above, split it up and try to recreate it with ‘smaller’ components…
From what I understand, you would like to have a frame with 3 rows of contents (1st row: Image of the played media; 2nd row: player controls on the left / volume control and stop button on the right; 3rd row: Name of the currently used app) inside and some basic logic (like hiding elements based on item-status).


Frame

So the first thing here would be some card around your content to make it look like a single usable widget. ‘f7-card’ would be the right choice here, as its meant to act as a holder for different widget-components and you can already apply some basic informations like a title, footer, background-color and so on.

component: f7-card
config:
  title: Chromecast widget example
slots:
  default:

1st row

Now we create the first row inside that card, which should hold the image of the played content. So ‘oh-image’ would be a good fit to that requirement as we can define an image-url here as well as an item, which holds an image(-url).

    - component: f7-block
      config:
        class:
          - padding
      slots:
        default:
          - component: f7-row
            slots:
              default:
                - component: oh-image
                  config:
                    url: https://picsum.photos/600/300
                    style:
                      width: 100%
                      height: auto

You might ask, what the reason for the f7-block component is, which the content (f7-row) is sitting in.
We will use this as a container around all the components inside the content area of the widget which we can apply styles and classes to later on. The ‘style’ part inside the oh-image component just makes our image responsive in width and height.

I added an image url for demonstration purposes to the ‘oh-image’ component.


2nd row

The second row should have 2 columns in which we can put the different components. Each column will adapt to the available space which it sits in - so it will use 50% of the row each in our example.

Instead of ‘oh-player-item’ (which is normaly used as a component inside a ‘oh-list’) we use the standalone version of this control-component which is called ‘oh-player-controlls’ inside the first column and doing the same for ‘oh-slider’ (instead of ‘oh-slider-item’) as well as ‘oh-button’ (instead of ‘oh-label-item’) in the second column.

          - 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

3rd row

Now to the 3rd row which should show the title of the currently played app.
This could be realized with a simple component named ‘Label’ which is nothing else than what it says, a label… :slight_smile:
As we`re using this basic component, we have to apply some styles to it to make it fit our needs.

          - component: f7-row
            config:
              class:
                - padding-top
                - justify-content-center
              style:
                border-top: 1px solid lightgray
            slots:
              default:
                - component: Label
                  config:
                    text: YouTube Music
                    style:
                      font-size: 24px

If you then want to hide some components based on item states, I would do that with the ‘visible:’ configuration feature and an expression which you have to add to the corresponding component.

All of the above should look like this…

image

8 Likes

So I am not alone!

My present problems:

How to find the correct wording for the needed control/ element ( or whatever wording should be used in this context), for example a list from which an element could be selected.
How to dynamically fill such list (with items).

1 Like

I was struggling to find this one as well, Yannick to the rescue! You have to do it via an action, specifically the “options” action. I used a label card and linked the label and action to the same item. The options are pulled automatically from commandDescription metadata or can be provided in the widget properties.

Is there any example on how to format an item’s state with an expression?
I’ve searched on github (https://github.com/openhab/openhab-webui/issues/155) and this thread and found none.

I’m having an item with state ‘9999,32 Wh’ and want to output it on a label card to ‘9,99 kWh’ - so converting using units of measurement and formating the number.

I’ve had no success using as expression ‘=format("%,.02f kWh", item.state)’ :frowning:

I’m not positive on this but I think there is both a .displayState and a .state on the item. I would expect that you can define displayState either from the item label or the state description metadata. I’ve not played with that though. so I could be easy off.

As for the rest i think I need to suggest and get this out some. But over all your version of the chromecast widget looks exactly like what I was going for. I agree that in the beginners tutorial we want to stick as much as possible to what can be done without editing the code by hand. More to follow after I play with it a bit…

Not much to add besides @RGroll has already said here.

Yes it’s an object but it can have either only state or both state and displayState, depending on whether you have a pattern/transformation applied or not.
To be on the safe side in your expressions you can use this syntax:

=items.MyItem.displayState || items.MyItem.state

It will use displayState and fall back to state if it’s not truthy.

I believe we should also link to useful CSS resources to help developing widgets, notably:

  • This has a list of the CSS properties that you can use on most components in the style parameter.

You’ll even find nice “play it” interactive examples like these:
- https://www.w3schools.com/cssref/playit.asp?filename=playcss_justify-content
- https://www.w3schools.com/cssref/playit.asp?filename=playcss_grid
- https://www.w3schools.com/cssref/playit.asp?filename=playcss_grid2

  • These are all the CSS variables that you have in Framework7, some at the root level, some for a particular component, you can also override them or reference them in CSS like so:
--f7-text-color: red               // override
color: var( --f7-text-color)       // reference
  • This illustrates quite nicely how to make “Flexbox” layouts in CSS:
5 Likes

How can I format the display state format in an item or format the state in a widget?

Item -> Add Metadata -> Set State Pattern

1 Like

I think in M5 it is

Item -> Add Metadata -> State Description -> Pattern
1 Like

You are right, sorry…

2 Likes

Hi,
some of the material-icon are not shown correctly (i.e. microwave).
Is it a problem of F7, or some OH library to update?
Regards
Lorenzo

Nice find - also the soup bowl and some other exciting icons are missing :stuck_out_tongue_closed_eyes:

It seems that other devs also had problems using some of the icons as the library isn’t mantained well by google anymore.

@ysc Any idea how to fix this?! There seems to be an updated library available as a fork, if this could help:

Or http://materialdesignicons.com since that’s the library used by home assistant it gets regular home automation related updates. What would be cool is to be able to reference them in f7-icons. That remains to be seen.

4 Likes

Even better!