Oh-repeater configuration

Hello all,

I wanted to have a label cell indicate in the “text” the state of “gAllDoors”, namely whether “all closed” or “number of open doors from total”.

Thanks to you, I was able to get an Item state count across a Group with the oh-repeater successfully, but the above seems a bit more involved. I have tried the script bellow, but now I’m wondering whether this can be accomplished the way I formulated it.

component: oh-repeater
config:
  for: door
  sourceType: itemsInGroup
  groupItem: gDSCZoneTrippedUpdate
slots:
  default:
    - component: Label
      config:
        visible: =(loop.door_idx == 0)
        text: "=(items['gAllDoors'].state === 'CLOSED') ? 'All doors closed' :  '=loop.door_source.filter(i => items[i.name].state === 'OPEN').length + '/20 doors open'"

I was expecting to get “All doors closed” when the aggregated state of the “gAllDoors” is “CLOSED”, and when the group state changed to “OPEN”, to have for example “3/20 doors open”. Is this possible or am I completely off the reservation. Any input would be much appreciated.
Many thanks.

I have a widget on my overview page and it tells me how many doors/windows/blind/curtains are open and when they are all closed I get a green padlock.
You can modify it if you want.

component: oh-cell
config:
  action: popup
  actionModal: widget:Status-list-metadata-door-window-blinds
  actionModalConfig:
    item: gDoors
    prop1: DOOR
  footer: Windows Doors Curtains Blinds
  icon: f7:menu
  title: Click to find the open items
slots:
  background:
    - component: f7-block
      config:
        class:
          - card-opened-fade-out
        style:
          bottom: 10px
          position: absolute
          right: 10px
      slots:
        default:
          - component: f7-chip
            config:
              class:
                - margin-left
              iconColor: red
              iconF7: lock_open_fill
              text: Gate
              visible: =items.Gate_opener_status.state === 'OPEN'
          - component: f7-chip
            config:
              iconColor: "=(Number(items.gwindowscount.state) > 0) ? 'red' : 'green'"
              iconMaterial: "=(Number(items.gwindowscount.state) > 0) ? 'window' : 'lock'"
              iconSize: 18
              style:
                --f7-chip-bg-color: rgba(255, 255, 255, 0)
              text: "=(Number(items.gwindowscount.state) > 0) ? items.gwindowscount.state : ''"
              textColor: "=(Number(items.gwindowscount.state) > 0) ? 'red' : 'green'"
              tooltip: Windows
          - component: f7-chip
            config:
              iconColor: "=(Number(items.gdoorcount.state) > 0) ? 'red' : 'green'"
              iconMaterial: "=(Number(items.gdoorcount.state) > 0) ? 'door_front' : 'lock'"
              iconSize: 18
              style:
                --f7-chip-bg-color: rgba(255, 255, 255, 0)
              text: "=(Number(items.gdoorcount.state) > 0) ? items.gdoorcount.state : ''"
              textColor: "=(Number(items.gdoorcount.state) > 0) ? 'red' : 'green'"
              tooltip: Doors
          - component: f7-chip
            config:
              iconColor: "=(Number(items.gcurtaincount.state) > 0) ? 'red' : 'green'"
              iconMaterial: "=(Number(items.gcurtaincount.state) > 0) ? 'vertical_split' : 'lock'"
              iconSize: 18
              style:
                --f7-chip-bg-color: rgba(255, 255, 255, 0)
              text: "=(Number(items.gcurtaincount.state) > 0) ? items.gcurtaincount.state : ''"
              textColor: "=(Number(items.gcurtaincount.state) > 0) ? 'red' : 'green'"
              tooltip: Curtains
          - component: f7-chip
            config:
              iconColor: "=(Number(items.gblindscount.state) > 0) ? 'red' : 'green'"
              iconMaterial: "=(Number(items.gblindscount.state) > 0) ? 'vertical_split' : 'lock'"
              iconSize: 18
              style:
                --f7-chip-bg-color: rgba(255, 255, 255, 0)
              text: "=(Number(items.gblindscount.state) > 0) ? items.gblindscount.state : ''"
              textColor: "=(Number(items.gblindscount.state) > 0) ? 'red' : 'green'"
              tooltip: Blinds

it looks like this:
Screenshot from 2023-09-05 14-23-45

And here is the widget that pops up when you click the cell:
It has oh-repeater code in it.

uid: Status-list-metadata-door-window-blinds
tags: []
props:
  parameters:
    - default: door
      description: Tag group name
      label: Tag name to search
      name: prop1
      required: true
      type: TEXT
    - default: WINDOW
      description: Tag group name
      label: Tag name to search
      name: prop2
      required: true
      type: TEXT
    - default: BLINDS
      description: Tag group name
      label: Tag name to search
      name: prop3
      required: true
      type: TEXT
  parameterGroups: []
timestamp: Aug 11, 2023, 11:11:58 AM
component: oh-list
config:
  style:
    --f7-list-item-after-font-size: 12px
    --f7-list-item-after-font-weight: bold
    --f7-list-item-after-text-color: var(--f7-text-color)
    min-width: 250px
slots:
  default:
    - component: oh-repeater
      config:
        for: index
        fragment: true
        itemTags: =props.prop1
        sourceType: itemsWithTags
      slots:
        default:
          - component: oh-list-item
            config:
              badge: =loop.index.state
              badgeColor: '=(loop.index.state === "OPEN") ? "red" : "green"'
              icon: '=(loop.index.state === "OPEN") ? "f7:lock_open" : "f7:lock"'
              iconColor: '=(loop.index.state === "OPEN") ? "red" : "green"'
              iconUseState: true
              title: =loop.index.label
    - component: oh-repeater
      config:
        for: index
        fragment: true
        itemTags: =props.prop2
        sourceType: itemsWithTags
      slots:
        default:
          - component: oh-list-item
            config:
              badge: =loop.index.state
              badgeColor: '=(loop.index.state === "OPEN") ? "red" : "green"'
              icon: '=(loop.index.state === "OPEN") ? "f7:lock_open" : "f7:lock"'
              iconColor: '=(loop.index.state === "OPEN") ? "red" : "green"'
              iconUseState: true
              title: =loop.index.label
    - component: oh-repeater
      config:
        for: index
        fragment: true
        itemTags: =props.prop3
        sourceType: itemsWithTags
      slots:
        default:
          - component: oh-list-item
            config:
              badge: =loop.index.state
              badgeColor: '=(loop.index.state === "OPEN")||(loop.index.state === "100") ? "red" : "green"'
              icon: '=(loop.index.state === "OPEN")||(loop.index.state === "100") ? "f7:lock_open" : "f7:lock"'
              iconColor: '=(loop.index.state === "OPEN")||(loop.index.state === "0") ? "red" : "green"'
              iconUseState: true
              title: =loop.index.label

It’s the last part of this expression that is the issue. Right now your expression, if you reduce it down to basic parts is

if all doors are closed return the string 'All doors closed',
but if not then return the string '=loop.door_source.filter(i => items[i.name].state === 'OPEN').length + '/20 doors open'

So, the problem is that the second return value is a string that looks like an expression, not a continuation of the original expression itself (the reason you don’t see that as the result in the label si that you’re missing one more closing ' in the expression so it’s throwing an error instead of finishing the evaluation).

You don’t want this whole return value to be a string, you want it to be the result of the filter method tacked on to a string. So, you don’t need the '...' around the whole return value, and you don’t want the = in front of it. This would be one option:

text: "=(items['gAllDoors'].state === 'CLOSED') ? 'All doors closed' :  loop.door_source.filter(i => items[i.name].state === 'OPEN').length + '/20 doors open'"

This is a fairly common use case, however, and there is a typical solution that you may find preferable. OH has a built-in way of handling this easily. Like you I have a group that tells me if any door is open (gPortals) by using an aggregation function. I have a second group that contains all the same door contacts (gPortal_Count), but I use a different aggregation function:


By treating the member base type as a Number the aggregation function gets 0 if a door is closed and 1 if a door is open, so the sum of those numerical states is the count of how many doors are open.

With this setup you don’t need the repeater for your label, it would just looks like this:

- component: Label
  config:
    text: "=(items['gAllDoors'].state === 'CLOSED') ? 'All doors closed' :  items[`gAllDoors_Count`].state + '/20 doors open'"

This is awesome! Thank you both for responding so quickly.
Greg, many thanks for sharing your widget and the oh-repeater examples. I can definitely use that as a template for some of my own applications.
Justin, your detailed response is much appreciated. Thanks for breaking down the whole expression. My syntax was way off…but I can definitely learn from the mistake.
Interestingly, I started out with Group aggregation as well, however I was trying to use the COUNT function across the Group, which apparently is not working in OH3. I was primarily using the file-based configuration without success. As a test, I tried configuring the Group through the Main UI and noticed the COUNT function is not listed in the drop-down menu. Seems like SUM is the way to go, and maybe not as convoluted. That said, it’s great to have alternatives…
Thanks again!