Oh-repeater issue with accordion

I’m stuck with a oh-repeater issue using oh-list with accordion set to true.

It took some time for me to get accordion working in general. However now the items are not looped anymore, and I don’t know what went wrong :frowning:

YAML configuration with accordion enabled, but incomplete item-list
uid: all_timestamps_v5_ac
props:
  parameters: []
  parameterGroups: []
timestamp: Dec 21, 2023, 6:48:53 PM
component: f7-block
config: {}
slots:
  default:
    - component: oh-repeater
      config:
        fetchMetadata: semantics, widgetOrder, uiSemantics
        for: allStatusItems
        fragment: true
        itemTags: Status,TimeStamp
        listContainer: false
        sourceType: itemsWithTags
      slots:
        default:
          - component: oh-list
            config:
              accordionList: true
              mediaList: true
            slots:
              default:
                - component: oh-list-item
                  config:
                    visible: =(loop.allStatusItems_idx==0)
                    title: =loop.allStatusItems_source.length + " Sensors erkannt"
                  slots:
                    accordion:
                      - component: oh-list
                        config:
                          visible: =(loop.allStatusItems_idx==0)
                          mediaList: true
                          style:
                            margin-left: 0px
                            padding: 0px
                            width: 100%
                        slots:
                          default:
                            - component: oh-repeater
                              config:
                                fetchMetadata: semantics, widgetOrder, uiSemantics
                                filter: '(dayjs().diff(items[loop.i.name].state,"m") > 60) ? true : false '
                                for: i
                                in: =loop.allStatusItems_source
                                listContainer: false
                                sourceType: array
                              slots:
                                default:
                                  - component: oh-list
                                    config:
                                      accordionList: true
                                    slots:
                                      default:
                                        - component: oh-list-item
                                          config:
                                            visible: =(loop.i_idx==0)
                                            title: =loop.i_source.length + " Sensors - OK"
                                          slots:
                                            accordion:
                                              - component: oh-list
                                                config: {}
                                                slots:
                                                  default:
                                                    - component: oh-list-item
                                                      config:
                                                        iconUseState: false
                                                        action: group
                                                        actionGroupPopupItem: =loop.i.metadata.semantics.config.isPointOf
                                                        badge: =dayjs(items[loop.i.name].state).fromNow()
                                                        badgeColor: =dayjs().diff(dayjs(items[loop.i.name].state),"h")<1?"green":dayjs().diff(dayjs(items[loop.i.name].state),"d")<1?"orange":"red"
                                                        footer: =loop.i.metadata.uiSemantics.config.preposition + loop.i.metadata.uiSemantics.config.location
                                                        icon: f7:arrow_clockwise
                                                        iconColor: '=items[loop.i.name].state === "ON" || items[loop.i.name].state > 0 ? "green" : "gray"'
                                                        media-list: true
                                                        title: =loop.i.metadata.uiSemantics.config.equipment
                                                        noChevron: true
                            - component: oh-repeater
                              config:
                                fetchMetadata: semantics, widgetOrder, uiSemantics
                                filter: '(dayjs().diff(items[loop.i.name].state,"m") <= 60) ? true : false '
                                for: i
                                fragment: true
                                in: =loop.allStatusItems_source
                                listContainer: false
                                sourceType: array
                              slots:
                                default:
                                  - component: oh-list
                                    config:
                                      accordionList: true
                                    slots:
                                      default:
                                        - component: oh-list-item
                                          config:
                                            visible: =(loop.i_idx==0)
                                            title: =loop.i_source.length + " Sensors - Warnung"
                                          slots:
                                            accordion:
                                              - component: oh-list
                                                config: {}
                                                slots:
                                                  default:
                                                    - component: oh-list-item
                                                      config:
                                                        iconUseState: false
                                                        action: group
                                                        actionGroupPopupItem: =loop.i.metadata.semantics.config.isPointOf
                                                        badge: =dayjs(items[loop.i.name].state).fromNow()
                                                        badgeColor: =dayjs().diff(dayjs(items[loop.i.name].state),"h")<1?"green":dayjs().diff(dayjs(items[loop.i.name].state),"d")<1?"orange":"red"
                                                        footer: =loop.i.metadata.uiSemantics.config.preposition + loop.i.metadata.uiSemantics.config.location
                                                        icon: f7:arrow_clockwise
                                                        iconColor: '=items[loop.i.name].state === "ON" || items[loop.i.name].state > 0 ? "green" : "gray"'
                                                        media-list: true
                                                        title: =loop.i.metadata.uiSemantics.config.equipment
                                                        noChevron: true

The output should look like below screenshot, but for some reason only 1 item is listed when I use the config with accordion.

Any hint want I did wrong is appreciated :slight_smile:

YAML config for working output, but without accordion enabled
uid: all_timestamps_v5
tags:
  - example
  - testing
props:
  parameters: []
  parameterGroups: []
timestamp: Dec 20, 2023, 2:30:54 PM
component: f7-card
config: {}
slots:
  default:
    - component: f7-list
      slots:
        default:
          - component: oh-repeater
            config:
              fetchMetadata: semantics, widgetOrder, uiSemantics
              for: allStatusItems
              fragment: true
              itemTags: Status,TimeStamp
              listContainer: false
              sourceType: itemsWithTags
              accordionList: true
            slots:
              default:
                - component: f7-block-title
                  config:
                    visible: =(loop.allStatusItems_idx==0)
                  slots:
                    default:
                      - component: Label
                        config:
                          text: =loop.allStatusItems_source.length + " Sensors erkannt"
                - component: f7-list
                  config:
                    visible: =(loop.allStatusItems_idx==0)
                    mediaList: true
                    accordionList: true
                    style:
                      margin-left: 0px
                      padding: 0px
                      width: 100%
                  slots:
                    default:
                      - component: oh-repeater
                        config:
                          fetchMetadata: semantics, widgetOrder, uiSemantics
                          filter: '(dayjs().diff(items[loop.i.name].state,"m") > 60) ? true : false '
                          for: i
                          fragment: true
                          in: =loop.allStatusItems_source
                          listContainer: false
                          sourceType: array
                          accordionList: true
                        slots:
                          default:
                            - component: f7-block-title
                              config:
                                visible: =(loop.i_idx==0)
                              slots:
                                default:
                                  - component: Label
                                    config:
                                      text: =loop.i_source.length + " Sensors - Warnung"
                            - component: oh-list-item
                              config:
                                iconUseState: false
                                action: group
                                actionGroupPopupItem: =loop.i.metadata.semantics.config.isPointOf
                                badge: =dayjs(items[loop.i.name].state).fromNow()
                                badgeColor: =dayjs().diff(dayjs(items[loop.i.name].state),"h")<1?"green":dayjs().diff(dayjs(items[loop.i.name].state),"d")<1?"orange":"red"
                                footer: =loop.i.metadata.uiSemantics.config.preposition + loop.i.metadata.uiSemantics.config.location
                                icon: f7:arrow_clockwise
                                iconColor: '=items[loop.i.name].state === "ON" || items[loop.i.name].state > 0 ? "green" : "gray"'
                                media-list: true
                                title: =loop.i.metadata.uiSemantics.config.equipment
                                noChevron: true
                                accordionItem: true
                      - component: oh-repeater
                        config:
                          fetchMetadata: semantics, widgetOrder, uiSemantics
                          filter: '(dayjs().diff(items[loop.i.name].state,"m") <= 60) ? true : false '
                          for: i
                          fragment: true
                          in: =loop.allStatusItems_source
                          listContainer: false
                          sourceType: array
                        slots:
                          default:
                            - component: f7-block-title
                              config:
                                visible: =(loop.i_idx==0)
                              slots:
                                default:
                                  - component: Label
                                    config:
                                      text: =loop.i_source.length + " Sensors - OK"
                            - component: oh-list-item
                              config:
                                action: group
                                actionGroupPopupItem: =loop.i.metadata.semantics.config.isPointOf
                                badge: =dayjs(items[loop.i.name].state).fromNow()
                                badgeColor: =dayjs().diff(dayjs(items[loop.i.name].state),"h")<1?"green":dayjs().diff(dayjs(items[loop.i.name].state),"d")<1?"orange":"red"
                                footer: =loop.i.metadata.uiSemantics.config.preposition + loop.i.metadata.uiSemantics.config.location
                                icon: f7:arrow_clockwise
                                iconColor: '=items[loop.i.name].state === "ON" || items[loop.i.name].state > 0 ? "green" : "gray"'
                                media-list: true
                                title: =loop.i.metadata.uiSemantics.config.equipment
                                noChevron: true

It looks to me like your structure is a little more convoluted than you need. If you break it down to just the components, your structure is as follows:

f7-block: Base container, fine, but probably not necessary
    oh-repeater: Initial API calling repeater
          oh-list: Actual base component of the widget - only rendered once
                oh-list-item: Main list item with total sensors
                      oh-list: List in the accordion space of the main list item
                            oh-repeater: Why is this here at this point? You are not repeating anything yet
                                  oh-list: This list is now a child of the list above the repeater which doesn't make sense
                                        oh-list-item: This is not what you want repeated at this point, this is just the heading list item so it's only rendered once which limits everything below it as well
                                              oh-list: List in the accordion space of the heading item
                                                    oh-list-item: Actual components that you want filtered and repeated from the source array
                            oh-repeater: Same pattern as above...
                                  oh-list:
                                        oh-list-item:
                                              oh-list:
                                                    oh-list-item:

Based on this breakdown, where things go awry is where you have the secondary repeaters. My guess is that you used the trick of preloading the repeater so that you could get the total number of sensors in the first level list which is correct. Then you tried to do the same thing the second time which isn’t necessary and is what is causing your confusion.

Here’s how I would structure this:

oh-repeater: Initial API calling repeater
    oh-list: Actual base component of the widget - only rendered once
          oh-list-item: Main list item with total sensors
                oh-list: List in the accordion space of the main list item
                      oh-list-item: OK sensors list item (see below to get number)
                            oh-list: List in the accordion space of the OK sensor list item
                                  oh-repeater: This repeater now populates the OK sensor accordion list
                                        oh-list-item: Actual components that you want filtered and repeated from the source array
                      oh-list-item: Warning sensors list item (see below to get number)
                            oh-list: List in the accordion space of the Warning sensor list item
                                  oh-repeater: This repeater now populates the Warning sensor accordion list
                                        oh-list-item: Actual components that you want filtered and repeated from the source array

As I said above, my guess is that you tried to use the second repeater in a way that mimics how the first repeater is used because you wanted to be able to get the number of OK and Warning sensors in the titles of the respective list items. First of all at this point you don’t need to do this, because you already have access to the same array that the repeaters are going to use. Second of all, if you did do this, then you would need to add a THIRD repeater or you get exactly this issue where you are not rendering the whole list, but only the first element of it.

So, how do you get the numbers for OK and Warning from the main array? The same way you are doing it: you don’t need the repeater to apply the filter to the array. Filter is one of the javascript array methods that is available in the widget expressions. It works like this:

array.filter( e => boolean test on variable e ) //<-- Doesn't have to e can be any variable you want to use

In your case you have already developed the filter, it’s the same one you use in the second level repeater to narrow down the elements shown. For for the OK sensors your list item title would be:

title: =loop.allStatusItems_source.filter( e => (dayjs().diff(items[e.name].state,"m") > 60) ).length + " Sensors - OK"
1 Like

Many thanks @JustinG for your detailed explanation - this is very helpful :slight_smile:
I’m doing the preloading as you correctly guess to avoid multiple API calls (I used your explanation in another discussion as reference :slight_smile: )
I managed to get the accordion working, thanks to your help. :+1:
I wasn’t aware how to use the array.filter, that’s the reason I put the repeater at the wrong place :woozy_face:

I have just some questions about the accordion behavior:

  • I can open/close the 1st main accordion (18 Sensors erkannt).
    It is also possible to open either the 2nd OR 3rd accordion, however never both at the same time.
    If 2nd accordion (3 Sensors - Warnung) is expanded and I click on the 3rd (15 Sensors - OK), this accordion gets expanded, but the 2nd gets collapsed automatically.
    Can this behavior being changed?
    Edit: the command I just found accordionItemOpened: true can force to open both lists. However, once I manually close one list, I never get both opened at the same time.

  • I’m wondering whether it is possible to control the behavior, whether the list is expanded or collapsed.
    I would like to always show the items in the Warning List, but hide the OK items by default?
    I didn’t find an option to do this.
    Edit: I just found the command accordionItemOpened: true, which solved this problem :slight_smile:

  • Is it possible to “sort” the list items in the allStatusItems_source array by the .state?

  • I’m wondering about the accordionItem option. Is this only required for f7-list-items, but not used for oh-list-item?

Many thanks an Merry Christmas :christmas_tree:

This is the standard behavior for the f7 lists and as far as I know there is no option to turn it off. There is a somewhat obscure workaround however. Something about vue fragments seems to short-circuit that list feature. So, it’s very simple if you have created the list using a repeater; you just add fragment: true to the repeater config. However, the list you’re referring to is not created by a repeater it’s just the two oh-list-items. So here’s the weird workaround. You can add another repeater that doesn’t do anything except contain the two list items and set use that repeater to set a fragment.

Here’s a demo for you to try. The top list is normal and only allows one accordion open at a time, but the bottom list which is the same except for the addition of the dummy repeater allows each item to be opened and closed separately:

uid: demo
props:
  parameterGroups: []
  parameters: []
tags: []
component: f7-block
config: {}
slots:
  default:
    - component: oh-list
      config:
        accordionList: true
      slots:
        default:
          - component: oh-list-item
            config:
              title: 1
            slots:
              accordion:
                - component: f7-card
                  config:
                    title: A1
          - component: oh-list-item
            config:
              title: 2
            slots:
              accordion:
                - component: f7-card
                  config:
                    title: A2
          - component: oh-list-item
            config:
              title: 3
            slots:
              accordion:
                - component: f7-card
                  config:
                    title: A3
    - component: oh-list
      config:
        accordionList: true
      slots:
        default:
          - component: oh-repeater
            config:
              for: dummy
              in:
               - dummy
              fragment: true
              accordionList: true
            slots:
              default:
                - component: oh-list-item
                  config:
                    title: 1
                  slots:
                    accordion:
                      - component: f7-card
                        config:
                          title: A1
                - component: oh-list-item
                  config:
                    title: 2
                  slots:
                    accordion:
                      - component: f7-card
                        config:
                          title: A2
                - component: oh-list-item
                  config:
                    title: 3
                  slots:
                    accordion:
                      - component: f7-card
                        config:
                          title: A3

Not easily. However, in the same way that you used the filter array method, there is a sort array method. However, I have found sort to be less reliable in the widget expressions and I’m not sure why.

The oh-list-item is just a f7-list-item with many presets and extra built in functions. When you add an accordion slot to an oh-list-item then accordionItem is automatically set on the base f7-list-item so that you don’t have to.

1 Like