Error when switching between block shapes

127 views
Skip to first unread message

Bart Butenaers

unread,
Sep 16, 2018, 6:42:43 AM9/16/18
to Blockly
Dear,

We use the great JS-object blocks from Mark friedman.
Last week I added a dropdown to his get-property block, to add two functionalities:
  • 'remove': to remove a specified property from a JS object
  • 'has': to check whether a specified property doesn't exist in a JS object
Now it looks like this:

blockly_issue_1.png

This all works fine and the block changes shape (from value block to statement block) when the 'remove' option is selected:


blockly_issue_2.png


Changing the shape is done in the validator of the dropdown:


Blockly.Blocks['node_object_get'] = {
    init
: function () {
       
this.jsonInit({
           
"type": "node_object_get",
           
"message0":  "%1 %2 property %3",
           
"args0": [
               
{
                   
"type": "field_dropdown",
                   
"name": "action",
                   
"options": [["Get"   , "GET"   ],
                               
["Has"   , "HAS"   ],
                               
["Remove", "REMOVE"]]
               
},
               
{
                   
"type": "input_value",
                   
"name": "object",
                   
"check": "Object"
               
},
               
{
                   
"type": "input_value",
                   
"name": "field_name",
                   
"check": "String"
               
}
           
],
           
"inputsInline": true,
           
"output": null,
           
"colour": '#BB8FCE',
           
"helpUrl": ""
       
});
       
       
// Assign 'this' to a variable for use in both callback functions below.
       
var thisBlock = this;
       
       
this.getField('action').setValidator(function(action) {
           
if (thisBlock.previousAction !== action) {
               
if(thisBlock.previousAction) {
                    thisBlock
.unplug(true, true);
               
}
               
               
switch (action) {
                   
case 'REMOVE':
                       
// statement block
                        thisBlock
.setOutput(false);
                        thisBlock
.setPreviousStatement(true);
                        thisBlock
.setNextStatement(true);
                       
break;
                   
case 'HAS':
                       
// value block
                        thisBlock
.setPreviousStatement(false);
                        thisBlock
.setNextStatement(false);
                        thisBlock
.setOutput(true, 'Boolean');
                       
break;
                   
default: // GET
                        thisBlock
.setPreviousStatement(false);
                        thisBlock
.setNextStatement(false);
                        thisBlock
.setOutput(true); // Any type
                       
break;
               
}
           
}
           
            thisBlock
.previousAction = action;
       
});
   
},
    mutationToDom
: function() {
       
const actionDropdown = this.getField('action');
       
const loadedAction = actionDropdown.getValue();
        actionDropdown
.getValidator().call(this, loadedAction);
   
},
};

However as soon as this block has a next statement, it goes wrong.  For example:

blockly_issue_3.png


When I load this workspace in the editor, the next statement disappears.  So the set-property block is gone ;-(

In my browser log this error appears (only when a next block is available!):
TypeError: Cannot read property 'isConnected' of null
    at
Object.Blockly.Xml.domToBlockHeadless_ (blockly_compressed.js:1210)
    at
Object.Blockly.Xml.domToBlock (blockly_compressed.js:1204)
    at
Object.Blockly.Xml.domToWorkspace (blockly_compressed.js:1200)
    at createWorkspace
(eval at <anonymous> (vendor.js:2), <anonymous>:32:21)
    at
Object.oneditprepare (eval at <anonymous> (vendor.js:2), <anonymous>:242:13)
    at d
(red.min.js:formatted:11466)
    at m
(red.min.js:formatted:11486)
    at
Object.open (red.min.js:formatted:12308)
    at n
(red.min.js:formatted:13213)
    at
Object.show (red.min.js:formatted:13253)

I assume that my switching between shapes causes this error:
  1. By default my get-block is a value block (since the 'get' option in the dropdown is a value block).  So I assume it is loaded as a value block initially.
  2. Afterwards - in the mutationToDom - I trigger the validator, otherwise I see a 'remove' option inside a value block (instead of a statement block).  In other words, I had to do this to make sure that the block shape is rendered correctly when the workspace is loaded.
Could it be that the 'unplug' is breaking somehow the connections to the next block, causing this problem ??
Have tried all kind of combinations I could come up with.

Thanks again ............
Bart

Erik Pasternak

unread,
Sep 17, 2018, 1:03:08 PM9/17/18
to Blockly
Hi Bart,

Since you're changing the shape of the block you need to implement the mutator functions for your block. When a block is initialized the mutator is run at the end of init to make sure all the connections exist before attaching other blocks. If you're only changing it in the dropdown selector and don't have the changes saved in a mutator the sequencing might be wrong and could cause what you're describing. Take a look at lists_getIndex for an example.

Cheers,
Erik

Bart Butenaers

unread,
Sep 17, 2018, 5:16:15 PM9/17/18
to Blockly
Hi Erik,

Thank you very much !!!!!!!
Had been debugging for two evenings, and at the end I had no cluel what I was doing wrong.
Based on your tip, it now seems to be working fine if I do it like this:

mutationToDom: function() {
       
const actionDropdown = this.getField('action');

       
const action = actionDropdown.getValue();

       
var container = document.createElement('mutation');
        container
.setAttribute('action', action);
       
return container;
},
domToMutation
: function(xmlElement) {

       
const actionDropdown = this.getField('action');

       
       
// Get the stored action from the mutator container element
       
const action = xmlElement.getAttribute('action');
       
        actionDropdown
.getValidator().call(this, action);
},

Summarized (in case anybody ever needs it):
  • In the mutationToDom, the dropdown value is stored in a 'mutation' element (which will be stored in the XML).
  • In the domToMutation, the value is loaded from that 'mutation' element (from the XML).  And the value is passed to the validator function, which will (based on the value) setup the correct block shape (statement block or input block).
In my original post the validator was called in mutationToDom, while it should be called in domToMutation.  What a stupid mistake ...
And I wasn't even aware that the domToMutation is never called, when you don't return a (non-null) element in mutationToDom.

My compliments to this community !!

Kind regards,
Bart

Mark Friedman

unread,
Sep 17, 2018, 5:20:12 PM9/17/18
to Bart Butenaers, blo...@googlegroups.com
Very nice, Bart!  Maybe it's time for me to create an actual GitHub repo for the object stuff and not just a Gist, so that we can add improvements like this that everyone can use!

-Mark

--
You received this message because you are subscribed to the Google Groups "Blockly" group.
To unsubscribe from this group and stop receiving emails from it, send an email to blockly+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Bart Butenaers

unread,
Sep 17, 2018, 6:02:45 PM9/17/18
to Blockly
Hi Mark,

That might indeed be interesting for some users.  
If I ever add ever again new functionality to my blocks, I will let you know (so you can add it to your repo)...

My Blockly integration for Node-RED wouldn't exists without your block set, since Node-RED is all about  processing messages (which are JS objects)...
I have changed your original blocks a bit for my purpose:
  • The Javascript block definitions have been converted to JSON definitions, to have better support for multiple languages.
  • All the fixed texts have been replaced by locale files (currently only in dutch, french, english, japanese since I have to wait for volunteers creating pull requests for this).
  • My code generators make a distinction between normal Javascript objects and Node-RED memory (since we also use it to get/set/remove properties stored in the application's memory).
  • Added the above functionality like 'has' and 'remove'.
P.S. If somebody ever needs it, my custom blocks for Node-RED are here.
P.S. The ioBroker project also has custom blocks, a.o. two JSON converter blocks similar to the ones from Mark.  The code can be found here.

Bart
Reply all
Reply to author
Forward
0 new messages