Node-red-contrib-blockly 1.0.0 available

Also found a minor issue in IF logic.

Generates:

if (!((msg['previous']) == true)) {

It should generate:

if (!((msg['previous']) === true)) {

Triple not double =

But does it actually work?

My attitude would be

What outcome do I want?

How do I achieve it in this Blockly language?

In most cases, the end JS code will be different and certainly will not be the best way of doing it but if it gets the right answer - win :slight_smile:

It will work almost all of the time. The issue will be if you intended that the equality test should include the data type.

100 == "100" is true

100 === "100" is false

Unfortunately, my first real test has failed :frowning: because I can't work out how to do a deep property comparison. I expect I could work it out eventually but I don't have time just now. I will try some other test another time.

To be fair, Blockly isn't really aimed at me :slight_smile:

That's good enough for me :slight_smile:

Your thinking too much at the JS level :slight_smile:

Blockly plays fast and loose with data types

I'd like to have a go :slight_smile:
Can you post a typical msg input that this node is expected to work on please?

Simon

Currently got
image

to handle no msg.previous property present

Sure, here is a typical message payload:

{"Living Room":{"id":1,"Name":"Living Room","CurrentTemperature":17.5,"DesiredTemperature":16,"Heating":"Off","Override":"No","OverrideTimeout":"N/A"},"Kitchen":{"id":2,"Name":"Kitchen","CurrentTemperature":17.7,"DesiredTemperature":-20,"Heating":"Off","Override":"No","OverrideTimeout":"N/A"},"Dining Room":{"id":3,"Name":"Dining Room","CurrentTemperature":18.1,"DesiredTemperature":18,"Heating":"Off","Override":"No","OverrideTimeout":"N/A"},"Rear Hall":{"id":4,"Name":"Rear Hall","CurrentTemperature":19.5,"DesiredTemperature":16,"Heating":"Off","Override":"No","OverrideTimeout":"N/A"},"Front Hall":{"id":5,"Name":"Front Hall","CurrentTemperature":18,"DesiredTemperature":17,"Heating":"Off","Override":"No","OverrideTimeout":"N/A"},"Master Bedroom":{"id":6,"Name":"Master Bedroom","CurrentTemperature":16.9,"DesiredTemperature":15,"Heating":"Off","Override":"No","OverrideTimeout":"N/A"},"James Bedroom":{"id":7,"Name":"James Bedroom","CurrentTemperature":18.8,"DesiredTemperature":16,"Heating":"Off","Override":"No","OverrideTimeout":"N/A"},"Libby Bedroom":{"id":8,"Name":"Libby Bedroom","CurrentTemperature":18.6,"DesiredTemperature":16,"Heating":"Off","Override":"No","OverrideTimeout":"N/A"},"Loft main":{"id":9,"Name":"Loft main","CurrentTemperature":-3276.8,"DesiredTemperature":15,"Override":"No","OverrideTimeout":"N/A"},"Loft box":{"id":10,"Name":"Loft box","CurrentTemperature":-3276.8,"DesiredTemperature":16,"Override":"No","OverrideTimeout":"N/A"}}

There may be a msg.previous which has exactly the same format but is the previous iteration of the msg so that I can compare current with previous.

The aim is to produce a msg on a second output for every room who's heating status has changed with a payload that says whether the heating is now on or off.

This will be fed into a reporting topic on MQTT. Eventually to be linked to a conversational interface on Telegram.

Ah, I made mine slightly too complex (story of my life):

Hi Julian & Simon,
Didn't have time yet to follow the discussion. Will respond to it later...

In this Github issue @cymplecy added this feature request:

What's really needed is a way to copy paste blocks between Blockly nodes

That is indeed a must have. However Blockly doesn't support copying blocks in workspace 1 and pasting them in workspace 2. Finally I found a rather simply workaround by using my own clipboard variable. The 1.1.0 branch on Github now supports this new feature ( CTRL-C in one node and CTRL-V in another node ) :

blockly_copy_paste

P.S. in this version the new list_push and list_pop blocks have again be removed, as agreed upon.

mm.. just updated and looked inside the node I was last working on

but the JS is still there

var oldPayload, j;


if (!(msg['previous'])) {
  msg['previous'] = (msg['payload']);
}

oldPayload = (msg['payload']);

for (var j_index in oldPayload) {
  j = oldPayload[j_index];
}

msg['payload'] = j;

delete msg['previous'];
;

Great - really good to have :slight_smile:
but....

we really need it to be able to copy/paste a set of blocks as well ......

Simon (He who is never satisfied)

I'm afraid that he - who is indeed never satisfied - needs to join the Blockly team, and add this functionality by himself to Blockly. It seems that they never will implement your request: see this issue.

1 Like

Finally managed to do it !

But just having tea so will post results later :slight_smile:

@TotallyInformation

So here it is :slight_smile:

image

image

The flow which includes an inject to inject the payload, change node to add different previous version and need a JS function node to inject the date as Blockly has no block to generate that

Pastebin link to flow as it doesn't format correctly on here properly

https://pastebin.com/FKJuAe2q

I wouldn't recommend trying to use Blockly to do this sort of processing (unless @BartButenaers adds in some blocks to simplify trying to get/set property values of objects such as msg.payload[room].Heating )

And Blockly probably needs a Date block (or an entire category of Date blocks even )

While trying to do this, I came across an error with the

image

block and i've added a quick fix into the 1.1.0 code base so if anyone wants to try out this flow - you need to update to the latest github version

npm install bartbutenaers/node-red-contrib-blockly#release-1.1.0

(Of course @BartButenaers might be along shortly to remove my code change but I think it works OK :slight_smile: )

Simon

PS He should NEVER have given me access............

PPS I have learnt a LOT about JSON objects doing this so its been quite a useful exercise - and I've even learnt a bit of JS as well :slight_smile:

1 Like

Hmm, no kidding! :open_mouth:

Well, glad to be of service :blush:

1 Like

Simon already found a workaround to get nested properties like this:
image

But when the number of levels grows, you will end up with a long chain of blocks. Therefore I have updated the property-get and property-set nodes to allow the user to enter nested field names. Here is a flow to test Julian's JSON example (see the block with the nested property name containing multiple '.') :

image

[{"id":"fad577fd.329388","type":"Blockly","z":"279b8956.27dfe6","language":"en","func":"msg['resultaat'] = (msg['payload']['Living Room']['Heating']);\nnode.send([msg]);\n","workspaceXml":"<xml xmlns=\"http://www.w3.org/1999/xhtml\"><block type=\"node_object_set\" id=\"N3~a4NI3,C+907(}yo.Y\" x=\"12\" y=\"63\"><value name=\"object_field\"><shadow type=\"node_msg\" id=\"`YPpOSgAK#nM@FVV6)T4\"></shadow></value><value name=\"field_name\"><shadow type=\"text\" id=\"8_0}d(^f2Fb*D3DiPxcZ\"><field name=\"TEXT\">resultaat</field></shadow></value><value name=\"value_field\"><shadow type=\"text\" id=\"$j^w=^r[ZRR!d*@!hH6`\"><field name=\"TEXT\"></field></shadow><block type=\"node_object_get\" id=\"gMIoT]/n}M/r9YGA2aB[\"><field name=\"action\">GET</field><value name=\"object\"><shadow type=\"node_msg\" id=\"3UDd$^tB)^2XvZJ:GRcS\"></shadow></value><value name=\"field_name\"><shadow type=\"text\" id=\"1go9(l|I,t|(,O/nF{*7\"><field name=\"TEXT\">payload.Living Room.Heating</field></shadow></value></block></value><next><block type=\"node_send\" id=\"s?7XLpf%(Y;|Bkqbl5!n\"><field name=\"OUTPUT_NR\">1</field><value name=\"MESSAGE_INPUT\"><shadow type=\"node_msg\" id=\"eX3OM@95(tt,,~.Pz`5[\"></shadow></value></block></next></block></xml>","outputs":1,"name":"","x":2020,"y":720,"wires":[["98596a6d.f1f928"]]},{"id":"9821685f.189158","type":"inject","z":"279b8956.27dfe6","name":"Inject JSON","topic":"","payload":"{\"Living Room\":{\"id\":1,\"Name\":\"Living Room\",\"CurrentTemperature\":17.5,\"DesiredTemperature\":16,\"Heating\":\"Off\",\"Override\":\"No\",\"OverrideTimeout\":\"N/A\"},\"Kitchen\":{\"id\":2,\"Name\":\"Kitchen\",\"CurrentTemperature\":17.7,\"DesiredTemperature\":-20,\"Heating\":\"Off\",\"Override\":\"No\",\"OverrideTimeout\":\"N/A\"},\"Dining Room\":{\"id\":3,\"Name\":\"Dining Room\",\"CurrentTemperature\":18.1,\"DesiredTemperature\":18,\"Heating\":\"Off\",\"Override\":\"No\",\"OverrideTimeout\":\"N/A\"},\"Rear Hall\":{\"id\":4,\"Name\":\"Rear Hall\",\"CurrentTemperature\":19.5,\"DesiredTemperature\":16,\"Heating\":\"Off\",\"Override\":\"No\",\"OverrideTimeout\":\"N/A\"},\"Front Hall\":{\"id\":5,\"Name\":\"Front Hall\",\"CurrentTemperature\":18,\"DesiredTemperature\":17,\"Heating\":\"Off\",\"Override\":\"No\",\"OverrideTimeout\":\"N/A\"},\"Master Bedroom\":{\"id\":6,\"Name\":\"Master Bedroom\",\"CurrentTemperature\":16.9,\"DesiredTemperature\":15,\"Heating\":\"Off\",\"Override\":\"No\",\"OverrideTimeout\":\"N/A\"},\"James Bedroom\":{\"id\":7,\"Name\":\"James Bedroom\",\"CurrentTemperature\":18.8,\"DesiredTemperature\":16,\"Heating\":\"Off\",\"Override\":\"No\",\"OverrideTimeout\":\"N/A\"},\"Libby Bedroom\":{\"id\":8,\"Name\":\"Libby Bedroom\",\"CurrentTemperature\":18.6,\"DesiredTemperature\":16,\"Heating\":\"Off\",\"Override\":\"No\",\"OverrideTimeout\":\"N/A\"},\"Loft main\":{\"id\":9,\"Name\":\"Loft main\",\"CurrentTemperature\":-3276.8,\"DesiredTemperature\":15,\"Override\":\"No\",\"OverrideTimeout\":\"N/A\"},\"Loft box\":{\"id\":10,\"Name\":\"Loft box\",\"CurrentTemperature\":-3276.8,\"DesiredTemperature\":16,\"Override\":\"No\",\"OverrideTimeout\":\"N/A\"}}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":1830,"y":720,"wires":[["fad577fd.329388"]]},{"id":"98596a6d.f1f928","type":"debug","z":"279b8956.27dfe6","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"resultaat","x":2220,"y":720,"wires":[]}]

Some questions about these nested properties:

  • Property names are case sensitive, so we should add that in the documentation.

  • This does ONLY WORK when the nested property name is a literal. It doesn't work when the property name is passed e.g. as a variable. For example:

    var someNestedFieldName = 'someProperty.someNestedProperty';
    msg[ someNestedFieldName ] = 'newValue';
    

    The content of that variable someNestedFieldName is only know at runtime, not at code generation time :weary: Therefore I have added a warning, that appears on both nodes when no string literal is being used:

    blockly_nested_warning

    Is that warning text understandable enough for novice users?

  • I have a question about how I should handle unexisting properties? When a user specifies a nested property "X.Y.Z" but property "Y" is not available, this will result in "TypeError: Cannot set property 'Z' of undefined". Is this a problem? See some tricks on the internet about safely accessing a nested property (e.g. with || {} ), but don't know if there is a good solution available in Javascript. Would be nice if ALL teh intermediate nested properties were created automatically, so the user don't has to create them one by one. Similar to the Node-RED memory where you can add all levels at once:
    flow.set('X.Y.Z', "myContent");
    Which results in:
    image
    Any tips on how I could achieve that, and still generate simple code (i.e. single line, without loops or recursive function calls)?

Simon had a simple workaround like this:
image

But I have added another solution, in the get-property block:
blockly_has_property

Some questions:

  • Is the code for the Node-RED memory correct, or is there an easier way (via the standard API) to check whether a property exists?
  • Had a short discussion with Simon already about the description in the dropdown. Should it be 'contains', 'has', 'contains a', 'has a', or something else ?

Thanks!

Great stuff as always Bart!

I don't think you can have chained property names in a [] reference anyway. It would have to be msg[someProperty][someNestedProperty] I think.

Happy for now that you can't use a variable for property names but I have to say that this is a really common case so we might want to have a think about how best to do it in the future.

Yes, this is always a problem in JavaScript. It is very common to have to check for the presence of a property or nested property before being able to access it. If you found a way to do this without forcing the (probably novice) user to do a separate check, that would be mighty interesting. Not sure how though.

Perhaps the simplest way would be to have a block that checks for the presence of a property - something that looks similar to an if block but would be in the objects section?

Trying to auto-create missing intermediate properties throws up a whole host of other potential issues that would need working through - is this always what you want? Might you not want to take a different set of actions if something is missing?

Ah, that answers my point 2 paras above - and undoubtedly in a better way :wink:

Not sure, do you mean the image

hasOwnProperty is the usual way as this prevents accidentally checking an inherited property (though that in it self might be what you want occasionally). Whilst not perhaps quite "usual", I suspect that your code might be more generally useful.

I think that has is more Blockly-like?

1 Like

Julian,

Don't think I can use hasOwnProperty.
For example I have following data on my global memory:
image

When I run this test code in a function node:

msg.keys = global.keys();
msg.includes = global.keys().includes("topics");
msg.hasown = global.hasOwnProperty("topics");
msg.objectkeys = Object.keys(global);
return msg;

The result is:
image

The hasown=false since it doesn't find 'topics' in the list ['set', 'get', 'keys']: the keys of the global object are the 3 functions that have been made available in the API for the sandbox that runs the function node code:

global: {
     set: function() {
          node.context().global.set.apply(node,arguments);
     },
     get: function() {
          return node.context().global.get.apply(node,arguments);
     },
     keys: function() {
          return node.context().global.keys.apply(node,arguments);
     }
},

But thanks a lot for reviewing my proposals !