Also found a minor issue in IF logic.
Generates:
if (!((msg['previous']) == true)) {
It should generate:
if (!((msg['previous']) === true)) {
Triple not double =
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
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 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
That's good enough for me
Your thinking too much at the JS level
Blockly plays fast and loose with data types
I'd like to have a go
Can you post a typical msg input that this node is expected to work on please?
Simon
Currently got
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.
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 ) :
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
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.
Finally managed to do it !
But just having tea so will post results later
So here it is
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
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
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 )
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
Hmm, no kidding!
Well, glad to be of service
Simon already found a workaround to get nested properties like this:
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 '.') :
[{"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 Therefore I have added a warning, that appears on both nodes when no string literal is being used:
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:
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:
But I have added another solution, in the get-property block:
Some questions:
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
Not sure, do you mean the
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?
Julian,
Don't think I can use hasOwnProperty.
For example I have following data on my global memory:
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:
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 !