[Solved] My Squeeze Control Custom Widget is partially broken

Hi,

Today another user reported errors while installing my widget via the UI.
We tried to debug this, but I realized something must be broken since OH 3.1.

I used a conversion pattern

- component: Label
  config:
     pattern: %1$tM:%1$tS

to convert seconds to a user readable minutes:seconds

this worked in OH 3.1 but now the logs the user provided show this

2022-03-30 10:02:40.898 [ERROR] [munity.CommunityUIWidgetAddonHandler] - Unable to parse YAML: while scanning for the next token
found character '%' that cannot start any token. (Do not use % for indentation)
 in 'reader', line 179, column 30:
                        pattern: %1$tM:%1$tS
                                 ^

 at [Source: (StringReader); line: 179, column: 28] (through reference chain: org.openhab.core.ui.components.RootUIComponent["slots"]->java.util.LinkedHashMap["default"]->java.util.ArrayList[5]->org.openhab.core.ui.components.UIComponent["slots"]->java.util.LinkedHashMap["default"]->java.util.ArrayList[0]->org.openhab.core.ui.components.UIComponent["slots"]->java.util.LinkedHashMap["default"]->java.util.ArrayList[0]->org.openhab.core.ui.components.UIComponent["config"])
2022-03-30 10:02:40.900 [ERROR] [munity.CommunityUIWidgetAddonHandler] - Widget from marketplace is invalid: Unable to parse YAML

he wasnā€™t able to install the widget via mainUI on openHAB 3.3.0~S2833-1

I edited the line to the following

pattern: "%1$tM:%1$tS"

After the change Iā€™m able to install the widget from mainUI, but the TrackDuration isnā€™t shown anymore with the edited pattern.

I also tried changing the pattern to a JS transformation

pattern: JS(mmss.js):%s

and added a js file in the transform folder but the Duration isnā€™t shown either.

I donā€™t know how this could happen?!?

Are there any changes made on parsing the pattern declaration? Any changes in yaml syntax?

Any help appreciated. I tested this today on openHAB 3.3.0.M2

To be honest, I donā€™t believe that this ever worked to begin with. The only thing that I think has changed with regard to labels is that thereā€™s better linting so errors like this are now caught when previously they were more readily ignored.

If Iā€™m not mistaken, the full component that you are referring to is:

- component: Label
  config:
    pattern: "%1$tM:%1$tS"
    class:
      - text-align-right
      - margin-right
    text: =items[props.prefix+items[props.selected].state+props.duration].displayState

The text that you are supplying to the label is the itemā€™s displayState which means that there may already be a pattern applied if there is one defined for the item in the StateDescription metadata.

Secondly, if you look at where the Label component is created in the UI source code, itā€™s a very simplistic component.

  <div v-else-if="componentType && componentType === 'Label' && visible" :class="config.class" :style="config.style">
    {{ config.text }}
  </div>

You can see that all it does is directly pass the text config parameter to the content of a div block and apply the style and class config parameters to the div. Thereā€™s no processing of the text and that hasnā€™t changed. I could be wrong, but as far as I know a pattern config value has never been parsed for the labels.

1 Like

thanks for your reply :wink:
even if it surprises me a bitā€¦

as you can see in my screenshot it was working when I developed the custom widget.

Screenshot 2022-03-30 at 18-18-49 Squeeze Control Custom Widget - Add-on Marketplace _ UI Widgets - openHAB Community(1)

I must admit, initially I used the JS conversion as pattern and not the other one to be honestā€¦
but if youā€™re right and this wouldnā€™t work at all - the only possibility would be that the Item itself converted it, as you stated above if I get that rightā€¦

A few weeks ago I converted my items from files to UI, so Iā€™m not able to confirm thisā€¦
What I can see is that none of my UI items have a conversion scheme in the ā€œnameā€ now.

Do you think thereā€™s a way to achieve the conversion via the widget?

Edit:
I added the JS conversion via state description for testing purposes this seems to work.
but it would be nice to convert the values in the widget so that users donā€™t have to add the conversion to all their duration itemsā€¦

Ah yes. That makes everything more clear. In deed. When you had item files with the pattern in the label definition those patterns where converted to the state description metadata so that is what was being displayed by the call to displayState in the widget. When you converted to the UI items, which, as you noted, donā€™t take the pattern in the label anymore then you lost that formatting information, and have to manually add it to the metadata.

Certainly. The label text does get parsed through the expression parser so you can use JS expressions to format the label directly. So you should be able to adapt what you have in your transform file directly into the label line using the item state (not displayState) in place of your transform function variable.

1 Like

yes but the funny thing is, I have other UI converted items that had a conversion pattern in the label, and theyā€™re still there ā€¦so this looks like I never converted via Item in this case?!?

or some item were converted in another way - but that sounds weird to meā€¦ theyā€™re all imported to UI in the same way.

very mysterious :wink:

Ok any hint how this would look like? I donā€™t really get what you mean exactlyā€¦

I donā€™t think you can use the dayjs library for this very easily, so, the most straightforward would probably be just to use some basic math:

- component: Label
  config:
    text: =Math.trunc(Number(items[props.prefix+items[props.selected].state)/60) + ":" + (Number(items[props.prefix+items[props.selected].state) % 60)
1 Like

Thanks a ton for your effortā€¦ nice to have people like you here!

Have a nice weekend!

cheers, Dan

had to correct your snippet 'cause it was missing the props.durationā€¦

text: =Math.trunc(Number(items[props.prefix+items[props.selected].state+props.duration].state)/60) + ":" + (Number(items[props.prefix+items[props.selected].state+props.duration].state) % 60)

Now everything is working as exprected! Very nice :wink:

@JustinG

I have one additional questionā€¦ I realized that the managing of numbers < 10 is not covered, which results in 3:8 instead of 3:08

I tried to use this solution, but Iā€™m not really familar with the syntax

text: "=(Math.trunc(Number(items[props.prefix+items[props.selected].state+props.duration].state) % 60) < '10')"
      ? 'Math.trunc(Number(items[props.prefix+items[props.selected].state+props.duration].state)/60) + ":" + (Number(items[props.prefix+items[props.selected].state+props.duration].state) % 60)'
      : 'Math.trunc(Number(items[props.prefix+items[props.selected].state+props.duration].state)/60) + ":0" + (Number(items[props.prefix+items[props.selected].state+props.duration].state) % 60)'

this isnā€™t working - maybe you have an idea what I can do for a workaround? (linebreaks only for the readability)

this is the error:

Nested mappings are not allowed in compact mappings at line 241, column 15:

        text: "=(Math.trunc(Number(items[props.prefix+items[props.selected].staā€¦
                            ^

EDIT: Iā€™ve read a lot about Number and Math objects in javascript, but found no solution. Can you explain what is a ā€œcompact mapping/nested mappingā€ Iā€™ve found no info so farā€¦

This is not a javascript error, this is a yaml error. This results from an unfortunate collision of using some js syntax inside yaml. The js ternary expression (boolean test) ? result if true : result if false uses a colon, but a colon is a reserved character for yaml to separate the key name and the value, so if you have an colon in the middle of the yaml value the yaml parser thinks you are trying to define another key/value pair inside the first one (nesting mapping) while putting it all on one line. The solution to this is to make sure that if you have a colon in your value you wrap the entire value in ā€œā€¦ā€ or ā€˜ā€¦ā€™ so that the yaml parser knows the colon is just part of a string and not an attempt to map a value. You can do this even for entire expressions (anything in the widget definition that starts with an =). The only trick to remember is that if you already have one type of quotes in the expression for some string value you have to wrap the whole expression in the other type of quote. So, for any of the ternary expressions in the widgets is goes somthing like this:

key: "=(boolean test) ? 'string if true' : 'string if false'"

Fortunately, we donā€™t have to worry about using the ternary expression in this case. JS has built-in methods for padding strings. In this case you want to pad the front of the string with a zero so you use the padStart method. padStart is, however a string method, so we have to make sure that we convert our number into a string first:

text: =Math.trunc(Number(items[props.prefix+items[props.selected].state+props.duration].state)/60) + ":" + (Number(items[props.prefix+items[props.selected].state+props.duration].state) % 60).toString().padStart(2,'0')
1 Like

Ok thats good news!

ā€¦this is all relatively new to me - but I think I got what you explained.
I will try out this padstart method and read a little bit more about the use of js

:slight_smile: Iā€™m very happy that you had the time to explain this - thanx a ton!