OH3 - Default list item widget for Sonos player

I’m using openHAB 3.0.1 and would like to convert a widget to be used as the default for for the list item displayed it in the generated pages.

The “standalone” widget looks as follows:
Screenshot at 2021-04-11 14-19-39

widget.yaml
uid: player_v1
tags: []
props:
  parameters:
    - description: Title for the widget
      label: Static Title
      name: title
      required: false
      type: TEXT
    - description: Player prefix
      label: Player prefix
      name: prefix
      required: true
      type: TEXT
  parameterGroups: []
timestamp: Apr 10, 2021, 5:46:17 PM
component: f7-card
config:
  title: =props.title
  style:
    min-width: 270px
slots:
  default:
    - component: f7-row
      config:
        class: margin display-flex align-items-center
      slots:
        default:
          - component: f7-col
            config:
              width: 100
              xsmall: 30
              small: 50
              medium: 50
              large: 50
              xlarge: 50
            slots:
              default:
                - component: f7-row
                  config:
                    class: '=(items[props.prefix+"CurrentAlbumCoverArt"].state === "NULL")?"display-none" : "- margin-vertical - justify-content-center"'
                  slots:
                    default:
                      - component: oh-image
                        config:
                          item: =props.prefix+"_CurrentAlbumCoverArt"
                          style:
                            width: 70%
          - component: f7-col
            config:
              width: 100
              xsmall: 30
              small: 50
              medium: 50
              large: 50
              xlarge: 50
            slots:
              default:
                - component: f7-row
                  config:
                    class: = "display-flex justify-content-center"
                  slots:
                    default:
                      - component: Label
                        config:
                          text: =items[props.prefix+"_MediaArtist"].state || "-"
                          style:
                            white-space: nowrap
                            overflow: hidden
                            font-size: normal
                            font-weight: bold
                            font-style: italic
                - component: f7-row
                  config:
                    class: = "display-flex justify-content-center"
                  slots:
                    default:
                      - component: Label
                        config:
                          text: =items[props.prefix+"_MediaTitle"].state || "-"
                          style:
                            white-space: nowrap
                            overflow: hidden
                            font-size: normal
                            font-weight: bold
                - component: f7-row
                  config:
                    class: = "display-flex justify-content-center"
                  slots:
                    default:
                      - component: Label
                        config:
                          text: =items[props.prefix+"CurrentAlbum"].state || "-"
                          style:
                            white-space: nowrap
                            overflow: hidden
                            font-size: normal
                            font-style: italic
    - component: f7-row
      config:
        class:
          - justify-content-space-around
          - display-flex
          - align-items-center
          - align-content-stretch
          - margin-top
      slots:
        default:
          - component: f7-icon
            config:
              f7: '=(props.prefix+"_Shuffle") ? "shuffle" : ""'
              size: 20
              color: '=(items[props.prefix+"_Shuffle"].state === "ON") ? "green" : ""'
              style:
                position: relative
                left: +7%
            slots:
              default:
                - component: oh-button
                  config:
                    action: command
                    actionItem: = props.prefix+"_Shuffle"
                    actionCommand: '=(items[props.prefix+"_Shuffle"].state !== "ON") ? "ON" : "OFF"'
                    style:
                      position: absolute
                      width: 100%
                      height: 100%
                      top: 0px
          - component: oh-player-item
            config:
              style:
                width: 150px
              item: =props.prefix+"_MediaControl"
              class:
                - display-flex
                - margin-
                - align-content-stretch
                - align-items-center
                - justify-content-space-around
          - component: f7-icon
            config:
              f7: '=(props.prefix+"_Repeat") ? (items[props.prefix+"_Repeat"].state === "Off") ? "repeat" : (items[props.prefix+"_Repeat"].state === "Track") ? "repeat_1" : "repeat" : ""'
              size: 20
              color: '=(items[props.prefix+"_Repeat"].state === "Queue") ? "green" : (items[props.prefix+"_RepeatMode"].state === "track") ? "green" : ""'
              style:
                position: relative
                left: -8%
            slots:
              default:
                - component: oh-button
                  config:
                    action: command
                    actionItem: = props.prefix+"_Repeat"
                    actionCommand: '=(items[props.prefix+"_Repeat"].state === "Off") ? "Track" : (items[props.prefix+"_Repeat"].state === "Track") ? "Queue": "Off"'
                    style:
                      position: absolute
                      width: 100%
                      height: 100%
                      top: 0px
    - component: f7-row
      config:
        class:
          - justify-content-space-around
          - display-flex
          - align-items-center
          - align-content-stretch
          - margin-top
      slots:
        default:
          - component: f7-card
            config:
              noShadow: true
              class: margin display-flex align-items-center
              style:
                height: 20px
                fontSize: 20px
                width: 100%
            slots:
              default:
                - component: f7-icon
                  config:
                    f7: speaker_3
                    class: margin-horizontal margin
                    size: 25
                - component: oh-slider
                  config:
                    label: true
                    style:
                      width: 75%
                      --f7-range-knob-color: rgba(122,122,122,0.8)
                      --f7-range-bar-size: 12px
                      --f7-range-bar-border-radius: 6px
                      --f7-range-knob-size: 16px
                      --f7-range-bar-bg-color: rgba(122,122,122,0.2)
                      --f7-range-bar-active-bg-color: linear-gradient(to right, rgba(255,255,255,0), rgba(255,255,255,0.6))
                      --f7-range-knob-box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3)
                    item: =props.prefix+"_Volume"

The widget is based on the Spotify Widget.

As my first attempts to display this in the generated pages failed I’ve created a test widget that I set as default in the metadata:

uid: test
tags: []
props:
  parameterGroups: []
timestamp: Apr 11, 2021, 1:57:58 PM
component: f7-list-item
slots:
  default:
    - component: oh-list-card
      config:
        accordionList: true
      slots:
        default:
          - component: oh-list-item
            config:
              title: Title
            slots:
              accordion:
                - component: oh-button
                  config:
                    text: Text

The widget is displayed in the generated pages, but the chevron on the right to expand is not shown. It is possible to expand the content but it does not move the rest of the content down (the content is behind the close button if I expand):
Screenshot at 2021-04-11 14-11-31

I first need to get a simple example with accordion function to work, then I can try to convert the player widget … I have another simple widget that I use in the generated pages which works - but this one does not use expand, so I can not use it as an example.

Screenshot at 2021-04-11 14-32-25

light.yaml
uid: light_v1
tags: []
props:
  parameters:
    - context: item
      description: Dimmer
      label: Item
      name: item
      required: true
      type: TEXT
  parameterGroups: []
timestamp: Apr 6, 2021, 6:07:15 PM
component: f7-list-item
slots:
  content:
    - component: f7-row
      slots:
        default:
          - component: f7-col
            config:
              width: 90
            slots:
              default:
                - component: oh-slider
                  config:
                    item: =props.item
                    step: 5
                    min: 0
                    max: 100
                    scale: true
                    scaleSteps: 5
                    scaleSubSteps: 5
                    unit: "%"
          - component: f7-col
            config:
              width: 10
            slots:
              default:
                - component: oh-button
                  config:
                    iconF7: '=(items[props.item].state > 0) ? "lightbulb_fill" : "lightbulb"'
                    iconColor: '=(items[props.item].state > 0) ? "yellow" : "gray"'
                    action: command
                    actionItem: =props.item
                    actionCommand: '=(items[props.item].state > 0) ? "OFF" : "ON"'

Any help on how to convert the player widget, or how to get the simple example to work correctly in the generated pages would be highly appreciated.

with kind regards,
Patrik

The way it is currently set up, in order to properly render the accordion content, each item with that content needs to be the direct child of a component that has the accordionList: true. This gets complicated when you’re trying to add accordion content to the default lists because you don’t have the control to set the accordionList property on that component.

The only solution I have found is a workaround that adds a level of “invisible” component in front of the default list item. For example, with my window blinds, I have the following default widget:

image

and I can open the accordion content within the list

image

But, to achieve that, in the widget defintion, I’ve had to add an oh-repeater in front of the list item. This repeater doen’t actually repeat anything useful, it’s just something that doesn’t get rendered at all but can be the parent of the list item that holds the accordionList parameter.

uid: blinds_default
tags:
  - default
  - blinds
props:
  parameters:
    - description: Label to show
      label: Item Label
      name: Label
      required: false
      type: TEXT
    - context: item
      description: An item to control
      label: Item
      name: item
      required: false
      type: TEXT
component: oh-repeater
config:
  for: dummyVar
  in:
    - nothing: here
  accordionList: true
slots:
  default:
    - component: oh-list-item
      config:
        icon: oh:blinds
        title: =props.Label
        after: =items[props.item].displayState
      slots:
        accordion:
          - component: oh-repeater
            config:
              sourceType: itemCommandOptions
              itemOptions: =props.item
              fragment: true
              for: option
            slots:
              default:
                - component: oh-button
                  config:
                    text: =loop.option.label
                    raised: true
                    action: command
                    actionItem: =props.item
                    actionCommand: =loop.option.command

You can see that in the accordion slot of the list item you can then build whatever you’d like, I just happen to use a repeater to add buttons for the item’s defined commands, but I think you could put your sonos widget in there without any other wrapping.

There’s probably a cleaner way of doing it, but this works for me.

1 Like

That is really strange, but with this trick I could get my basic example to work. Unfortunately the weekend is over, so I do not have time to try it out with the full player widget. But this gives me a good start. I wonder how you figured this out :slight_smile: , that’s far from obvious.

Thanks for taking the time to share and explain the solution to me!

with kind regards,
Patrik

It did also work with the player :slight_smile: - great:

2021-04-12 08_11_43-openHAB

.yaml
uid: player_list_v2
tags: []
props:
  parameters:
    - description: Titel
      label: Titel
      name: title
      required: false
      type: TEXT
    - description: Player prefix
      label: Player prefix
      name: prefix
      required: true
      type: TEXT
  parameterGroups: []
timestamp: Apr 11, 2021, 8:43:04 PM
component: oh-repeater
config:
  accordionList: true
  for: dummy
  in:
    - nothing: here
slots:
  default:
    - component: oh-list-item
      config:
        title: =props.title
        after: '=(items[props.prefix+"_MediaTitle"].state || "-") + ", " + items[props.prefix+"_MediaArtist"].state || "-"'
        icon: '=items[props.prefix+"_MediaControl"].state == "PLAY" ? "f7:play" : "f7:pause"'           
      slots:
        accordion:
          - component: f7-card
            style:
              min-width: 270px
            slots:
              default:
                - component: f7-row
                  config:
                    class: margin display-flex align-items-center
                  slots:
                    default:
                      - component: f7-col
                        config:
                          width: 100
                          xsmall: 30
                          small: 50
                          medium: 50
                          large: 50
                          xlarge: 50
                        slots:
                          default:
                            - component: f7-row
                              config:
                                class: '=(items[props.prefix+"CurrentAlbumCoverArt"].state === "NULL")?"display-none" : "- margin-vertical - justify-content-center"'
                              slots:
                                default:
                                  - component: oh-image
                                    config:
                                      item: =props.prefix+"_CurrentAlbumCoverArt"
                                      style:
                                        width: 70%
                      - component: f7-col
                        config:
                          width: 100
                          xsmall: 30
                          small: 50
                          medium: 50
                          large: 50
                          xlarge: 50
                        slots:
                          default:
                            - component: f7-row
                              config:
                                class: = "display-flex justify-content-center"
                              slots:
                                default:
                                  - component: Label
                                    config:
                                      text: =items[props.prefix+"_MediaArtist"].state || "-"
                                      style:
                                        white-space: nowrap
                                        overflow: hidden
                                        font-size: normal
                                        font-weight: bold
                                        font-style: italic
                            - component: f7-row
                              config:
                                class: = "display-flex justify-content-center"
                              slots:
                                default:
                                  - component: Label
                                    config:
                                      text: =items[props.prefix+"_MediaTitle"].state || "-"
                                      style:
                                        white-space: nowrap
                                        overflow: hidden
                                        font-size: normal
                                        font-weight: bold
                            - component: f7-row
                              config:
                                class: = "display-flex justify-content-center"
                              slots:
                                default:
                                  - component: Label
                                    config:
                                      text: =items[props.prefix+"CurrentAlbum"].state || "-"
                                      style:
                                        white-space: nowrap
                                        overflow: hidden
                                        font-size: normal
                                        font-style: italic
                - component: f7-row
                  config:
                    class:
                      - justify-content-space-around
                      - display-flex
                      - align-items-center
                      - align-content-stretch
                      - margin-top
                  slots:
                    default:
                      - component: f7-icon
                        config:
                          f7: '=(props.prefix+"_Shuffle") ? "shuffle" : ""'
                          size: 20
                          color: '=(items[props.prefix+"_Shuffle"].state === "ON") ? "green" : ""'
                          style:
                            position: relative
                            left: +7%
                        slots:
                          default:
                            - component: oh-button
                              config:
                                action: command
                                actionItem: = props.prefix+"_Shuffle"
                                actionCommand: '=(items[props.prefix+"_Shuffle"].state !== "ON") ? "ON" : "OFF"'
                                style:
                                  position: absolute
                                  width: 100%
                                  height: 100%
                                  top: 0px
                      - component: oh-player-item
                        config:
                          style:
                            width: 150px
                          item: =props.prefix+"_MediaControl"
                          class:
                            - display-flex
                            - margin-
                            - align-content-stretch
                            - align-items-center
                            - justify-content-space-around
                      - component: f7-icon
                        config:
                          f7: '=(props.prefix+"_Repeat") ? (items[props.prefix+"_Repeat"].state === "Off") ? "repeat" : (items[props.prefix+"_Repeat"].state === "Track") ? "repeat_1" : "repeat" : ""'
                          size: 20
                          color: '=(items[props.prefix+"_Repeat"].state === "Queue") ? "green" : (items[props.prefix+"_RepeatMode"].state === "track") ? "green" : ""'
                          style:
                            position: relative
                            left: -8%
                        slots:
                          default:
                            - component: oh-button
                              config:
                                action: command
                                actionItem: = props.prefix+"_Repeat"
                                actionCommand: '=(items[props.prefix+"_Repeat"].state === "Off") ? "Track" : (items[props.prefix+"_Repeat"].state === "Track") ? "Queue": "Off"'
                                style:
                                  position: absolute
                                  width: 100%
                                  height: 100%
                                  top: 0px
                - component: f7-row
                  config:
                    class:
                      - justify-content-space-around
                      - display-flex
                      - align-items-center
                      - align-content-stretch
                      - margin-top
                  slots:
                    default:
                      - component: f7-card
                        config:
                          noShadow: true
                          class: margin display-flex align-items-center
                          style:
                            height: 20px
                            fontSize: 20px
                            width: 100%
                        slots:
                          default:
                            - component: f7-icon
                              config:
                                f7: speaker_3
                                class: margin-horizontal margin
                                size: 25
                            - component: oh-slider
                              config:
                                label: true
                                style:
                                  width: 75%
                                  --f7-range-knob-color: rgba(122,122,122,0.8)
                                  --f7-range-bar-size: 12px
                                  --f7-range-bar-border-radius: 6px
                                  --f7-range-knob-size: 16px
                                  --f7-range-bar-bg-color: rgba(122,122,122,0.2)
                                  --f7-range-bar-active-bg-color: linear-gradient(to right, rgba(255,255,255,0), rgba(255,255,255,0.6))
                                  --f7-range-knob-box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3)
                                item: =props.prefix+"_Volume"                

Excuse me for asking one question here - it is one solution that interests me. Can you give me an example of the entry in the yaml file under “Player prefix” or describe this point a little more?

Thanks in advance.