Try to understand popup widgets

Hey everyone! :wave:

I’m currently working on a project where two separate widgets are triggering popups.

First widget:

uid: Cell_piholecard
tags: []
props:
  parameters:
    - description: Small title on top of the card
      label: Title
      name: title
      required: false
      type: TEXT
    - description: Header big sized
      label: Header
      name: header
      required: false
      type: TEXT
    - label: Icon
      name: icon
      required: false
      type: TEXT
    - description: HEX or rgba
      label: Background Color
      name: bgcolor
      required: false
      type: TEXT
    - default: f7:shield_fill
      description: Blocking enabled Icon - Use f7:iconName (Framework7 icon),
        material:iconName (Material icon) or iconify:iconSet:iconName
      label: Icon
      name: IconBlockingEnabled
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Oct 30, 2024, 8:05:08 AM
component: f7-card
config:
  style:
    background-color: "=props.bgcolor ? props.bgcolor : ''"
    border-radius: var(--f7-card-expandable-border-radius)
    box-shadow: 5px 5px 10px 1px rgba(0,0,0,0.1)
    height: 120px
    margin-left: 5px
    margin-right: 5px
    noShadow: false
    padding: 0px
slots:
  content:
    - component: f7-block
      config:
        style:
          display: flex
          flex-direction: row
          left: 16px
          position: absolute
          top: -5px
      slots:
        default:
          - component: oh-icon
            config:
              icon: =props.IconBlockingEnabled
              iconColor: "#4cd964"
              size: 22
              style:
                margin-right: 10px
              visible: true
          - component: Label
            config:
              style:
                font-size: 12px
                margin-top: 0px
              text: "=props.title ? props.title : ''"
    - component: f7-block
      config:
        style:
          bottom: -15px
          flex-direction: row
          left: 16px
          position: absolute
      slots:
        default:
          - component: Label
            config:
              style:
                font-size: 17px
                font-weight: 600
                margin-left: 0px
                margin-top: 0px
              text: "=props.header ? props.header : 'Set Props'"
    - component: f7-block
      config:
        style:
          bottom: -35px
          flex-direction: row
          left: 16px
          position: absolute
      slots:
        default:
          - component: Label
            config:
              style:
                font-size: 12px
                margin-left: 0px
                margin-top: 0px
              text: dd
    - component: f7-button
      config:
        popupOpen: .popup
        style:
          bottom: -6em
          left: calc(100% - 12em)
          position: absolute
        text: Weitere Informationen
    - component: f7-popup
      config:
        class: popup
      slots:
        default:
          - component: f7-page
            slots:
              default:
                - component: f7-list
                  config: {}
                  slots:
                    default:
                      - component: oh-toggle-item
                        config:
                          color: green
                          icon: oh:poweroutlet
                          item: =props.itemSwitch
                          title: Steckdose
                      - component: f7-button
                        config:
                          popupClose: true
                          text: Schließen
    - component: oh-toggle
      config:
        item: =props.SwitchGroupItem
        style:
          position: absolute
          right: 20px
          top: 15px
        visible: "=props.SwitchGroupItem ? true : false"

second widget:

uid: Cell_hueLight_Card_1
tags: []
props:
  parameters:
    - description: Small title on top of the card
      label: Title
      name: title
      required: false
    - description: Small header on top of the card
      label: header
      name: header
      required: false
      type: TEXT
    - description: Select the switch group item (e.g., Light_Power)
      label: Switch Group Item
      name: SwitchGroupItem
      required: false
      type: TEXT
    - description: Select the Color group item (e.g., Light_Color)
      label: Color Group Item
      name: ColorGroupItem
      required: false
      type: TEXT
    - description: Select the Brightness group item (e.g., Light_Brightness)
      label: Brightness Group Item
      name: BrightnessGroupItem
      required: false
      type: TEXT
    - description: Select the scene group item (e.g., Light_Power)
      label: Scene Group Item
      name: SceneGroupItem
      required: false
      type: TEXT
    - description: Select the scene1 description group item (e.g., Light_Power)
      label: Scene1 Description
      name: Scene1Description
      required: false
      type: TEXT
    - description: Select the scene1 value item (e.g., Light_Power)
      label: Scene1 Value
      name: Scene1Value
      required: false
      type: TEXT
    - description: Select the scene2 description group item (e.g., Light_Power)
      label: Scene2 Description
      name: Scene2Description
      required: false
      type: TEXT
    - description: Select the scene1 description group item (e.g., Light_Power)
      label: Scene2 value
      name: Scene2Value
      required: false
      type: TEXT
    - description: Select the switch LightOne item (e.g., Light_Power)
      label: Switch LightOne Item
      name: SwitchLightOneItem
      required: false
      type: TEXT
    - description: Select the Color LightOne item (e.g., Light_Color)
      label: Color LightOne Item
      name: ColorLightOneItem
      required: false
      type: TEXT
    - description: Select the Brightness LightOne item (e.g., Light_Brightness)
      label: Brightness LightOne Item
      name: BrightnessLightOneItem
      required: false
      type: TEXT
    - description: Select the scene LightOne item (e.g., Light_Power)
      label: Scene LightOne Item
      name: SceneLightOneItem
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Oct 31, 2024, 2:17:21 PM
component: f7-card
config:
  style:
    background-color: "=props.bgcolor ? props.bgcolor : ''"
    border-radius: var(--f7-card-expandable-border-radius)
    box-shadow: 5px 5px 10px 1px rgba(0,0,0,0.1)
    height: 150px
    margin-left: 5px
    margin-right: 5px
    noShadow: false
    padding: 0px
slots:
  content:
    - component: f7-block
      config:
        style:
          display: flex
          flex-direction: row
          left: 16px
          position: absolute
          top: -5px
      slots:
        default:
          - component: f7-icon
            config:
              f7: =props.icon
              size: 18
              style:
                margin-right: 10px
              visible: "=props.icon ? true : false"
          - component: Label
            config:
              style:
                font-size: 12px
                margin-top: 0px
              text: "=props.title ? props.title : ''"
    - component: f7-block
      config:
        style:
          bottom: -15px
          flex-direction: row
          left: 16px
          position: absolute
      slots:
        default:
          - component: Label
            config:
              style:
                font-size: 17px
                font-weight: 600
                margin-left: 0px
                margin-top: 0px
              text: "=props.header ? props.header : 'Set Props'"
    - component: oh-slider
      config:
        item: =props.BrightnessGroupItem
        label: true
        max: 100
        min: 0
        style:
          --f7-range-bar-border-radius: 8px
          --f7-range-bar-size: 8px
          --f7-range-knob-box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3)
          --f7-range-knob-size: 20px
          bottom: -75px
          left: 20px
          position: absolute
          width: calc(100% - 40px)
        visible: "=props.BrightnessGroupItem ? true : false"
    - component: oh-button
      config:
        action: command
        actionCommand: =props.Scene1Value
        actionItem: =props.SceneGroupItem
        style:
          bottom: -115px
          left: 2em
          position: absolute
        text: Szene 1
        visible: "=props.Scene1Description ? true : false"
    - component: oh-button
      config:
        action: command
        actionCommand: =props.Scene2Value
        actionItem: =props.SceneGroupItem
        style:
          bottom: -115px
          left: 8em
          position: absolute
        text: Szene 2
        visible: "=props.Scene2Description ? true : false"
    - component: f7-button
      config:
        popupOpen: .popup
        style:
          bottom: -115px
          left: calc(100% - 8em)
          position: absolute
        text: Einstellungen
    - component: f7-popup
      config:
        class: popup
      slots:
        default:
          - component: f7-page
            config: {}
            slots:
              default:
                - component: f7-navbar
                  config:
                    title: "=props.header ? props.header : 'Set Props'"
                - component: oh-list
                  config: {}
                  slots:
                    default:
                      - component: oh-toggle-item
                        config:
                          item: =props.SwitchGroupItem
                          title: Lichtschalter
                          visible: "=props.SwitchGroupItem ? true : false"
                      - component: oh-list-item
                        config:
                          title: Helligkeit
                          visible: "=props.BrightnessGroupItem ? true : false"
                      - component: oh-slider
                        config:
                          item: =props.BrightnessGroupItem
                          label: Helligkeit
                          max: 100
                          min: 0
                          step: 1
                          style:
                            --f7-range-bar-border-radius: 8px
                            --f7-range-bar-size: 8px
                            --f7-range-knob-box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3)
                            --f7-range-knob-size: 20px
                            left: 20px
                            width: calc(100% - 40px)
                            visible: "=props.BrightnessGroupItem ? true : false"
                      - component: oh-list-item
                        config:
                          title: Szenenauswahl
                          visible: "=props.BrightnessGroupItem ? true : false"
                      - component: oh-input
                        config:
                          style:
                            background: white
                            margin-left: 15px
                            margin-top: 20px
                            width: auto
                          type: select
                          variable: SelectedSceneGroupValue
                          visible: "=props.SceneGroupItem ? true : false"
                        slots:
                          default:
                            - component: option
                              config:
                                value: 1
                              slots:
                                default:
                                  - component: Content
                                    config:
                                      text: Sonnenuntergang Savanne
                            - component: option
                              config:
                                value: 2
                              slots:
                                default:
                                  - component: Content
                                    config:
                                      text: Tropendämmerung
                      - component: oh-button
                        config:
                          action: command
                          actionCommand: =vars.SelectedSceneLightOneValue
                          actionItem: =props.SceneGroupItem
                          style:
                            background: white
                          text: Szene aktivieren
                          visible: "=props.SceneGroupItem ? true : false"
                      - component: oh-list-item
                        config:
                          title: Farbauswahl
                          visible: "=props.ColorGroupItem ? true : false"
                      - component: oh-colorpicker-item
                        config:
                          item: YourColorItem
                          title: Choose Color
                          modules: -hue-slider
                          visible: "=props.ColorGroupItem ? true : false"
                - component: f7-button
                  config:
                    popupClose: true
                    text: Schließen
    - component: oh-toggle
      config:
        item: =props.SwitchGroupItem
        style:
          position: absolute
          right: 20px
          top: 15px
        visible: "=props.SwitchGroupItem ? true : false"

The issue I’m running into is that while these popups come from different widgets, they contain the same information.

popup widget 1

popup widget2

I’m hoping to gather your thoughts on the best way to manage this so it doesn’t feel redundant for users.

Would you find it confusing if two different widgets triggered popups with identical content?
I’d love to hear your feedback on whether this setup could be streamlined or if there’s a way to avoid any potential user experience issues.

Thanks in advance for your insights and suggestions! :blush:

If the two widgets are on the same page then you are, in fact, opening the same popup. The answer to why is here:

The f7 popupOpen property is not aware that you have created a popup in the same OH widget as the button making this call. In fact, the way f7 modals all work is that they are not even rendered in the document tree where you define them, they are moved up to a much higher separate modal node so that they easily cover other elements when open. So the f7 library needs a way for each element to be able to define which popup it wants to open given that there could be many different popups in that modal node at the top of tree.

CSS selectors, of course, already exists as a way to search and identify all the elements in the document, so that’s what the popupOpen (and all the other related open a modal properties) uses. So your css setting of .popup tells the button to open the popup with the class popup. But, if you have put both these widgets on a page then you have two different popups with the same identifying information and f7 just picks the first one. So, both of your buttons will open the same popup and ignore the second popup with the same class.

The solution, of course is to find a way to make your popup uniquely identifiable from a css perspective. There are many ways to do this. If there are only ever going to be one copy of each of these widgets on a page, then you can just set the class to something distinct. For example, for the pi hole widget:

    - component: f7-button
      config:
        popupOpen: .popup-pi

and

    - component: f7-popup
      config:
        class: popup-pi

But, I have two different pi-hole instances running that I track, so what if I want two of these widgets on my overview page? I’d run into the same problem with two popups that both have popup-pi as their class.

In that case you have to make sure that the class you are creating in every card is unique. One easy way to do this with cards that are connected to items is to use whatever the widget’s item property is to include the item name in the class. For example a widget that carries the name of an item (which must be unique in the OH item list by definition) would be:

    - component: f7-button
      config:
        popupOpen: =`.popup-${props.item}`

and

    - component: f7-popup
      config:
        class: =`popup-${props.item}`

Of course, if you have no available property in the widget that might make it sufficiently unique, then you can use an oh-context to create an additional random ID that you can use to make sure each created widget calls a different popup.

component: oh-context
config:
  constants:
    widgetID: =Number.parseInt(Math.random()*8912).toString(16).padStart(4, '0')

and

    - component: f7-button
      config:
        popupOpen: =`.popup-${const.widgetID}`

and

    - component: f7-popup
      config:
        class: =`popup-${const.widgetID}`

Lastly, if you are going to have a lot of the same widget on a page, then, instead of cluttering up your page with a bunch of different popups that all just get rendered and sit in the background until they are needed, you could consider creating a standalone popup widget which uses widget variables to know which item to work with at any one time. Then each of the buttons can simultaneously call popupOpen on the shared popup and set the widget variable that determines the popup’s item for that instance.

2 Likes

Where do I need to place the

component: oh-context
config:
  constants:
    widgetID: =Number.parseInt(Math.random()*8912).toString(16).padStart(4, '0')

this gives me erros

uid: Cell_piholecard
tags: []
props:
  parameters:
    - description: Small title on top of the card
      label: Title
      name: title
      required: false
      type: TEXT
    - description: Header big sized
      label: Header
      name: header
      required: false
      type: TEXT
    - label: Icon
      name: icon
      required: false
      type: TEXT
    - description: HEX or rgba
      label: Background Color
      name: bgcolor
      required: false
      type: TEXT
    - default: f7:shield_fill
      description: Blocking enabled Icon - Use f7:iconName (Framework7 icon),
        material:iconName (Material icon) or iconify:iconSet:iconName
      label: Icon
      name: IconBlockingEnabled
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Oct 31, 2024, 5:16:21 PM
component: f7-card
config:
  style:
    background-color: "=props.bgcolor ? props.bgcolor : ''"
    border-radius: var(--f7-card-expandable-border-radius)
    box-shadow: 5px 5px 10px 1px rgba(0,0,0,0.1)
    height: 120px
    margin-left: 5px
    margin-right: 5px
    noShadow: false
    padding: 0px
slots:
  default:
    - component: oh-context
      config:
        constants:
          widgetID: =Number.parseInt(Math.random()*8912).toString(16).padStart(4, '0')
  content:
    - component: oh-context
    - component: f7-block
      config:
        style:
          display: flex
          flex-direction: row
          left: 16px
          position: absolute
          top: -5px
      slots:
        default:
          - component: oh-icon
            config:
              icon: =props.IconBlockingEnabled
              iconColor: "#4cd964"
              size: 22
              style:
                margin-right: 10px
              visible: true
          - component: Label
            config:
              style:
                font-size: 12px
                margin-top: 0px
              text: "=props.title ? props.title : ''"
    - component: f7-block
      config:
        style:
          bottom: -15px
          flex-direction: row
          left: 16px
          position: absolute
      slots:
        default:
          - component: Label
            config:
              style:
                font-size: 17px
                font-weight: 600
                margin-left: 0px
                margin-top: 0px
              text: "=props.header ? props.header : 'Set Props'"
    - component: f7-block
      config:
        style:
          bottom: -35px
          flex-direction: row
          left: 16px
          position: absolute
      slots:
        default:
          - component: Label
            config:
              style:
                font-size: 12px
                margin-left: 0px
                margin-top: 0px
              text: dd
    - component: f7-button
      config:
        popupOpen: =`.popup-${const.widgetID}`
        style:
          bottom: -6em
          left: calc(100% - 12em)
          position: absolute
        text: Weitere Informationen
    - component: f7-popup
      config:
        class: =`popup-${const.widgetID}`
      slots:
        default:
          - component: f7-page
            slots:
              default:
                - component: f7-list
                  config: {}
                  slots:
                    default:
                      - component: oh-toggle-item
                        config:
                          color: green
                          icon: oh:poweroutlet
                          item: =props.itemSwitch
                          title: Steckdose
                      - component: f7-button
                        config:
                          popupClose: true
                          text: Schließen
    - component: oh-toggle
      config:
        item: =props.SwitchGroupItem
        style:
          position: absolute
          right: 20px
          top: 15px
        visible: "=props.SwitchGroupItem ? true : false"


You’ve gotten it a little jumbled up here. Firstly, you have a duplicate oh-context with no configuration at all. Secondly, you’ve not added a default slot to the context. The oh-context has a default slot, just like every other component, and the only components that have access to its context information are components that are in that default slot. So you use it just like any other component in the component tree, you just have to make sure that you insert it at a location in the tree where all the pieces that need it are it’s children. Think of the oh-context like an oh-repeater, it doesn’t render anything itself, so you can put it anywhere you need the extra information without changing how the widget is built. In practice, that means that it’s usually just best to make it the root component of the widget to make sure all the other components have access to the context information.

component: oh-context
config:
  constants:
    widgetID: =Number.parseInt(Math.random()*8912).toString(16).padStart(4, '0')
slots:
  default:
    - component: f7-card
      config:
        style:
          background-color: "=props.bgcolor ? props.bgcolor : ''"
          border-radius: var(--f7-card-expandable-border-radius)
          box-shadow: 5px 5px 10px 1px rgba(0,0,0,0.1)
          height: 120px
          margin-left: 5px
          margin-right: 5px
          noShadow: false
          padding: 0px
    slots:
      default:
        ...rest of widget