openhab / openhab-webui

Web UIs of openHAB
Eclipse Public License 2.0
224 stars 239 forks source link

[MainUI] Add new syntax for common expressions-- #1851

Open rkoshak opened 1 year ago

rkoshak commented 1 year ago

I don't know if this is even remotely possible or not. If not I'll gladly close the issue.

The problem

A common use case is to set up an expression with a chain of ternary operators to select an icon or color in a widget based on the state of the Item. For example:

iconColor: '=(Number.parseFloat(items[props.item].state) > 75) ? "red" : (Number.parseFloat(items[props.item].state) > 55) ? "orange" : (Number.parseFloat(items[props.item].state) > 32) ? "blue" : "purple"'

This is long, has lots of redundancy, and hard for the human user to read and understand.

Your suggestion

Would it be possible to define our own expression syntax somehow where we could simplify this. Maybe we could use something like:

=Number.parseFloat(items[props.item].state) : (75] "red", (75,55) "orange", [55,32) "blue", "purple"

where we use something like Maven supported version syntax to define number ranges. For non number ranges we could use something like

=items[props.item].state : ("ON") "green", ("OFF") "red", "gray"

Obviously this is just notional syntax and I'm not remotely certain anything like this is possible. But anything where we can provide the thing to compare to once and provide some sort of short hand for the conditions and the values to use under those conditions would be an improvement.

Your environment

OH 4 SNAPSHOT #3417

Additional information

crnjan commented 1 year ago

While not ideal, maybe a more "explicit" version of your expression

iconColor: '=(Number.parseFloat(items[props.item].state) > 75) ? "red" : (Number.parseFloat(items[props.item].state) > 55) ? "orange" : (Number.parseFloat(items[props.item].state) > 32) ? "blue" : "purple"'

can be written as:

iconColor: '=[{val: 75, col: "red"}, {val: 55, col: "orange"}, {val: 32, col: "blue"}, {val: 0, col: "purple"}].find(a => Number.parseFloat(@@props.item) > a.val).col'

or more compact

iconColor: =[[75,"red"], [55,"orange"], [32,"blue"], [0,"purple"]].find(p => Number.parseFloat(@@props.item) > p[0])[1]
rkoshak commented 1 year ago

I didn't think that expressions supported => (deliberately). Do your examples work for you or are they a notional idea?

crnjan commented 1 year ago

Above code works 100%, tested with Build #3417. Support for arrow operator (=>) was added https://github.com/openhab/openhab-webui/pull/1522.

rkoshak commented 1 year ago

I must have missed that PR.

I'll have to consider if either of those approaches are easier to understand or not. My concern is more understandability than length. I see exactly what's going on but it's using some language features the average OH user might not think of.

I do see that => is documented at https://www.openhab.org/docs/ui/personal-widgets.html but not at https://www.openhab.org/docs/ui/building-pages.html#dynamically-configuring-components-with-expressions. I wonder if something should be added to the latter.

I'll leave this open for a bit to see if there are any other comments or ideas and I might possibly convert it to a documentation issue (which I can submit the PR for myself :-D ).

crnjan commented 1 year ago

IMHO it's better to use what language already provides, which is already documented as part of (js in this case) language itself (and more or less known to users) compared to come up with new constructs to solve things that can be solved already ... but I guess that is just my point of view being a programmer. If we decide to follow a different approach I might even help implementing it ;).

rkoshak commented 1 year ago

IMHO it's better to use what language already provides, which is already documented as part of (js in this case) language itself

The problem is for many of our users their only exposure to programming is Rules DSL or (increasingly) Blockly. How would these users even know where to look to come up with those lines in generic JS docs and tutorials? A common refrain I see on the forums is, as easy as they are, creating custom widgets is still too hard for many.

We also are not necessarily required to use any language constructs/expressions at all. What if it were captured in the YAML instead somehow?

  iconColor: 
    - 75 : red
    - 55: yellow
...

I'm just brains storming...

ghys commented 1 year ago

There's room for extending the expression syntax (https://github.com/6utt3rfly/jse-eval/#extending-evaluation), but also to add utility functions to the expression context (https://github.com/6utt3rfly/jse-eval/#evaluation), which might be the better way to go: there would be no additional syntax to learn about expressions, but there would be functions that you can use and would be documented, and save you from implementing such things directly in your expressions if they become complex. An "range-to-result" evaluation could be one of them. The challenge is to define that API as it's always a balance to give raw tools, or facilitate things and then be confronted with questions and additional maintenance ("why doesn't it handle this?").

If we add a "mini-library" of functions they would be implemented in the main UI's code (including with the use of npm libraries if necessary) and available for use just like another function. Another I had in mind a while ago is a HSBtoRGB function which would for example convert 0,100,100 (a red Color item state) to #ff0000 (HTML color)