A couple of simple oh-repeater examples

I would like to create a widget that list all the items in a group with their default controls (i.e. a switch represented by a switch, or dimmer represented by a slider). This would look similar to the list that pops up when the Action is “Group” on an item, however I want this to be included directly on a UI page.

  • First, I don’t believe any of the standard widgets which support this, correct?
  • Second, if I use oh-repeater - how would I select the appropriate oh--item to display? Is there a “smart” oh--item which can be used to display the appropriate controls based on the item?

Thanks
Jeff

Hi all,
I fail to use numerical comparisons for Number:Dimensionless items in the oh-repeater filter.

The items I want to look at, represent the battery-charge of sensors and are set up as Number:Dimensionless.
From Jython, I can check for low battery using the floatValue():

[...]
        for item in battery_group.getMembers():

            if item.type == "Number:Dimensionless":
                # Battery modelled as fill-percentage
                if item.state.floatValue() < battery_low_warning_at_percent:
                    battery_low_warning.append(u"{} ({})".format(
                                                        battery_group.label,
                                                        item.state))
[...]

In my widget , I just cannot get hold of the numerical value for the item state.

title: =loop.item.label + " " + loop.item.type + " " + loop.item.state

prints out the state as e.g. 25 % and I can do string-machtes for that:

filter: items[loop.item.name].state == "25 %"

But how can I get to the numerical value and do, e.g.:

filter: items[loop.item.name].state <= 10

I tried things like loop.item.state.floatValue() (and other methods I knew of) but to no avail.
I was unable to find any documentation on what functions are supported by the filter-expression.
Is this documented somewhere? If not - wouldn’t that be a helpful addition?

In the widget editor the items object returns strings no matter what the item type because that’s how the information is returned from an api call. So if you want a numerical comparison, you have to convert the string using Number(). In your case you have to strip away the % but, because there’s a space, that’s easy with the split() method.

filter: Number(items[loop.item.name].state.split(" ")[0]) <= 10
2 Likes

@JustinG - thank you!
Is the filter expression evaluated by JavaScript in the end?
So I can use all the possibilities which are there in JS?

The expressions in widgets are not true javascript but jsep, a lightweight javascript-like expression parser:

A small comment (improvement?) - instead of using split, one can use

and with this it will work if you either have a number type or a type with a dimension attached to it … just FYI

2 Likes

parseInt and parseFloat are certainly useful alternatives. I can’t tell you if there’s a performance difference but if there is I suspect it’s minuscule. I prefer Number() in the widgets whenever possible because I like its null handling better for display (personal pet peeve seeing NaN’s in the UI), although as the filter expression is not a displayed value that’s less relevant in this case.

The split(" ") will also intelligently handle “76” vs “76 %” so no worries there.

As it might be interesting for someone else who’s looking for a widget, which will display battery warnings which are modelled as switches as well as battery levels which are modelled as percentages, here is my code.

uid: battery_warnings
tags:
  - battery
props:
  parameterGroups: 
    - description: "Configuration of widget behaviour"
      name: behaviour
    - description: "Widget setup"
      name: setup
  parameters:
    - description: "Only show batteries with warnings [on] or show all batteries [off]"
      groupName: behaviour
      label: Battery warnings only
      name: battWarnOnly
      required: true
      type: BOOLEAN
      default: true
    - description: "Percentage, below which batteries with a percentage indicator are considered as warning"
      groupName: behaviour
      label: Battery warning threshold
      name: battWarnThresh
      required: true
      type: INTEGER
      min: 0
      max: 100
      default: 15
    - description: "Label for battery warning items"
      groupName: setup
      label: Label
      name: itemlabel
      required: true
      type: TEXT
timestamp: Jul 28, 2021, 4:31:08 PM
component: f7-card
config:
  title: Batteriewarnungen
slots:
  default:
    - component: oh-list
      slots:
        default:
          - component: oh-repeater
            config:
              fragment: true
              for: item
              sourceType: itemsWithTags
              itemTags: =props.itemlabel
              filter: items[loop.item.name].state == "ON" || Number.parseInt(items[loop.item.name]) < 15 || ! props.battWarnOnly
            slots:
              default:
                - component: oh-list-item
                  config:
                    icon: '=loop.item.state == "ON" || Number.parseInt(items[loop.item.name].state) < 15 ? "f7:battery_25" : "f7:battery_100"'
                    iconColor: '=loop.item.state == "ON" || Number.parseInt(items[loop.item.name].state) < 15 ? "red" : "green"'
                    title: '=loop.item.type.startsWith("Number") ? loop.item.label + " " + loop.item.state : loop.item.label'
                    item: =loop.item.name

PS: let me know if you spot something, that could be improved - I am always interested to learn more.

3 Likes

Can someone give a hint how to loop through a json Object in the oh-repeater.
I would like to loop show the keys and values items below the repeater.

The item (string) value is like this: {"C812105B04000400":"-93","blt.3.17q3si5345k00":"-48","blt.4.10heul64og400":"-68"}

I’m trying to make something like:
image

I can make something if I completely transform the json, but would like to archieve this without the transformation (or do the transformation in the widget)

uid: widget_bt_tab
tags: []
props:
  parameters:
    - description: Header
      label: log
      name: logArray
      required: false
      type: TEXT
    - context: item
      description: An item to control
      label: BT Item list
      name: item
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Dec 4, 2021, 11:50:08 AM
component: f7-card
config:
  title: '="BT devices state: (" + items[props.item].state + ") " '
slots:
  default:
    - component: oh-list
      slots:
        default:
          - component: oh-repeater
            config:
              for: btitems
              sourceType: array
              fragment: true
              in: '=JSON.parse("[ {\"device\": \"C812105B04000400\",\"strength\":\"-89\"},{\"device\":\"blt.3.17q3si5345k00\",\"strength\":\"-43\"},{\"device\":\"blt.4.10heul64og400\",\"strength\":\"-67\"}]")'
            slots:
              default:
                - component: oh-list-item
                  config:
                    after: ...
                    badge: '=loop.btitems.strength'
                    badgeColor: '=(loop.btitems.strength  > -50) ? "green" : (loop.btitems.strength  <-80) ? "red" :  "yellow"'
                    icon: f7:wifi
                    style:
                      --f7-list-item-after-font-weight: bold
                    title: '= "Device " + loop.btitems.device '

To the best of my knowledge, the expression parser the widgets use is missing a few key pieces to do this easily by staying with the JSON (regex in string.replace(), and array.keys() method off the top of my head). I’d just resort to some simple string manipulation. For the repeater’s array strip the outer braces and use a simple string split:

in: =items.Test_JSONString.state.slice(1,-1).split(",")

which would make each of your loop variables "device name":"strength"

Then use a similar iteration of that to extract either the 0th element or the 1st element and strip the ". So the badge text would be:

badge: =loop.btitems.split(":")[1].slice(1,-1)

and the badge color would be the same but cast to a number for numerical comparisons:

badgeColor: '=(Number(loop.btitems.split(":")[1].slice(1,-1)) > -50) ? "green" : (Number(loop.btitems.split(":")[1].slice(1,-1)) < -80) ? "red" :  "yellow"'

and the device name just using the 0th element:

title: = "Device " + loop.btitems.split(":")[0].slice(1,-1)
1 Like

Great works like a charm!

Hi Jeff, did you find a solution?

I really like the OH3 UI and the standard UI rendering from the semantic model is really great. Especially with the possibility to assign custom widgets to the model items. Works fantastic for the standard page with tabs.

But all that just stops when developing pages and widgets. Is there a way to use the features from the semantic model? Something like.

    - component: oh-list
      slots:
        default:
          - component: oh-repeater
            config:
              for: item
              sourceType: itemsWithTags
              itemTags: Energy
              fragment: true
            slots:
              default:
                - component: default-list-item

Yes, that construction works for getting all items with the various semantic tags. You can even go one step further and add

fetchMetadata: semantics

to the repeater which will give you access to a little more model information including the name of the equipment item a point is a member of

loop.item.metadata.semantics.config.isPointOf

or the name of the location the equipment item is in

loop.item.metadata.semantics.config.hasLocation
1 Like

Thanks Justin. That is actually really nice.

I could not get the location. Is that because the item related to equipment instead of directly within a location?

Yes. The semantic tags do not propagate down the hierarchy so hasLocation only works for equipment not points.

In some cases you may want to add some points to the location group directly, in addition to the equipment. For instance adding a temperature measurement to a room location, because it represents the temperature in the room.

Am I going mad? I can’t seem to search more than one itemTag at a time.

I type itemTags: Smartphone and all Smartphone tagged items are found. But the moment I add a second term I get 0 results itemTags: Smartphone,Smartwatch

All of the examples above that use more than one term just seperate with a comma, but this just isn’t working for me

The array in itemTags is treated as AND. You cannot search for Smartphone OR Smartwatch, but you can search for Switch AND Light or Door AND Status.

Ahhh…
secrets.

Wonder why this simple info isn’t in the docs?

Is there a way to do OR?