adding a math_number block in init creates two blocks

331 views
Skip to first unread message

Stefan Höhn

unread,
Apr 3, 2022, 2:07:56 PM4/3/22
to Blockly
Hi to all,

I have the following init()-method with the intent to add an input block already prefilled with a math_number-block (which could be replaced by something else). I intentionally don't use a shadow block which should not be a topic for the discussion here (the final block is much more complicated due to a mutator to add several fields)  ;-)

...
this.appendDummyInput()
.appendField(new Blockly.FieldImage(nextImage, 15, 15, undefined, this.onClick), 'NEXT')
this.appendValueInput('value')
this.appendDummyInput()
.appendField('days', 'blockType')

let parentConnection = this.getInput('value').connection
parentConnection.connect(this.workspace.newBlock('math_number').outputConnection)
..

The good news is this actually works in that it prefills the input with the block BUT it does create several math number blocks and I have no clue why????

I not only see these but it is also reflected in the serialized blocks' XML

  <block type="math_number" id=")IcaNyc|t4?b|g={f:6d" x="192" y="342">
  <field name="NUM">0</field>
  </block>
  <block type="oh_zdt_temporal_unit_input" id="E`FtgN^4NBk[1GXVLeHR" x="194" y="390">
     <value name="value">
        <block type="math_number" id="4Sp_SuAdhS`*]VxPPsoC">
           <field name="NUM">0</field>
        </block>
     </value>
   </block>
  <block type="math_number" id=",walx!?m#u}fS;SD1,!B" x="280" y="429">
    <field name="NUM">0</field>
  </block>
</xml>

Does anyone have an idea why this magic duplication happens?

Thanks in advance,
Stefan

Neil Fraser

unread,
Apr 3, 2022, 3:16:07 PM4/3/22
to blo...@googlegroups.com
My knowledge here is a bit out of date.  But the first thing I'd dry is adding a `console.log('init');` line in your init function.  Is it getting called twice?

--
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.
To view this discussion on the web visit https://groups.google.com/d/msgid/blockly/703c026e-6d1d-48a9-a771-144049db5b92n%40googlegroups.com.


--

Stefan Höhn

unread,
Apr 3, 2022, 3:59:54 PM4/3/22
to Blockly
It's not called only once but several times and even when I drag the block it is called again. What do I now? 

Tobias Weinert

unread,
Apr 3, 2022, 11:10:18 PM4/3/22
to Blockly
Hello Stefan,

Init is called in the context of the Toolbox Workspace and your main Workspace. And then as you noticed on every Drag.

if (this.workspace.id == Blockly.getMainWorkspace().id){
// Here you go :)
}

Now, you won't see your math_number block, 'cause it was rendered automatically by the Toolbox (my Guess). You have to initialize and render it, and then establish the connections.

So call .initSvg() and .render() on your newly created Block:

let mathNumberBlock = this.workspace.newBlock('math_number');
mathNumberBlock.initSvg();
mathNumberBlock.render();
let parentConnection = this.getInput('value').connection;
parentConnection.connect(mathNumberBlock.outputConnection);

Fun Fact: Neil from the past has the hint regarding initSVG() and render(): https://groups.google.com/g/blockly/c/VKnorn3xXrQ/m/lG9FO-KACwAJ

But all this should imho not happen in the init-Function. Write an Extension, which is pretty straightforward:

Blockly.Extensions.register(
'add_math_number',
function () {
if (this.workspace.id == Blockly.getMainWorkspace().id) {
let mathNumberBlock = this.workspace.newBlock('math_number');
mathNumberBlock.initSvg();
mathNumberBlock.render();
let parentConnection = this.getInput('value').connection;
parentConnection.connect(mathNumberBlock.outputConnection);
}
});

and register it in your init-Function:

Blockly.Extensions.apply("add_math_number", this, false);

(Attention: In the Documentation there is a mysterious function Block.applyExtension() mentioned. I searched the Blockly Source, but there is not such a function :/ Maybe my first commit *lol*

I tested everything in a little and it works.

Although the Extension is called with every Drag, there is no creation of additional Fields, as they get created on a temporary block. This Temporary Block gets disposed after the drag Operation ended and so their newly created little Child math_number. Try console log the id of the current Block in the Extension: console.log(this.id) and you see the Block id changes, but the Block id in your Workspace remains the same as you see in your serialisation Data.

Hope this helps.

Best wishes
Tobias
(tweini)

Stefan Höhn

unread,
Apr 4, 2022, 3:52:17 AM4/4/22
to Blockly
Thanks Tobias, I appreciate the comprehensive answer!

Sorry for what's coming now because I did a lot of work and investigation which I wanted to provide systematically ;-)

re https://groups.google.com/g/blockly/c/VKnorn3xXrQ/m/lG9FO-KACwAJ : interesting you mention that as this is exactly the thread where I got the connect-code from :-)

I also had the initSVG and render in the code and now I took it in again and difference is that I see the duplicated block right away and not only after serialization (which is good to see).

Just for the record I added the following code that intents to create the block only once, but it also does >not< prevent the duplicate code (If I only knew what the root cause for that effect was), so I took that out again (it was just a try not knowing the root cause of that problem)

let parentConnection = this.getInput('value').connection
if (this.numberBlockInput===null) { // make sure it is only created once -> unfortunately still duplicates the block
   this.numberBlockInput = this.workspace.newBlock('math_number')
   this.numberBlockInput.initSvg();
   this.numberBlockInput.render();
}
parentConnection.connect(this.numberBlockInput.outputConnection)

I now went to your proposal and for those who read along,  here is the documentation of the register method you mention: https://developers.google.com/blockly/reference/js/Blockly.Extensions 

1) I added the block as exactly as you proposed above
2) then in the init-method I call: Blockly.Extensions.apply("add_math_number", this, false);

At least the behaviour is a bit different but not really successful

- when I open the toolbox, the parent block can be seen but without a number block (I'd be fine with that)
- as soon as I drag the block from the toolbox the number block appears in the parent block (I am fine with that)

The serialization is as follows:

<xml
    xmlns="https://developers.google.com/blockly/xml">
    <block
        type="oh_zdt_temporal_unit_input" id="sp/aquzo+b`rFyH2azr=" x="113" y="394">
        <value name="value">
            <block type="math_number" id="s9.%oBG_)AB^4Q@!dal[">

                <field name="NUM">0</field>
            </block>
        </value>
    </block>
</xml>

So only one block...

but when I reopen / reload it into the workspace, another block is added which then can be seen in the serialization :-(

<xml
    xmlns="https://developers.google.com/blockly/xml">
    <block
        type="oh_zdt_temporal_unit_input" id="sp/aquzo+b`rFyH2azr=" x="113" y="394">
        <value name="value">
            <block type="math_number" id="s9.%oBG_)AB^4Q@!dal[">

                <field name="NUM">0</field>
            </block>
        </value>
    </block>
    <block type="math_number"
        id="Zd!UD:{B?jLa0d`0^IS5" x="192" y="436">

        <field
        name="NUM">0</field>
    </block>
</xml>

Even worse I now get an error in the console saying

Error while retrieving Blockly data - Error: Error: Extension "add_math_number" is already registered.:undefined

I tried to prevent that by changing the code to 

if (Blockly.Extensions.isRegistered("add_math_number")===false) {

   Blockly.Extensions.apply("add_math_number", this, false);
}
but this throws an error

blockly__WEBPACK_IMPORTED_MODULE_0___default.a.Extensions.isRegistered is not a function:undefined

telling me that Blockly.Extensions.isRegistered seems not to be available even though I can see in the code online.

Now, it was about to check the version I am using: 6.20210701.0 which is basically from mid of last year (see https://github.com/google/blockly/releases)

Wow, that is behind 
7.20211209.4
 and  
8.0.0
 which is out there but both have breaking changes...

What version were you trying your above examples? How you recommend me to go on?

cheers
Stefan

Stefan Höhn

unread,
Apr 4, 2022, 6:48:32 AM4/4/22
to Blockly
I changed a few things

1) I moved to 8.0.0
2) This avoids the "already registered"-error even though I wonder why this would be necessary

if (!Blockly.Extensions.isRegistered("add_math_number")) {
  Blockly.Extensions.register('add_math_number', function() {....}
}

3) Just to be 100% sure I added this around the apply-method in init()

if (Blockly.Extensions.isRegistered("add_math_number")) {
    console.log("init -> add_math_number is registered, so apply it")

    Blockly.Extensions.apply("add_math_number", this, false);
}

At least I don't get exceptions anymore but the duplication still happens

I added a few logs (in init, in registering and in the function of the math_number block)

a) I am open the blockly workspace and it registers add_math_number
-------- registering add_math_number

b) I open the toolbox with that parent block
------- INIT
init -> add_math_number is registered, so apply it
add_math_number function is called

Note that the number block is not created which is most like because of the following line that checks the id's of the workspace

if (this.workspace.id == Blockly.getMainWorkspace().id) {
  console.log('----- creating number block')

  let mathNumberBlock = this.workspace.newBlock('math_number');

c) I drag to block to the workspace

------- INIT
init -> add_math_number is registered, so apply it
-------- add_math_number function is called
----- creating number block  id= tZ}D|[jsEUTZKv3@=U,h

Note that only one number block in the parent is shown on the workspace

d) I drag the block

------- INIT
init -> add_math_number is registered, so apply it
-------- add_math_number function is called
----- creating number block id = O0jF+)wrG(4ba.hz5.wU

Init is called again and another block is created!
Note that this block does not appear

e) I save and  check the serialization and it only reveals one number block which at least is consistent to what I view
  <block type="oh_zdt_temporal_unit_input" id="o35YQ!A_)rpIN5(PAX9h" x="93" y="319">
     <value name="value">
            <block type="math_number" id="tZ}D|[jsEUTZKv3@=U,h">
                     <field name="NUM">0</field>
            </block>
     </value>
  </block>
</xml>

You can see that only the first num-block created (c) appears within the serialization

f) The problem happens when I reopen the workspace (=deserialization)
(Before the workspace is opened I verify that the persisted xml is still the same as above and it is)

------- INIT
init -> add_math_number is registered, so apply it
-------- add_math_number function is called
----- creating number block id = z2$[{jv[e)8oCEZ64D-

It is creating a new number block with a new id and this time is a single number block floating around in the workspace

and to no surprise it appears in the serialized XML

   <block type="oh_zdt_temporal_unit_input" id="o35YQ!A_)rpIN5(PAX9h" x="93" y="319">
      <value name="value">
          <block  type="math_number" id="tZ}D|[jsEUTZKv3@=U,h">
             <field. name="NUM">0</field>
         </block>
      </value>
   </block>
   <block type="math_number id="z2$[{jv[e)8oCEZ64D-" x="166" y="355">
     <field name="NUM">0</field>
   </block>
</xml>

Tobias Weinert

unread,
Apr 4, 2022, 7:57:02 AM4/4/22
to Blockly
Hiho Stefan,

haven't tried the Serialisation Process in my test-Environment, but think the Deserialisation triggers the Extension to create the unwanted block.

And as you mentioned, the number block doesn't appear in the Toolbox and is created when you Drag the Block into your Workspace.

So it could be as simple as to create the number Block in the Toolbox (or to be more exact in the Flyout)

if (this.workspace.id != Blockly.getMainWorkspace().id) {
...}

(Alter "==" to "!=" in the if Statement)

Would be more consistent, because the initial creation of a block is in the Toobox (Flyout) and not on the Main Workspace.

Let me know if it works.

Best wishes
Tobias
(tweini)

Stefan Höhn

unread,
Apr 4, 2022, 11:46:05 AM4/4/22
to Blockly
This looks really promising currently, not to say it seems to work so far. :-)

btw, I definitely have to say goodbye to 6.2. as it does not allow to use Blockly.Extensions.isRegistered which really seems to be necessary to avoid the "already registered"-error.

for the record here is the final solution:

if (!Blockly.Extensions.isRegistered("add_math_number")) {
  Blockly.Extensions.register('add_math_number',
  function () {
    // workspace needs to be different(!), so the block is already added in the flyout and not only in the workspace
    if (this.workspace.id != Blockly.getMainWorkspace().id) {
       let parentConnection = this.getInput('value').connection;

       let mathNumberBlock = this.workspace.newBlock('math_number');
       mathNumberBlock.initSvg();
       mathNumberBlock.render();
       parentConnection.connect(mathNumberBlock.outputConnection);
   }
}

init() {
...
   if (Blockly.Extensions.isRegistered("add_math_number")) {. 

      Blockly.Extensions.apply("add_math_number", this, false);
   }
...
}

Honestly I am not really sure if the isRegistered is necessary with the apply()-method but I think it doesn't hurt and should make more secure, so I guess I leave it in. 

Thanks a lot for your time, Tobias, I really appreciate it (fyi, I am developing the blocklies for openHAB, so your help really brought it forward quite a bit for the ideas I have in mind and things the bugged me for some time!)

Cheers
Stefan

Tobias Weinert

unread,
Apr 4, 2022, 12:38:25 PM4/4/22
to Blockly

Hi Stefan,

always a pleasure to help.

The Registration of the Extension should only called once. So your Application Code should be responsible to register it as an one time job. A Page reload should not trigger two executions.

The Blockly.Extensions.apply("add_math_number", this, false); should be called at the very end of the init-function of the Block. This would be consistent with the call Blockly.defineBlocksWithJsonArray() in the newer JSON API of Blockly, which is the recommended way instead of the "old way" for a Block Definition with the Javascript-API (ie Blockly.Blocks['example_variable_untyped'] = {
init: function () {...})

I realised in the meantime, that the Extension Way is NOT necessary, in case your Extension isn't used in other Blocks. You can do all the Stuff in the init function, because the Blockly.Extensions.apply("add_math_number", this, false) delegates it's call to the Function.protoype.apply() of the Extension function, so the code is executed immediately. So there is no difference in code execution.

OpenHab is on my list to try. I was too lazy for my Home Automation solution and I use the "big" commercial ones. Shame on me *lol*

Best wishes
Tobias
(tweini)
Reply all
Reply to author
Forward
Message has been deleted
0 new messages