Block library with (code multilines) var and return

Hi,
getting no results after hours with my own library using more than one line.
Following is working:

uid: blocklibrary_isTagged
tags: []
props:
  parameters: []
  parameterGroups: []
timestamp: Apr 2, 2024, 2:30:26 PM
component: BlockLibrary
config:
  name: Contains Tag(s)
slots:
  blocks:
    - component: BlockType
      config:
        type: contains_tag
        message0: Item %1 contains tag %2 ?
        args0:
          - type: input_value
            name: sourceItem
            check: oh_item
          - type: input_value
            name: TAG
            check: String
        output: Boolean
        colour: 190
        tooltip: Check if the list contains the specified string.
        helpUrl: ""
      slots:
        toolbox:
          - component: PresetInput
            config:
              name: sourceItem
              shadow: true
              type: oh_item
          - component: PresetInput
            config:
              name: TAG
              shadow: true
              type: text
              fields:
                TEXT: tag
        code:
          - component: BlockCodeTemplate
            config:
              template: items.getItem({{input:sourceItem}}).tags.includes({{input:TAG}})
  utilities:
    - component: UtilityJavaType
      config:
        javaClass: org.openhab.core.model.script.actions.Things
        name: things

But now I want to look for an array as second argument, leeds me for at least one loop and multiple lines:

code:
          - component: BlockCodeTemplate
            config:
              template: >
                var sourceItem = '{{input:sourceItem}}'; 
                var tags = '{{input:TAGS}}'; 
                var itemData = items[sourceItem]; 
                var itemTags = itemData.tags.split(','); 
                var result = true; tags.forEach(function(tag) {
                  if (!itemTags.includes(tag)) {
                    result = false;
                    return;
                  }
                }); return [result];

That could never work…

console.error((var sourceItem = ''ZWave_Node_042_Fenster'';  var tags = '';  var itemData = items[sourceItem];  var itemTags = itemData.tags.split(',');  var result = true; tags.forEach(function(tag) {
  if (!itemTags.includes(tag)) {
    result = false;
    return;
  }
}); return [result];
));

How does design work here?

It’s the same with working with YAML in regular rules and scripts. If you want a newline in the actual code generated you need to add two newlines in the YAML.

                var sourceItem = '{{input:sourceItem}}'; 

                var tags = '{{input:TAGS}}'; 

                var itemData = items[sourceItem]; 

                var itemTags = itemData.tags.split(','); 

                var result = true; tags.forEach(function(tag) {

                  if (!itemTags.includes(tag)) {
                    result = false;
                    return;
                  }

                }); return [result];

Note that this is only required at the top level indentation (see the if statement above).

The reason this is the case is because YAML supports line wrapping and there has to be a way to tell it you really want a newline instead of just wrapping a line.

I think that’s what you are asking.

Not realy :face_with_hand_over_mouth:

code:
          - component: BlockCodeTemplate
            config:
              template: >
                
                var tags = items.getItem({{input:sourceItem}}).tags;

                return tags.includes({{input:TAG}});

wont work any more…

This sentence contains an infinity of possibilities. What doesn’t work? Errors in the logs? :man_shrugging:

Are you trying to make a function that returns a value?

A block literally just copies the code in the template into the place where you put the block on the screen. If you want to create a function you need to define the function as a utility and then in the template call the function from the utility.

For example, I have the following utility in my OHRT block library.

    - component: UtilityFunction
      config:
        code: >
          function {{name}}(cache, name) {
            return {{getOrInstantiate}}(cache, name, null, () => {{OHRT}}.TimerMgr());
          }
        name: getTimerMgr

And then in the template I have

    - component: UtilityFunction
      config:
        code: |
          var {{name}} = (() => {
            const ohrt = require('openhab_rules_tools');
            ohrt.helpers.validateLibraries('4.1.0', '2.0.3');
            return ohrt;
          })();
        name: OHRT
    - component: UtilityFunction
      config:
        code: >
          function {{name}}(cache, name, condition, create) {
            condition = (condition === undefined || condition === null) ? ((manager) => false) : condition;
            let manager = cache.get(name);
            if(manager === null || condition(manager)) {
              manager = create();
              cache.put(name, manager);
            }
            return manager;
          }
        name: getOrInstantiate
         - component: BlockCodeTemplate
            config:
              template: >
                {{utility:getTimerMgr}}(cache.{{field:CACHE}},
                {{input:TIMER_MGR_NAME}}).check({{input:TIMER_KEY}}, 'PT' +
                {{input:DURATION}} + '{{field:UNIT}}',

                function() { {{statements:TIMER_FUNC}} },'{{field:RESCHDULE}}' == 'TRUE', 

                function() { {{statements:FLAPPING_FUNC}} }, {{input:TIMER_KEY}});

The generated code looks like this:

var loopCount, loopCount2, startTime;

function getOrInstantiate(cache, name, condition, create) {
  condition = (condition === undefined || condition === null) ? ((manager) => false) : condition;
  let manager = cache.get(name);
  if(manager === null || condition(manager)) {
    manager = create();
    cache.put(name, manager);
  }
  return manager;
}

var OHRT = (() => {
  const ohrt = require('openhab_rules_tools');
  ohrt.helpers.validateLibraries('4.1.0', '2.0.3');
  return ohrt;
})();

function getTimerMgr(cache, name) {
  return getOrInstantiate(cache, name, null, () => OHRT.TimerMgr());
}


getTimerMgr(cache.private, 'timerMgr').check('foo', 'PT' + 5 + 'S',
function() { console.info('timer expired'); },'TRUE' == 'TRUE',
function() { console.info('flapping'); }, 'foo');

A UtilityFunction will only be inserted into the code once even if you add the block multiple times. And then where you inserted the block a call to the function will be added to the code.

All of my blocks need to import the OHRT library but even if you drop a bunch of blocks into a script, the OHRT block of code will only be inserted once, and this is self calling function.

getOrInstantiate will pull a Object from the cache or create a new Object if there isn’t one. This is used by all the blocks that use the cache but again, even if I include a bunch of such blocks, there will be only the one getOrInstantiate function added to the code.

getTimerMgr is a function that calls getOrInstantiate to create the TimerMgr Object. This shows how a UtilityFunction can call another one.

Note that there is a separate utilities section in the YAML that goes after and at the same level as the “block” section under “slots”. You can see my full block library at openHAB Rules Tools [4.1.0.0;4.9.9.9] for reference, though many of the others are probably simpler examples.

That was regarding my first post with the fully working example. (Now minimal changes into 2 lines)

As conclusion you said “return” is nor valid inside template, correct? (using component: UtilityFunction instead)

Thanks

In general, you cannot return from a JS Scripting script at all. You can only return from a function inside a script. So if you want to return something it has to be put in a function and if it’s a function it needs to be a UtilityFunction.

However, if all you want this block to do is return a boolean if an Item has one of the selected tags you don’t need a function for that. It could be implemented inline.

items.{{input:sourceItem}}.tags.filter(tag => {{input:TAGS}}.split(',').includes(tag)).length != 0

That line

  • get the Item
  • get an array of the tags from the Item (note that the tags is an array, not a String)
  • filter the tags from the Item down to just those that match the set from the property, assuming comma separated String, the code would change if it’s something else
  • true if there is at least one match between the Item tags and the block’s property tags, false otherwise

Make sure to define the output of the block as a Boolean (see ohrt_has_managed_timer in the OHRT block library I linked to above).

(Just to make it complete; previous discussions are not free of typos)

uid: containsTags
tags: []
props:
  parameters: []
  parameterGroups: []
timestamp: Apr 2, 2024, 10:11:04 PM
component: BlockLibrary
config:
  name: Contains Tag(s)
slots:
  blocks:
    - component: BlockType
      config:
        type: contains_tag
        message0: Item %1 contains tag(s) %2 ?
        args0:
          - type: input_value
            name: sourceItem
            check: oh_item
          - type: input_value
            name: TAGS
            check: String
        output: Boolean
        colour: 190
        tooltip: Check if the list contains the specified string.
        helpUrl: ""
      slots:
        toolbox:
          - component: PresetInput
            config:
              name: sourceItem
              shadow: true
              type: oh_item
          - component: PresetInput
            config:
              name: TAGS
              shadow: true
              type: text
              fields:
                TEXT: tag
        code:
          - component: BlockCodeTemplate
            config:
              template: "{{utility:getItemOrInstance}}({{input:sourceItem}}).tags.filter(tag => {{input:TAGS}}.split(',').includes(tag)).length != 0"
  utilities:
    - component: UtilityFunction
      config:
        code: >-
          function {{name}}(input) {
            if (typeof input === 'string' || input instanceof String) {
              return items.getItem(input);
            } else {
              return input;
            }
          }
        name: getItemOrInstance
1 Like