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).
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.
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.
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:
Which works well and shows all 4 of the expected group members etc.
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?
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:
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
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:
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:
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.
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.
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:
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.
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):
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):
You get to see the background scrolling which doesnât look great.
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.
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?
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:
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:
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, âŠ