JavaScript with custom Widgets

Is there a way to use a .js file with a custom widget?

I want to be able to run the .js somehow when I switch on that button written in the .yaml file. I’m aware that you can write expressions, but I want to be able to achieve that with JS and be able to pass the defined items as parameters to functions written in the .js file

I tried the JavaScript Script Binding and managed to generally import and use functions from a script written in a file locally on my device and use it in a rule directly, so is it possible to use this binding maybe with widgets?

No, this is not possible.

1 Like

It is actually possible, but it is undocumented, not officially supported, and discouraged.

In this case, there are almost certainly better options. Depending on what capabilities the script requires the most native solution would just be to recreate the file as an OH script and have the toggle component trigger the oh script.

2 Likes

Thank you, I managed to do something like this and seems to be working fine when I called the script:

uid: widget_73caa24e4d
tags: []
props:
  parameters:
    - description: A text prop
      label: Prop 1
      name: prop1
      required: false
      type: TEXT
    - context: item
      description: An item to control
      label: Item
      name: item
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Jul 11, 2024, 9:59:20 AM
component: f7-card
config:
  title: '=(props.item) ? "State of " + props.item : "Set props to test!"'
  footer: =props.prop1
  content: =items[props.item].displayState || items[props.item].state
slots:
  default:
    - component: script
      config:
        src: /static/script1.js
    - component: f7-button
      config:
        text: Update Item
        onClick: printIt('Hello World')

I was wondering though, if someone made a widget like that, and had a bunch of scripts in the html folder imported, would it be accepted in the marketplace?

Also can I still control the items state and update them using that defined script? Like I know that I can do something like ‘items.getItem(“itemName”)’ in JavaScript Script Binding, so I was wondering if the same thing can be done, If it is possible to have scripts use that binding somehow? Or if there is any other alternative?

For example, I want to use this script with the widget:

// function for controlling parking light
function parkingLight(item1) {
  if ('OFF' == item1.state) {
    console.info('Turning on parking light');
    item1.sendCommand('ON');
    console.info('Parking light is on');
  }
}

This script already works if I try to import it from the scripts UI along with the binding and execute it inside a rule, but it would be nice if this same script can be applied to widgets and pass items as parameters.

You would have to distribute your script1.js file as part of the post with instructions on how to download and install it. I personally would be against a widget that downloads the JS from somwhere else than the local OH instance it’s running from.

There are other widgets with external dependencies on the marketplace.

1 Like

Ultimately, such a thing wouldn’t be that different than the javascript modules such as Rich’s OH Rules Tools. It is up to the end user to fully understand the risks associated with running code on their host machine and to evaluate whether that code is safe. So, there is currently no policy against putting a widget with a js dependency in the market place, and I doubt there would be a call to take it down if you did put one up.

However, I do 100% agree with Rich that if you want to do this you have to work hard to achieve not only the standard transparency about the code itself, but also the security of the js origin. If it were me, I probably wouldn’t even actually make the js file available for download, but just include the full text of the file in the post and require users to copy and paste it directly from the post into a file in the proper location. That’s less user-friendly, but, in this case, this is really not a beginner kind of situation and the ability to understand the process enough to mange the js file that way would be a minimum acceptable level of experience.

…and, I would definitely put a big clear warning section in the post with the code about all of the above.

No, you can’t really do this directly. In the thread I linked above, I mentioned that connecting scripts in the widgets to OH functions is “non-trivial”.

Code that works in the JSScripting binding does not work in your js file. This is because the OH script bindings are running in their own context on the host machine, whereas the widgets are running in the browser context on the whatever machine is currently displaying OH. So, all the special helper library functions are not available, direct access to OH java objects is not possible, indeed, any non-secured access to the the OH server at all is not possible unless you have (ill-advisedly) opened holes up in OH security.

The browser context, does have access, of course, to the MainUI javascript and that, in turn, has methods that can communicate with the OH backend, but the problem there is that javascript has been packed, so you would have to take the time work through the all the encoded webpack js to figure out which methods you want to call an dhow to call them. And even if you spent the time to do that it probably breaks the next time the Main UI is updated and the webpack result is slightly different.

Of course, your script could make api calls directly, but then anyone downloading it would also have to be able to configure the script correctly (a bigger hassle) and your API would have to be available from wherever the widget gets run (security red-flag!).

There are several other possibilities as well, but they all have their own major drawbacks or security issues which is why my initial reaction was (and remains) this path is almost certainly not worth the effort when there are already other (better) options available.

This is exactly what I mean about better solutions already in existence. The built-in rule widget actions does exactly this. It triggers an OH rule and even allows you to insert into that rule whatever context variables you want from the widget. There are some examples in the docs and on the forum of how to do this. For example, my Bayesian sensor system widgets and rules are linked in this manner.

There are a few limitations to the js environment in the JSScripting rules, however, so in the off chance that your ultimate goal runs into one of those limitations, you can look into creating your own js module (such as the rules tools mentioned above) and then access that module from an OH rule.

Edit:

Despite what looks initially to be a very useful system, this ability to get js into widgets really has only very limited uses because of all the things that can already be done natively. In all my widgets I use this in exactly one place. I have one button that, when I use it on my phone, I want haptic feedback from. In that case I add

        onclick: =(!device.desktop)?'window.navigator.vibrate(50);':''

to the button.

That’s it. That’s the only place I’ve ever found for my use where additional js in the widgets is worth it.

2 Likes

I guess so, but can I send commands or pass the items from the “props” to the function “parkingLight” in my example above? Because it seems it doesn’t work

No, see @JustinG 's reply. You can have your widget cause a rule to run, and you can even pass in arguments to that rule. But note that the rule runs on the server, not the browser.

But your custom.js file isn’t going to be able to send commands or updates to Items nor is it going to be able to get the states of Items directly.

1 Like

There’s a workaround to sending information from the widget to the functions defined in a file. Although the functions in the file cannot have access to the widget’s props or vars, you can craft static calls that include the information. Take a careful look at the example I provided with the vibration call; the workaround is subtle. That’s actually a widget expression. If you put that directly in the browser console you’d get an error. But that widget expression is evaluated when the widget is rendered. So, once the widget is running that element either has an onClick function that is window.navigator.vibrate(50); or '' depending on the state of the widget device object.

Do you want to send an item object to your custom function? You’d have to use a repeater to get the full item object (that’s not available as part of the widget items dictionary), then you would need a widget expression that stringifys that object as part of building the final string version of that function call that becomes the value of the component’s onclick function.

…or…

You could just use a widget action and an OH JSScripting rule and save yourself several major headaches.

1 Like

Thank you both for putting effort in answering my questions, but I was wondering if all what we discussed also applies on the widgets in the HABPanel?

No, HABPanel is a completely different app built upon a completely different framework.

1 Like

Not sure if this is what you’re looking for, but this is a method I use:

Widget action sends a command with multiple variables to a String Item (I have items like Lights_Config, Scenes_Config etc) separated by “,” or whatever you like.

example command:

"LIGHTTOGGLE," + props.prop1 + "," + props.prop2

Then the JS rule triggers on every change of the String item.
Then you can split the newState into your variables and run any kind of script

let input = event.newState.split(",")
let action = input[0]
let var1 = input[1]
let var2 = input[2]

if (action == "LIGHTTOGGLE") {
    ....
}

It’s a good idea to fill the string item with something after processing the rule so it is ready to receive the same command again

items.StringItem.sendCommand("waiting for next command...")
1 Like

The run rule action of the widget can send the props directly and you didn’t need to parse then out in the called rule. You can just reference them by name.

Seems like a better way alright. Do you know how to pass the context to a javascript text file? It only seems to work for scripts created in the UI.

You need OH 4.2 or the latest openhab-js. The variables are available in event.raw. For example, if you pass a variable named “foo” you’d access it using event.raw.foo. For managed scripts you’d just use foo.