OH4: Set variable based on result of oh-repeater

Hi All

Wondering if there is a clever way to assign a value to a variable based on the result of an oh-repeater? Want to be able to use the result of the oh-repeater outside of the component (to hide or not the block with the repeater in it).

          - component: f7-block
            config:
              class: roll_block
              style:
                background: var(--paradox-background-color)
                border-radius: 5px
                left: 0px
                margin: 0
                position: absolute
                top: 45px
                width: 300px
                z-index: 0
              visible: =(items[props.partitionExitDelayGroup].state === "ON")
            slots:
              default:
                - component: oh-repeater
                  config:
                    for: PartitionExitDelay
                    fragment: true
                    groupItem: =props.partitionsGroup
                    sourceType: itemsInGroup
                  slots:
                    default:
                      - component: oh-repeater
                        config:
                          filter: ({'ON':1,'-':1})[loop.exitDelay.filter]
                          for: exitDelay
                          fragment: true
                          in:
                            - filter: =items[loop.PartitionExitDelay.name.slice (0,10) + "_Partition_In_Exit_Delay"].state
                              title: =items[loop.PartitionExitDelay.name].state + " Exit Delay"
                          sourceType: array
                        slots:
                          default:
                            - component: Label
                              config:
                                large: true
                                raised: true
                                style:
                                  background: var(--paradox-background-color)
                                  color: var(--paradox-font-color)
                                  flex: 0 0 420px
                                  font-size: 30px
                                  font-weight: 700
                                  top: 45px
                                  white-space: nowrap
                                  width: 300px
                                text: =loop.exitDelay.title

So I would like to set the value of the variable based on whether nested repeater produces any content?

Thanks
Mark

Not really. At present the only way to assign variables is with an action.

You can do this, however, by adding yet another repeater. It won’t even have a significant impact on the widget performance because you can still keep everything to just the 1 API call.

The basic idea is that you run your group repeater before the block you want to control so that you have your result. Then you just need three tricks 1) restrict the output of that repeater to only a single iteration so that you don’t produces a whole bunch of extra components, 2) use the source array that the repeaters produces instead of the individual elements, 3) set a repeater you actually want to have repeating to use the source array from the first repeater instead of making the API call a second time.

Here’s an example where I wanted to have the number of items in a list displayed above the list itself.

          - component: oh-repeater
            config:
              for: controllerCount
              fragment: true
              groupItem: gControlSensors
              sourceType: itemsInGroup
              fetchMetadata: SensorControl
              key: =Math.random() + @@'Widget_Settings_Sensor_Controller_Detail' + @@'Widget_Settings_Sensor_Controllers'
            slots:
              default:
                - component: f7-block-title
                  config:
                    visible: =(loop.controllerCount_idx==0)
                  slots:
                    default:
                      - component: Label
                        config:
                          text: =loop.controllerCount_source.length + " Sensor Controllers"
                - component: f7-list
                  config:
                    visible: =(loop.controllerCount_idx==0)
                    mediaList: true
                    style:
                      margin: 0
                      padding: 0
                      width: 100%
                  slots:
                    default:
                      - component: oh-repeater
                        config:
                          for: controller
                          fragment: true
                          sourceType: array
                          in: =loop.controllerCount_source
                        slots:
                          default:
                            - component: oh-list-item
                              config:
                                action: navigate
                                actionPage: page:settings_sensorcontrol_details
                                actionPageDefineVars:
                                  controller: =loop.controller.name
                                badge: "=(loop.controller.metadata.SensorControl.value == 'OFF') ? 'DISABLED' : ((items[loop.controller.name].state == 'OFF') ? 'Off' : 'On')"
                                badgeColor: "=(loop.controller.metadata.SensorControl.value == 'OFF') ? 'gray' : ((items[loop.controller.name].state == 'OFF') ? 'red' : 'green')"
                                footer: =loop.controller.name
                                title: =loop.controller.label.split('-').at(-1)

The first repeater (for: controllerCount) is just a regular repeater to fetch all the information I want to use later on. Both children of that repeater have an extra definition for visibility:

visible: =(loop.controllerCount_idx==0)

So they will both only be rendered on the first iteration of the repeater’s loop. But, during that iteration, we have access to the loop.controllerCount_source variable which is the complete array of returned information so I can get the length of that array to use in my label. Then to get all the results in a list, I need a second repeater, but I already have all the information in that _source array so I can just feed that into the second repeater (for: controller) and it’s results will be identical to the original but it will go through each element to add items to the list.

2 Likes

Hi Justin

Thanks for the information. Lots to digest. But has given me an idea to try.

My end goal - which I should have included in the original question I guess is:

I have two f7-blocks

The first is displayed all the time, but is overridden if the second clock has content.

Currently I achieve this with contra visible: properties on each block.

My idea now if to have the first block with no visible: property

The second f7-clock inside the oh-repeater, with a visible: property based on whether the repeater has content?

Hopefully will get somehere, but as usual your ideas are a great help

So after your great suggestions I realized I am actually rerunning oh-repeaters for the same information multiple times. And to achieve what I am trying to I need to put the f7-blocks inside the oh-repeater.
As a first step in migrating I got to this:

          - component: oh-repeater
            config:
              for: PartitionList
              fragment: true
              groupItem: =props.partitionsGroup
              sourceType: itemsInGroup
            slots:
              default:
                - component: f7-block
                  config:
                    visible: =(loop.PartitionList_source.length > 0) && (loop.PartitionList_idx==0)
                    style:
                      background: var(--paradox-background-color)
                      border-radius: 5px
                      display: flex
                      height: 45px
                      justify-content: center
                      left: 0px
                      margin: 0
                      overflow: hidden
                      position: absolute
                      top: 45px
                      width: 300px              
                  slots:
                    default:              
                      - component: oh-repeater
                        config:
                          for: partitionState
                          fragment: true
                          sourceType: array
                          in: =loop.PartitionList_source
                        slots:
                          default:              
                            - component: Label
                              config:
                                class: scroll_text
                                large: true
                                raised: true
                                style:
                                  background: var(--paradox-background-color)
                                  color: var(--paradox-font-color)
                                  flex: 0 0 450px
                                  font-size: 30px
                                  font-weight: 700
                                  top: 0px
                                  white-space: nowrap
                                  width: 420px
                                text: =items[loop.partitionState.name].state + " - " + items[loop.partitionState.name.slice (0,10) + "_Detailed_Partition_State"].state + "***" + loop.PartitionList_source.length

Which works well and shows all 4 of the expected group members etc.

States

I then realized I actually want to control the visible: of the block based on the partitionState, so I have tried to move the f7-block inside the second oh-repeater, this now produces only ONE result .

I can’t see what I need to change to get it working and why the re-arrange of the components breaks the result?

          - component: oh-repeater
            config:
              for: PartitionList
              fragment: true
              groupItem: =props.partitionsGroup
              sourceType: itemsInGroup
            slots:
              default:
                - component: oh-repeater
                  config:
                    for: partitionState
                    fragment: true
                    sourceType: array
                    in: =loop.PartitionList_source
                  slots:
                    default:               
                      - component: f7-block
                        config:
                          visible: =(loop.partitionState_source.length > 0) && (loop.partitionState_idx==0)
                          style:
                            background: var(--paradox-background-color)
                            border-radius: 5px
                            display: flex
                            height: 45px
                            justify-content: center
                            left: 0px
                            margin: 0
                            overflow: hidden
                            position: absolute
                            top: 45px
                            width: 300px              
                        slots:
                          default:              
                            - component: Label
                              config:
                                class: scroll_text
                                large: true
                                raised: true
                                style:
                                  background: var(--paradox-background-color)
                                  color: var(--paradox-font-color)
                                  flex: 0 0 450px
                                  font-size: 30px
                                  font-weight: 700
                                  top: 0px
                                  white-space: nowrap
                                  width: 420px
                                text: =items[loop.partitionState.name].state + " - " + items[loop.partitionState.name.slice (0,10) + "_Detailed_Partition_State"].state + "***" + loop.PartitionList_source.length

States2

Any ideas?
Fully Code
Yaml.txt (9.6 KB)

Added _source.lenght and both return 4 as expected.

Thanks
Mark

EDIT: I see the results are their, but not displayed?

PartitionList and partitionState are the same array. You are just iterating the array twice and the first time you don’t care about any iteration but the first one because you are only interested in the length of the array. It doesn’t make sense that you want one and not the other.

Structurally it doesn’t make sense to have the second repeater right after the other because the only reason to nest the two repeaters as in the example is that you have a component between them that isn’t involved in the inner repeater loop.

You are now getting only one result because your modified visible property includes (loop.partitionState_idx==0). This means that any iteration of the partitionState loop after the first one will not be rendered.

I suggest going back to your first attempt where the structure is correct. If there’s something about that that isn’t working as expected try to find a solution with that structure.

Thanks Justin. You are right. I think I can construct the entries I am looking for with just one oh-repeater for this block, however the second block, which is the one I want to hide if there are no entries needs a bit more, so I was trying to keep the two similar, so maybe some help with that, as it suffers the same (or similar) issue.
I think part of the issue is trying to get away with only a single group listing all the Partitions, and then constructing the detailed items from that:

          - component: oh-repeater
            config:
              for: PartitionList
              fragment: true
              groupItem: =props.partitionsGroup
              sourceType: itemsInGroup
            slots:
              default:
                - component: oh-repeater
                  config:
                    filter: ({'OFF':1,'-':1})[loop.exitDelay.filter]
                    for: exitDelay
                    fragment: true
                    in:
                      - filter: =items[loop.PartitionList.name.slice (0,10) + "_Partition_In_Exit_Delay"].state
                        title: =items[loop.PartitionList.name].state + " Exit Delay"
                    sourceType: array
                  slots:
                    default:
                      - component: f7-block
                        config:
                          class: roll_block
                          style:
                            background: var(--paradox-background-color)
                            border-radius: 5px
                            left: 0px
                            margin: 0
                            position: absolute
                            top: 45px
                            width: 300px
                          visible: =(loop.exitDelay_source.length > 0) && (loop.exitDelay_idx==0)
                        slots:
                          default:
                            - component: Label
                              config:
                                large: true
                                raised: true
                                style:
                                  background: var(--paradox-background-color)
                                  color: var(--paradox-font-color)
                                  flex: 0 0 420px
                                  font-size: 30px
                                  font-weight: 700
                                  top: 45px
                                  white-space: nowrap
                                  width: 300px
                                text: =loop.exitDelay.title + "-" + loop.exitDelay_source.length

I have removed the extra “block” in this code as it makes it easier to test and read etc. But if I hide the block in this one I see the other, as I need to do. Just need to get the results to work.

So my logic here is:

  • Get list of Possible Partitions using oh-repeater based on a common group
  • Construct a list of interesting items based on the result of the first oh-repeater and filtering that list only for items which are ON, using OFF here for testing to ensure I get a result.
  • Display the results of this oh-repeater in an f7-block with the f7-block hidden if there are no items of interest. visible: =(loop.exitDelay_source.length > 0)

I cannot see how to achieve this without a 2nd oh-repeater other than having an additional group and filtering that for the ON/OFF as required.

Do you think this is feasible or am I on a highway to nothing


From what you’ve laid out here, I don’t see why you need two repeaters. I think I’m not following what you mean by your second bullet point. From the code, it doesn’t look like you’re doing anything at all with the results of the first repeater except to then use the second one to filter them, but why can’t you just do that with a filter on the first repeater? Right now your second repeater doesn’t accomplish any actual repeating, you just give it a 1 element array but the filtering and conversions could be done in the original repeater or the other components.

Sometimes it helps to not just have the logic sketched out, but to put the logic on an outline of the structure you’re trying to build without all the extra pieces. That often helps me. The snippet above would have started something like this:

Label - Number of lines in the list <-- No way to get the length of the repeater results outside the repeater
List - Hold the list items
  Repeater - Collect the information for the list items
    List item - Display the information

Because of the issue with the label not having access to the repeater array the outline has to change:

Repeater - Collect the information for the list items
  Label - Number of lines in the list <-- Only need 1 iteration of label
  List - Hold the list items <-- Only need 1 iteration of List
    Repeater - Reuse the original repeater array to avoid 2nd API call
      List item - Display the information

If you can’t articulate why an element is in the outline then you probably don’t need it.

Thanks for your patience. I thought I could justify the need to have the 2nd oh-repeater, but was overlooking the possibility to apply the required filter to the first ```oh-repeater, and in fact “construct” the interesting items from there. Thanks to your patience I have now got an almost working solution, but I am having issues with the filter - as soon as I add it I, lose all my results. This filter was working before to get just the interesting “Partitions in Exit Delay”, but no longer.

I have double checked that the constructed filter expression is correct and produces the correct state, but I cannot see why it does not work?

So with no filter I now get the following, which is all of the PartitionX_Partition_In_Exit_Delay (they are all OFF

image

All No Filter

My previously working filter was:

              filter: ({'OFF':1,'-':1})[loop.exitDelayCount.filter]
              in:
                - filter: =items[loop.exitDelayCount.name.slice (0,10) + "_Partition_In_Exit_Delay"].state
                  title: =items[loop.exitDelayCount.name].state + " Exit Delay"

By forcing a display of =loop.exitDelayCount.name.slice (0,10) + "_Partition_In_Exit_Delay" which is the item name of what I am interested in I get the correct name:

<div fragment="1318d8dbb78" style="background: var(--paradox-background-color); color: var(--paradox-font-color); flex: 0 0 420px; font-size: 30px; font-weight: 700; top: 45px; white-space: nowrap; width: 300px;">
  Partition1_Partition_In_Exit_Delay
</div>

And if I check the .state I also get OFF as expected.

<div fragment="14bc6823055" style="background: var(--paradox-background-color); color: var(--paradox-font-color); flex: 0 0 420px; font-size: 30px; font-weight: 700; top: 45px; white-space: nowrap; width: 300px;">
  OFF
</div>

When I apply the filter though I end up with no results at all. Even in the “inspector” I don’t see anything.

My code with the filter applied is as follows:

          - component: oh-repeater
            config:
              groupItem: =props.partitionsGroup
              sourceType: itemsInGroup
              for: exitDelayCount



              filter: ({'OFF':1,'-':1})[loop.exitDelayCount.filter]
              in:
                - filter: =items[loop.exitDelayCount.name.slice (0,10) + "_Partition_In_Exit_Delay"].state
                  title: =items[loop.exitDelayCount.name].state + " Exit Delay"



              
              fragment: true
            slots:
              default:
                - component: f7-block
                  config:
                    visible: =(loop.exitDelayCount_source.length > 0) && (loop.exitDelayCount_idx==0)
                    class: roll_block
                    style:
                      background: var(--paradox-background-color)
                      border-radius: 5px
                      left: 0px
                      margin: 0
                      position: absolute
                      top: 45px
                      width: 300px
                  slots:
                    default:
                      - component: oh-repeater
                        config:
                          for: exitDelayList
                          fragment: true
                          sourceType: array
                          in: =loop.exitDelayCount_source
                        slots:
                          default:
                            - component: Label
                              config:
                                large: true
                                raised: true
                                style:
                                  background: var(--paradox-background-color)
                                  color: var(--paradox-font-color)
                                  flex: 0 0 420px
                                  font-size: 30px
                                  font-weight: 700
                                  top: 45px
                                  white-space: nowrap
                                  width: 300px
                                text: =items[loop.exitDelayList.name].state + " Exit Delay" + "-" + loop.exitDelayCount_source.length

Would appreciate any further advise.

Thanks
Mark
EDIT: Also checked the results more fully:

text: =loop.exitDelayCount.name.slice (0,10) + "_Partition_In_Exit_Delay" + items[loop.exitDelayCount.name.slice (0,10) + "_Partition_In_Exit_Delay"].state

Produces the required:
image

Your expression for filter requires loop.exitDelayCount.filter, but that only existed when you were using the sourceType: array (because that is the only repeater source that references the in property). When you change sourceType to itemsInGroup, your array goes back to being an array of items, none of which have a filter property, so the expression is always false and therefore all elements are filtered out of the array.

If you wish to convert your elements of your item array into different objects you would use the map property of the repeater:

map: ({"filter": items[loop.exitDelayCount.name.slice (0,10) + "_Partition_In_Exit_Delay"].state, "title": items[loop.exitDelayCount.name].state + " Exit Delay"})

but in that case your filter expression still wouldn’t work because the array is filtered before the map is applied. So the filter expression must use the basic array (in this case the item array) but you already know how to do that because that’s just what the filter part of your object is. Then you don’t even need the object anymore because the only value you’re interested in outside the repeater definition itself is the title which you can put in the map.

          - component: oh-repeater
            config:
              groupItem: =props.partitionsGroup
              sourceType: itemsInGroup
              for: exitDelayCount
              filter: ({'OFF':1,'-':1})[items[loop.exitDelayCount.name.slice (0,10) + "_Partition_In_Exit_Delay"].state]
              map: items[loop.exitDelayCount.name].state + " Exit Delay"
              fragment: true

now in the children of the widget you don’t even need loop.exitDelayCount.title, you just use loop.exitDelayCount because you’ve already done the conversion.

Hey Justin.

Thank you so much. That seems to work exactly as I wanted. I think I understand the explanation and reasoning - so thanks for the detailed information. I don’t think I would ever have found that on my own.

Will test with proper “data” later this evening when I can arm/disarm alarm etc. without disrupting everyone in my household.

On a slightly different track - how do you test expressions in an oh-repeater? The Widgets Expression Tester doe snot seem to be able to access the loop. etc?

Wish I had your level of knowledge and experience.

Correct. the expression test exists in its own context which means it doesn’t have the repeater loops of your actual widget. So the answer here is just a different form of the standard “log it out” In this case there’s no log but you can print whatever you need on the widget itself with a Label component. So, when it doubt, add a quick label and put your expression in that so see the results. With a repeater, you’ll get lots of labels, but that’s OK because that will also show you that the expression works as expected across the loop.

Thanks Justin, Your help has gotten me 95% where I wanted to be.
I have actually been using the “log it out” policy using Labels, occasionally get a roadblock where the label is hidden for some reason, but that adds to the challenge I guess.

By 95% of the way, I am getting this now:
Both Roll and Scroll
The the first (or bottom) block is still visible and shows up briefly during the amination of the second (top) block.

My original plan was to be able to hide the bottom block using the visible: =(loop.exitDelayCount_source.length < 1) expression. So if the top block has content then the bottom block is hidden.

I cannot add teh first block inside the oh-repeater for the second block, because I am filtering the source for just items of interest there.

That was why I was trying with two repeaters for each block.

I am now trying to see if I can fix the animation to “hide” the first block somehow.

Do you have any other suggestions?

The current working (95%) code is:

          - component: oh-repeater
            config:
              for: PartitionList
              fragment: true
              groupItem: =props.partitionsGroup
              sourceType: itemsInGroup
            slots:
              default:
                - component: f7-block
                  config:
                    style:
                      background: var(--paradox-background-color)
                      border-radius: 5px
                      display: flex
                      height: 45px
                      justify-content: center
                      left: 0px
                      margin: 0
                      overflow: hidden
                      position: absolute
                      top: 45px
                      width: 300px
                    visible: =(loop.PartitionList_source.length > 0) && (loop.PartitionList_idx==0)
                  slots:
                    default:
                      - component: oh-repeater
                        config:
                          for: partitionState
                          fragment: true
                          in: =loop.PartitionList_source
                          sourceType: array
                        slots:
                          default:
                            - component: Label
                              config:
                                class: scroll_text
                                large: true
                                raised: true
                                style:
                                  background: var(--paradox-background-color)
                                  color: var(--paradox-font-color)
                                  flex: 0 0 450px
                                  font-size: 30px
                                  font-weight: 700
                                  top: 0px
                                  white-space: nowrap
                                  width: 420px
                                text: =items[loop.partitionState.name].state + " - " + items[loop.partitionState.name.slice (0,10) + "_Detailed_Partition_State"].state + "***" + loop.PartitionList_source.length
                            - component: oh-link
                              config:
                                large: true
                                raised: true
                                style:
                                  background: var(--paradox-background-color)
                                  color: var(--paradox-font-color)
                                  font-size: 30px
                                  font-weight: 700
                                  position: absolute
                                  top: 0px
                                  width: 300px
                                text: "=(props.mask && vars.pincode) ? props.mask.repeat(vars.pincode.length || 0) : vars.pincode"
          - component: oh-repeater
            config:
              groupItem: =props.partitionsGroup
              sourceType: itemsInGroup
              for: exitDelayCount
              filter: ({'OFF':1,'-':1})[items[loop.exitDelayCount.name.slice (0,10) + "_Partition_In_Exit_Delay"].state]
              map: items[loop.exitDelayCount.name].state + " Exit Delay"
              fragment: true
            slots:
              default:
                - component: f7-block
                  config:
                    visible: =(loop.exitDelayCount_source.length > 0) && (loop.exitDelayCount_idx==0)
                    class: roll_block
                    style:
                      background: var(--paradox-background-color)
                      border-radius: 5px
                      left: 0px
                      margin: 0
                      position: absolute
                      top: 45px
                      width: 300px
                  slots:
                    default:
                      - component: oh-repeater
                        config:
                          for: exitDelayList
                          fragment: true
                          sourceType: array
                          in: =loop.exitDelayCount_source
                        slots:
                          default:
                            - component: Label
                              config:
                                large: true
                                raised: true
                                style:
                                  background: var(--paradox-background-color)
                                  color: var(--paradox-font-color)
                                  flex: 0 0 420px
                                  font-size: 30px
                                  font-weight: 700
                                  top: 45px
                                  white-space: nowrap
                                  width: 300px
                                text: =loop.exitDelayList + loop.exitDelayCount_source.length

Full code: YAML-Blocks.txt (9.2 KB)

If I understand your issue correctly, then I think you may be back to css and not widget structure. You want the second animation to start after the first animation, that sounds like adding an animation-delay to your second animation definition:

but you want to not see that second block at all until the animation starts. In that case, you can set the initial visibility of the element to hidden and then add visibility: visible to the keyframes of your animation which will override that initial setting.

Maybe something like this (you’ll want to adjust the timing of the delay, I suspect):

    .roll_block {
      display: block;
      overflow: hidden;
      animation: move_roll 4s linear 2s infinite;
      visibility: hidden;
    }

    @keyframes move_roll {
      from {
        visibility: visible;
        transform: translateY(25px);
      }
      to { 
        transform: translateY(calc(-100% + 25px));
      }
    }

Thanks again Justin.

Not quite, so let me try explain a bit better.

The First Block with the scrolling runs all the time and just displays the current state of all the partitions. So it is active all the time.

The second block only has content when the one or more Partitions are in Exit Delay. In that case the roll-block should override the scroll block (block 1) and block 1 should not be visible at all until the Partitons in Exit Delay are back to OFF and Block 2 no longer has content. Block 1 should then be visible again.

What is happening now is (had to slow down the roll to capture):
image

You get to see the background scrolling which doesn’t look great.

Hope that makes sense?

I think I follow now. Let’s see if we can outline it:
Basic structure:

Widget root
  Title
  Rolling current state of partitions <-- Only if no Exit Delay
  Scrolling list of Exit Delays <-- Only if Exit Delays

At a minimum, a first basic pass at an outline might be:

f7-block #Basic root element
  f7-block #Title container
    Label #Title
  f7-block #Rolling current state of partitions: Only if no Exit Delay
    oh-repeater #Loop through each partition
      Label #Some info about partition
  f7-block #Scrolling list of Exit Delays: Only if Exit Delay
    oh-repeater #Loop through each Exit delay
      Label #Some info about exit delay

Ok, but now we have another iteration of the problem that started this whole thread that we have two f7-blocks which require information they just don’t have. The f7-blocks need access to the information about the number of partitions in an exit delay state but that information is only available to children of the the exit delay repeater. There are a couple of different ways to get this information to the f7-blocks. To do it entirely within the widget code requires the stacked repeater trick. Since the information you want is in the exit delay repeater, that is the one that must be duplicated:

f7-block #Basic root element
  f7-block #Title container
    Label #Title
  oh-repeater (exit-delay-count) #Loop through each Exit delay and filter out any elements that are not in an exit delay
    f7-block #Rolling current state of partitions: Only display if exit-delay-count length =0 and exit-delay-count index =0
      oh-repeater #Loop through each partition
        Label #Some info about partition
    f7-block #Scrolling list of Exit Delays: Only display if exit-delay-count length > 0 and exit-delay-count index =0
      oh-repeater (exit-delay-info) #Reuse the (exit-delay-count array)
        Label #Some info about exit delay

I would probably simplify the logic there with the addition of one extra level that does the index=0 check for all the things at once:

f7-block #Basic root element
  f7-block #Title container
    Label #Title
  oh-repeater (exit-delay-count) #Loop through each Exit delay and filter out any elements that are not in an exit delay
    div (or f7-block with margin and padding 0) #Only visible if exit-delay-count index=0
      f7-block #Rolling current state of partitions: Only display if exit-delay-count length =0
        oh-repeater #Loop through each partition
          Label #Some info about partition
      f7-block #Scrolling list of Exit Delays: Only display if exit-delay-count length > 0
        oh-repeater (exit-delay-info) #Reuse the (exit-delay-count array)
          Label #Some info about exit delay

That’s not the only solution to the exit delay information problem, however, and if you want to make the widget code much simpler then you can manufacture the exit delay information outside the widget and just utilize that. You could add all your “_Partition_In_Exit_Delay” items (which appear to be switches?) to a Exit_Delay_Alarm Group Item with an aggregation function so that the group is ON if any one of the items is ON or whatever the most appropriate logic is. Or make a rule which sets an Exit_Delay_Alarm Switch Item if any of the partitions are in exit delay. However you want to do it, as long as you have that information in its own simple item, you can return to the original form of the outline and just use the state of that item:

f7-block #Basic root element
  f7-block #Title container
    Label #Title
  f7-block #Rolling current state of partitions: Only if Exit_Delay_Alarm.state == 'OFF'
    oh-repeater #Loop through each partition
      Label #Some info about partition
  f7-block #Scrolling list of Exit Delays: Only if Exit_Delay_Alarm.state == 'ON'
    oh-repeater #Loop through each Exit delay
      Label #Some info about exit delay

Thanks Just. I had it working like that, but wanted to try make the Widget as self contained as possible, with only one external group - which is what sent me down this rabbit hole.

I have a “working” version of this, which I like, however there is one issue.
It seems that if the first oh-repeater has no elements then nothing happens in the children either (so the block that I would like visible when (loop.exitDelayCount_source.length==0) is not displayed? Even just displaying a Label with text: ="Exit Delay Source Length" + loop.exitDelayCount_source.length does not happen.

Empty Repeater

I checked that the first block still works by changing the filter on the first repeater and the visible condition for the block and the content works.

Not sure if this is expected behavior for the child of an empty oh-repeater? I guess it could be
, which would be unfortunate for me in this case. In which case I would have to fall back to the external group option I guess?

Thanks again.

EDIT: Could this be something to do with my filter expression? To be honest I am not 100% sure exactly how it works? Especially {'ON':1,'-':1}, which filters for ON and ‘-’ but what do the “1” mean?

filter: ({'ON':1,'-':1})[items[loop.exitDelayCount.name.slice (0,10) + "_Partition_In_Exit_Delay"].state]

As with most filter methods is various languages, the filter for a javascript array is expected to return true if you want to keep the value in the array and false if you want to filter it out. Usually, that means that the filter method takes some comparison function that outputs a boolean result e.g. (x) => x >= 3. But it doesn’t have to be a comparison function it just has to be something that returns a true value or a false value. JS like many languages is fairly lax in what it considers a true value or false value. false, 0, undefined, and "" are all examples of “falsy” values which means JS will consider them false if they are encountered where it expects a boolean. Any non-zero, non-empty value is then taken to be true. So if your filter function returned “cucumber” for an element in some array that element would be kept in the array because “cucumber” is a non-zero, non-empty value.

In your specific case, it was never clear to me exactly what the range of possible values for your exit delay items is. Again, I assume they are switches, but I don’t know, so I never bothered to address the filter expression. But, the filter expression in general does not have to take this form and, in fact, in this case this form is probably complete overkill. But, just so you understand, here’s what’s going on.

In JS {...} defines an object. You’re using all sorts of object all the time in these widgets even if your not aware of it. Really object just means "some data structure with sub-parts that you can access individually by some name (“key”). JS is very flexible and the value that key points to can be any type of data structure itself, including another object with it’s own keys.

For example, items is a special kind of object in these widgets which holds data about the states of OH items and each item name is a “key” that allows you to access the state and formatted state for that item. There are two syntax options for using a key to get a value from an object. If you know the exact key then you can just use the dot notation: items.some_item_name. But sometimes the key you need is dynamic or has to be constructed from some other values. In that case you use the bracket notation: items["some_item" + suffixVariable].

The first part of your expression, {'ON':1,'-':1}, you are defining an object. This object has two keys: ‘ON’ and ‘-’. The value of the ON key is 1 and the same is true for the - key. If this object had a name, say filterObj, then we could use either of the notation options to get these values: filterObj.ON would return 1. However, in the widget expression we cannot make named variables, but that doesn’t matter we can still access the object directly and ({'ON':1,'-':1}).ON will also return 1. Your expression uses the [...] notation instead, because the key you are testing is dynamic: the state of some item. That’s OK, because the state of an item can possible evaluate to ON. So if some_item has ON for a state then ({'ON':1,'-':1})[items.some_item.state] will also return the value of the ON key (1). 1 is a non-zero, non-empty value so the filter expression evaluates to true and that array element is kept in the filtered array.

But, what happens if you try to access a key that isn’t defined in the object e.g. ({'ON':1,'-':1}).Walrus? That is, of course, undefined. But that’s OK, because undefined is one of our falsy values, so if this is a filter expression it will know to filter that element out of the array.

So, in the end your expression has the 1’s in it because the call to the object just has to return some non-zero, non-empty value. They could just as easily be 10, or 'Abraham Lincoln' but 1 is a little more concise. As for the - key, well, the items object is special. There’s a bunch of underlying code that prevents it from returning undefined. If you request the state of an item that doesn’t currently have a meaningful state you will get - as a result instead of undefined. So, that is a possible return value that you may or may not want to account for if you are using this object notation as your fitler.

Again, in this case, all of that is probably much more than you need. If your items really are just switches than all you are interested is is if they are ON or not ON. So your expression really could just be:

filter: items[loop.exitDelayCount.name.slice (0,10) + "_Partition_In_Exit_Delay"].state == 'ON`

Oops. You’re absolutely right and I should have caught that. A zero length array result means the repeater iterates zero times. Fortunately that only changes things a little. All this means is that 1) instead of filtering the exit delay items in the initial repeater you move the filter down to the second exit delay repeater and 2) you need to do a little more in the partition block to the status of the exit delay array.

The modified outline might look like this:

f7-block #Basic root element
  f7-block #Title container
    Label #Title
  oh-repeater (all-exit-delay-items) #Loop through each Exit delay and DO NOT filter out any elements
    f7-block #Rolling current state of partitions: Only display if all-exit-delay-items index =0 and if all elements in all-exit-delay-items ='OFF'
      oh-repeater #Loop through each partition
        Label #Some info about partition
    f7-block #Scrolling list of Exit Delays: Only display if all-exit-delay-items index =0 and if any elements in all-exit-delay-items ='ON'
      oh-repeater (exit-delay-info) #Reuse the (all-exit-delay-items array) and filter the array
        Label #Some info about exit delay

As for the new checking you’ll need to use the JS array methods some() and every(). Both of these array methods take a function parameter. They then pass each element of the array into the function. The every() method returns true only if the test function returns true for ALL the elements, and the some() method returns true if the test function is true for ANY ONE of the array elements. So the expression to represent the statement in the outline that says “if all elements in all-exit-delay-items =‘OFF’” would look like this:

loop.all-exit-delay-items_source.every( (x) => items[x.name].state == 'OFF')

and the complimentary test would be:

loop.all-exit-delay-items_source.some( (x) => items[x.name].state == 'ON')

Hey Justin.
Looks like your explanation got truncated?
I am hoping my filter is the problem?
Thank you.

These items are switches. So should be ON or OFF

Yeah, sorry. I hit some strange keystroke combo by accident that submitted my response in the middle of typing. Still not sure what it was.

The answer should be complete now.

1 Like

EDIT: Have tried to give it a go and to swap my Groups and construct the Partition State items from the Exit_Delay group instead.

Thank you Justin. Sorry for delay in response. Been a busy day at work and trying to work through all the information.

I think I understand and agree the simpler the better. One of the drawbacks of trying to find ideas via searches.

I have tried to implement this way and then realized that this way requires a Group for Partitions_In_Exit_Delay, which is what I am trying to avoid. I was hoping to be able to construct this information from the group with Partition_State, which is what I use everywhere else.

The only Group I have is props.partitionsGroup and up till now have been able to construct the Exit_Delay items from that.

If I have to add the extra group it would be easier to go back to my original code using that Group to hide (or not) the two blocks.

So unless I can construct the Exit_Delay information from props.partitionsGroup I think I am stumped?

The props.partitionsGroup group cancontain Items as follows:

Partition1_PartitionState
Partition2_PartitionState
Partition3_PartitionState
Partition4_PartitionState

up to...

Partition8_PartitionState

My system goes to 4, and I have added a dummy 5 to test with, but not linked to live data.

These states can be: Armed, Armed Stay, Disarmed, 


The Exit_Delay items look like:

Partition1_Partition_In_Exit_Delay
Partition2_Partition_In_Exit_Delay
Partition3_Partition_In_Exit_Delay
Partition4_Partition_In_Exit_Delay

these are ON or OFF

Hoping against hope you have some clever idea?

Not a total loss as I have learnt so much

Mark