how to make a mutator

1,194 views
Skip to first unread message

Sketch

unread,
Jan 11, 2022, 12:05:57 PM1/11/22
to Blockly
Hi,

I am working on a switch and case block with the case block having a mutator.
Now i've looked on the docs but stil don't understand on how i should make a mutator and to generate code for it.

Could anyone guide me through the steps?

thanks

fu6...@gmail.com

unread,
Jan 11, 2022, 7:19:48 PM1/11/22
to Blockly

Sketch

unread,
Jan 12, 2022, 8:07:07 AM1/12/22
to Blockly
Hi,
Thanks for writing back.

Sadly i still dont treally understand how to use mutators.

Thanks

Op woensdag 12 januari 2022 om 01:19:48 UTC+1 schreef fu6...@gmail.com:

Beka Westberg

unread,
Jan 12, 2022, 8:21:33 AM1/12/22
to blo...@googlegroups.com
Hello!

Could you provide some more details about where you're getting stuck? There are a lot of places to get confused b/c it's like a wicked complicated system. So I just want to make sure no one is re-explaining something you already understand.

If you could post your current block, that might be a good way to communicate where you're at.

Best wishes!
--Beka

--
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/bc1b4751-d7d6-4c52-9a94-eeca1c3ee96an%40googlegroups.com.

Sketch

unread,
Jan 12, 2022, 1:46:35 PM1/12/22
to Blockly
Hi Beka,
thanks for writing back.

These are the blocks codes:

//case block

Blockly.Blocks['switch_case'] = {
  init: function() {
    this.appendValueInput("NAME")
        .setCheck(null)
        .appendField("case");
    this.appendStatementInput("case")
        .setCheck(null);
    this.setPreviousStatement(true, null);
    this.setNextStatement(true, null);
    this.setColour(270);
      this.setMutator(new Blockly.Mutator(['case']));
 this.setTooltip("If the case value matches the switch value run the blocks");
 this.setHelpUrl("");
  },
    decompose: function(workspace) {
  var topBlock = workspace.newBlock('switch');
  topBlock.initSvg();
  return topBlock;
},
    compose: function(topBlock) {
}
};

//mutator block

Blockly.Blocks['case'] = {
  init: function() {
    this.appendDummyInput()
        .appendField("case");
    this.setPreviousStatement(true, null);
    this.setNextStatement(true, null);
    this.setColour(270);
 this.setTooltip("");
 this.setHelpUrl("");
  }
};

//mutator top block

Blockly.Blocks['switch'] = {
  init: function() {
    this.appendDummyInput()
        .appendField("switch");
    this.appendStatementInput("case")
        .setCheck(null);
    this.setInputsInline(true);
    this.setColour(270);
 this.setTooltip("");
 this.setHelpUrl("");
  }
};

and well with the rest of it i kinda got stuck.

Thanks
Op woensdag 12 januari 2022 om 14:21:33 UTC+1 schreef bekawe...@gmail.com:

Beka Westberg

unread,
Jan 13, 2022, 8:13:08 AM1/13/22
to blo...@googlegroups.com
Well that looks like a great start!

The first thing you're going to want to do is probably implement a function for updating the shape of your block. For example, the  controls_if updateShape_ function. This looks at properties of the block, and updates the shape of the block to match those properties.

```
  updateShape_: function() {
    // Delete everything.
    if (this.getInput('ELSE')) {
      this.removeInput('ELSE');
    }
    for (let i = 1; this.getInput('IF' + i); i++) {
      this.removeInput('IF' + i);
      this.removeInput('DO' + i);
    }
    // Rebuild block.

    // Observe how it is looking at the `this.elseifCount_` property
    for (let i = 1; i <= this.elseifCount_; i++) {
      this.appendValueInput('IF' + i).setCheck('Boolean').appendField(
          Msg['CONTROLS_IF_MSG_ELSEIF']);
      this.appendStatementInput('DO' + i).appendField(
          Msg['CONTROLS_IF_MSG_THEN']);
    }
    if (this.elseCount_) {
      this.appendStatementInput('ELSE').appendField(
          Msg['CONTROLS_IF_MSG_ELSE']);
    }
  },
```

Then you can implement your `compose` function so that it walks the blocks in your mutator workspace, and modifies the properties of your block.

For example:
```
compose: function(containerBlock) {
    let clauseBlock = containerBlock.nextConnection.targetBlock();
    // Count number of inputs.
    this.elseifCount_ = 0;
    this.elseCount_ = 0;
    const valueConnections = [null];
    const statementConnections = [null];
    let elseStatementConnection = null;
    while (clauseBlock && !clauseBlock.isInsertionMarker()) {
      switch (clauseBlock.type) {
        case 'controls_if_elseif':
          this.elseifCount_++;
          valueConnections.push(clauseBlock.valueConnection_);
          statementConnections.push(clauseBlock.statementConnection_);
          break;
        case 'controls_if_else':
          this.elseCount_++;
          elseStatementConnection = clauseBlock.statementConnection_;
          break;
        default:
          throw TypeError('Unknown block type: ' + clauseBlock.type);
      }
      clauseBlock = clauseBlock.nextConnection &&
          clauseBlock.nextConnection.targetBlock();
    }

    // Observe how it calls `updateShape_`
    this.updateShape_();
  },
```

And finally, all you need to do is implement your serialization. This makes sure that you're blocks round-trip through being saved and loaded correctly. You'll either want to implement mutationToDom and domToMutation if you're serializing to XML, or saveExtraState and loadExtraState if you're serializing to JSON. There is more info about both of those pairs of hooks in the mutators documentation. And there are also examples for the controls_if blocks here.

Sketch

unread,
Jan 14, 2022, 12:53:12 PM1/14/22
to Blockly
Hi Beka,

Thanks for writing back.

I ended up with the following code:

Blockly.Blocks['switch_case'] = {
  init: function() {
    this.appendValueInput("NAME")
        .setCheck(null)
        .appendField("case");
    this.appendStatementInput("case")
        .setCheck(null);
    this.setPreviousStatement(true, null);
    this.setNextStatement(true, null);
    this.setColour(270);
      this.setMutator(new Blockly.Mutator(['case']));
 this.setTooltip("If the case value matches the switch value run the blocks");
 this.setHelpUrl("");
  },
    updateShape_: function() {
    for (let i = 1; this.getInput('c' + i); i++) {
      this.removeInput('c' + i);

      this.removeInput('DO' + i);
    }
    // Rebuild block.

    // Observe how it is looking at the `this.elseifCount_` property
    for (let i = 1; i <= this.caseCount_; i++) {
      this.appendValueInput('c' + i).appendField(

          Msg['CONTROLS_IF_MSG_ELSEIF']);
      this.appendStatementInput('DO' + i).appendField(
          Msg['CONTROLS_IF_MSG_THEN']);
    }
    },
    decompose: function(workspace) {
  var topBlock = workspace.newBlock('switch');
  topBlock.initSvg();
  return topBlock;
},
    compose: function(topBlock) {
         let toppBlock = topBlock.nextConnection.targetBlock();

    // Count number of inputs.
    this.caseCount_ = 0;

    const valueConnections = [null];
    const statementConnections = [null];
    while (toppBlock && !toppBlock.isInsertionMarker()) {
      switch (toppBlock.type) {
        case 'case':
          this.caseCount_++;
          valueConnections.push(toppBlock.valueConnection_);
          statementConnections.push(toppBlock.statementConnection_);
          break;
        default:
          throw TypeError('Unknown block type: ' + toppBlock.type);
      }
      toppBlock = toppBlock.nextConnection &&
          toppBlock.nextConnection.targetBlock();

    }

    // Observe how it calls `updateShape_`
    this.updateShape_();
}
};

sadly this results in a error.

Thanks

Op donderdag 13 januari 2022 om 14:13:08 UTC+1 schreef bekawe...@gmail.com:
Screenshot 2022-01-14 6.52.25 PM.png

Beka Westberg

unread,
Jan 14, 2022, 1:36:59 PM1/14/22
to blo...@googlegroups.com
Heyo! Looks like your `switch` block (which lives inside the mutator workspace) doesn't have a next connection, but your compose() function expects it to. Does that sound like it could be the problem?

--Beka

Sketch

unread,
Jan 14, 2022, 1:54:38 PM1/14/22
to blo...@googlegroups.com
Hi Beka,

This does sound like the problem.
However I am not sure how to fix this.

Thanks

Op vr 14 jan. 2022 om 19:36 schreef Beka Westberg <bekawe...@gmail.com>:

Beka Westberg

unread,
Jan 16, 2022, 7:50:22 AM1/16/22
to Blockly
Hello, sorry for the late response,

I believe that in the definition of your 'switch' block you need to add a `this.setNextStatement(true)` call:

```
Blockly.Blocks['switch'] = {
  init: function() {
    this.appendDummyInput()
        .appendField("switch");
    this.appendStatementInput("case")
        .setCheck(null);
    this.setInputsInline(true);
    // Add the below line.
    this.setNextStatement(true);

    this.setColour(270);
 this.setTooltip("");
 this.setHelpUrl("");
  }
};
```

I hope that helps! If you have any further questions please reply!
--Beka

Sketch

unread,
Jan 16, 2022, 1:10:25 PM1/16/22
to blo...@googlegroups.com
Hi Beka,

Thanks for writing back.

Don't worry about your late reply.

Anyways i followed your advise and ended up with the following code:
},
    mutationToDom: function() {
  // You *must* create a <mutation></mutation> element.
  // This element can have children.
  var container = Blockly.utils.xml.createElement('mutation');
  container.setAttribute('items', this.caseCount_);
  return container;
},

domToMutation: function(xmlElement) {
  this.caseCount_ = parseInt(xmlElement.getAttribute('items'), 10);
  // This is a helper function which adds or removes inputs from the block.
  this.updateShape_();
},
};

however this still doesn't work there are now errors.
I believe it's the ' // You *must* create a <mutation></mutation> element.'
But the docs don't explain where i have to place it.

Thanks

Op zo 16 jan. 2022 om 13:50 schreef Beka Westberg <bekawe...@gmail.com>:

Beka Westberg

unread,
Jan 17, 2022, 8:12:49 AM1/17/22
to blo...@googlegroups.com
Hmmm weird. It looks to me like you're creating the mutation element correctly, and returning it correctly.

Could you post the text of the error?

Best wishes,
--Beka

Sketch

unread,
Jan 17, 2022, 10:10:51 AM1/17/22
to blo...@googlegroups.com
Hi Beka.

Thanks for writing back.

There are now errors when testing.

Thanks

Sketch

unread,
Jan 17, 2022, 12:35:48 PM1/17/22
to Blockly
Hi Beka,
My appologies my auto correction once again replace no with now.

Anyways i mean that there are 0 errors

Thanks
Op maandag 17 januari 2022 om 16:10:51 UTC+1 schreef Sketch:

Beka Westberg

unread,
Jan 17, 2022, 12:37:31 PM1/17/22
to blo...@googlegroups.com
Yay I'm glad it's working! If you have any further questions always feel free to ask :D

Best of luck with your project!
--Beka

Sketch

unread,
Jan 17, 2022, 12:40:45 PM1/17/22
to blo...@googlegroups.com
Hi Beka,
Actually it's still not working my apologies if I made it look like it was solved.
But the console doesn't throw any errors.


Op ma 17 jan. 2022 om 18:37 schreef Beka Westberg <bekawe...@gmail.com>:
Screen recording 2022-01-17 6.39.18 PM.webm

Beka Westberg

unread,
Jan 18, 2022, 8:00:47 AM1/18/22
to blo...@googlegroups.com
Ahh ok I see. You want the case mini-blocks to go *inside* the switch mini-block. In that case you need to do something like this:

First change the compose function to look at the statement input instead of the next connection:
```
compose: function(topBlock) {
         // This line changed. Now you should actually be accessing the first case min-block
         let toppBlock = topBlock.getinputTargetBlock('case');

    // Count number of inputs.
    this.caseCount_ = 0;
    const valueConnections = [null];
    const statementConnections = [null];
    while (toppBlock && !toppBlock.isInsertionMarker()) {
      switch (toppBlock.type) {
        case 'case':
          this.caseCount_++;
          valueConnections.push(toppBlock.valueConnection_);
          statementConnections.push(toppBlock.statementConnection_);
          break;
        default:
          throw TypeError('Unknown block type: ' + toppBlock.type);
      }
      toppBlock = toppBlock.nextConnection &&
          toppBlock.nextConnection.targetBlock();
    }

    // Observe how it calls `updateShape_`
    this.updateShape_();
}
```

Then you can remove the next statement from your switch mini-block, because you don't actually want to use it. Sorry about recommending you add it before, I was confused haha.
```
Blockly.Blocks['switch'] = {
  init: function() {
    this.appendDummyInput()
        .appendField("switch");
    this.appendStatementInput("case")
        .setCheck(null);
    this.setInputsInline(true);
    // Removethe below line

    this.setNextStatement(true);
    this.setColour(270);
 this.setTooltip("");
 this.setHelpUrl("");
  }
};
```

I hope that helps! If you have any further questions please reply!
--Beka

Sketch

unread,
Jan 18, 2022, 2:31:56 PM1/18/22
to Blockly
Hi Beka.

I got it to work YaY.

Anyways now i have 2 questions.

1 how do i generate code for them.
2 why is it that when i open the mutator dialog again the block is reseted to it's default state.

THANKS


Op dinsdag 18 januari 2022 om 14:00:47 UTC+1 schreef bekawe...@gmail.com:

Beka Westberg

unread,
Jan 19, 2022, 8:08:25 AM1/19/22
to blo...@googlegroups.com
Hello,

> how do i generate code for them?

Well that's going to be different for every block. But generally you're going to use those properties (like this.caseCount_). You can check out the if block generator for an example.

> why is it that when i open the mutator dialog again the block is reset to its default state.

Looks to be because the decompose function isn't creating all of the sub blocks it needs to. It should probably look something more like this:
```
decompose: function(workspace) {
  var topBlock = workspace.newBlock('switch');
  topBlock.initSvg();
  var connection = topBlock.getInput('case').connection;
  for (var i = 0; i < this.caseCount_; i++) {
    var case = workspace.newBlock('case');
    case.initSvg();
    connection.connect(case.previousConnection);
    connection = case.nextConnection;
  }
  return topBlock;
},
```

I hope that helps! If you have any further questions please reply!
--Beka

Sketch

unread,
Jan 19, 2022, 8:32:47 AM1/19/22
to Blockly
Thank you Beka.

It now works as expected!

As for the code generation i'll try to code somthing up and i'll ask you questions if i fail.

THANKS

Op woensdag 19 januari 2022 om 14:08:25 UTC+1 schreef bekawe...@gmail.com:

Beka Westberg

unread,
Jan 19, 2022, 8:53:44 AM1/19/22
to blo...@googlegroups.com
Oh woops! I meant to post a link to the if block generator.

Best of luck :D

Sketch

unread,
Jan 20, 2022, 3:02:03 AM1/20/22
to Blockly
Hi Beka.

I got the code generation working YaY.

Blockly.JavaScript['switch_case'] = function(block) {
    var value_name1 = Blockly.JavaScript.valueToCode(block, 'NAME', Blockly.JavaScript.ORDER_ATOMIC);
  var statements_case1 = Blockly.JavaScript.statementToCode(block, 'case');
  // TODO: Assemble JavaScript into code variable.
  var code = "case " + value_name1 + ": {\n" + statements_case1 + "\nbreak;\n}"
  for( var i = 1; i < this.caseCount_ + 1; i++) {
        var value_name = Blockly.JavaScript.valueToCode(block, 'c' + i, Blockly.JavaScript.ORDER_ATOMIC);
  var statements_case = Blockly.JavaScript.statementToCode(block, 'DO' + i);
      var code = code + "\ncase " + value_name + ": {\n" + statements_case + "\nbreak;\n}"
  }
  return code;
};

Anyways i encountered while testing te child blocks are disconnected when the blok is updates.
Looking at the if/else/elseif block i added the reconnect child blocks function but results in the mutator breaking and a error.
Screenshot 2022-01-20 8.58.29 AM.png
Here is the blocks code:
//case block

Blockly.Blocks['switch_case'] = {
  init: function() {
    this.appendValueInput("NAME")
        .setCheck(null)
        .appendField("case");
    this.appendStatementInput("case")
        .setCheck(null)
              .appendField("do");

    this.setPreviousStatement(true, null);
    this.setNextStatement(true, null);
    this.setColour(270);
      this.setMutator(new Blockly.Mutator(['case']));
 this.setTooltip("If the case value matches the switch value run the blocks");
 this.setHelpUrl("");
  },
    updateShape_: function() {
    for (let i = 1; this.getInput('c' + i); i++) {
      this.removeInput('c' + i);
      this.removeInput('DO' + i);
    }
    // Rebuild block.

    // Observe how it is looking at the `this.elseifCount_` property
    for (let i = 1; i <= this.caseCount_; i++) {
      this.appendValueInput('c' + i).appendField("case").setAlign(Blockly.ALIGN_RIGHT);
      this.appendStatementInput('DO' + i).appendField("do");

    }
    },
decompose: function(workspace) {
  var topBlock = workspace.newBlock('switch');
  topBlock.initSvg();
  var connection = topBlock.getInput('case').connection;
  for (var i = 0; i < this.caseCount_; i++) {
    var c = workspace.newBlock('case');
    c.initSvg();
    connection.connect(c.previousConnection);
    connection = c.nextConnection;

  }
  return topBlock;
},
    compose: function(topBlock) {
         // This line changed. Now you should actually be accessing the first case min-block
         let toppBlock = topBlock.getInputTargetBlock('case');


    // Count number of inputs.
    this.caseCount_ = 0;
    const valueConnections = [null];
    const statementConnections = [null];
    while (toppBlock && !toppBlock.isInsertionMarker()) {
      switch (toppBlock.type) {
        case 'case':
          this.caseCount_++;
          valueConnections.push(toppBlock.valueConnection_);
          statementConnections.push(toppBlock.statementConnection_);
          break;
        default:
          throw TypeError('Unknown block type: ' + toppBlock.type);
      }
      toppBlock = toppBlock.nextConnection &&
          toppBlock.nextConnection.targetBlock();
    }

    // Observe how it calls `updateShape_`
    this.updateShape_();
            this.reconnectChildBlocks_(
        valueConnections, statementConnections);

},
    mutationToDom: function() {
  // You *must* create a <mutation></mutation> element.
  // This element can have children.
  var container = Blockly.utils.xml.createElement('mutation');
  container.setAttribute('items', this.caseCount_);
  return container;
},

domToMutation: function(xmlElement) {
  this.caseCount_ = parseInt(xmlElement.getAttribute('items'), 10);
  // This is a helper function which adds or removes inputs from the block.
  this.updateShape_();
},  
      /**
   * Reconnects child blocks.
   * @param {!Array<?RenderedConnection>} valueConnections List of
   * value connections for 'if' input.
   * @param {!Array<?RenderedConnection>} statementConnections List of
   * statement connections for 'do' input.
   * @param {?RenderedConnection} elseStatementConnection Statement
   * connection for else input.
   * @this {Block}
   */
    reconnectChildBlocks_: function(
      valueConnections, statementConnections) {

    for (let i = 1; i <= this.caseCount_; i++) {
      Mutator.reconnect(valueConnections[i], this, 'c' + i);
      Mutator.reconnect(statementConnections[i], this, 'DO' + i);
    }
  },
};

THANKS :D
Op woensdag 19 januari 2022 om 14:53:44 UTC+1 schreef bekawe...@gmail.com:

Beka Westberg

unread,
Jan 20, 2022, 7:42:20 AM1/20/22
to blo...@googlegroups.com
So in the if block file, Mutator is defined here. What you need to do to define it depends on your setup. If you're using imports, you can do that. Or if you're not, you should be able to use `Blockly.Mutator` instead of `Mutator`.

I hope that helps! If you have any further questions please reply!
--Beka

Sketch

unread,
Jan 21, 2022, 3:39:18 AM1/21/22
to Blockly
Yello Beka.
thanks for writing back i've changed Mutator to Blockly.Mutator.
wich resolves the error but sadly the child block still dont reconect.
i've checked the repo but i couldnt find anything tht i've missed.

Thanks.


Op donderdag 20 januari 2022 om 13:42:20 UTC+1 schreef bekawe...@gmail.com:

Beka Westberg

unread,
Jan 21, 2022, 8:02:56 AM1/21/22
to blo...@googlegroups.com
Hello again!

It looks like your block needs to define the saveConnections function. There's a great section in the mutator docs about saveConnections. Basically, you want to assign the blocks to reconnect to properties of your mini-blocks. Then in your compose function, you want to collect those associations back up, and pass them to reconnect. Here's the if block's saveConnections function.

I hope that helps! If you have any further questions please reply!
--Beka

Sketch

unread,
Jan 21, 2022, 8:45:39 AM1/21/22
to blo...@googlegroups.com
Yello Beka.
I didn't know that haha.

Anyways i got it working YAY.

screenshot (26).png
THANK YOU

Op vr 21 jan. 2022 om 14:02 schreef Beka Westberg <bekawe...@gmail.com>:
You received this message because you are subscribed to a topic in the Google Groups "Blockly" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/blockly/sS_UUW8-D-w/unsubscribe.
To unsubscribe from this group and all its topics, send an email to blockly+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/blockly/CAEBzR94e6R%3DoFB8zRaU80uRv92qm4O2AY4%3Dy4UHRvLMEOc9nvA%40mail.gmail.com.

Sketch

unread,
Jan 22, 2022, 9:37:27 AM1/22/22
to Blockly
Yello Beka.

I am soory i keep asking weustions anyways.
I've ran into another problem :/

So i am making a block wich creates objects.
Now i tried to make it like the list blocks

this is the code:
Blockly.Blocks['create_object'] = {
  init: function() {
    this.appendDummyInput()
        .appendField("create object");
    this.setOutput(true, null);
    this.setColour("#F99EA3");
 this.setTooltip("Create a new object with some value's (optionally) ");
 this.setHelpUrl("");
      this.setInputsInline(true);
      this.setMutator(new Blockly.Mutator(['object']));
  },
    updateShape_: function() {
    for (let i = 1; this.getInput('o' + i); i++) {
      this.removeInput('o' + i);

    }
    // Rebuild block.

    // Observe how it is looking at the `this.elseifCount_` property
    for (let i = 1; i <= this.objectCount_; i++) {
      this.appendValueInput('o' + i).appendField("").setAlign(Blockly.ALIGN_RIGHT);
    }
    },
decompose: function(workspace) {
  var topBlock = workspace.newBlock('object_top_block');
  topBlock.initSvg();
    var connection = topBlock.getInput('object').connection;
  for (var i = 0; i < this.objectCount_; i++) {
    var c = workspace.newBlock('object');

    c.initSvg();
    connection.connect(c.previousConnection);
    connection = c.nextConnection;
  }
  return topBlock;
},
    compose: function(topBlock) {
         // This line changed. Now you should actually be accessing the first case min-block
         let toppBlock = topBlock.getInputTargetBlock('object');


    // Count number of inputs.
    this.objectCount_ = 0;
    const valueConnections = [null];

    while (toppBlock && !toppBlock.isInsertionMarker()) {
      switch (toppBlock.type) {
        case 'object':
          this.objectCount_++;
          valueConnections.push(toppBlock.valueConnection_);

          break;
        default:
          throw TypeError('Unknown block type: ' + toppBlock.type);
      }
      toppBlock = toppBlock.nextConnection &&
          toppBlock.nextConnection.targetBlock();
    }

    // Observe how it calls `updateShape_`
    this.updateShape_();
            this.reconnectChildBlocks_(
        valueConnections);

},
    mutationToDom: function() {
  // You *must* create a <mutation></mutation> element.
  // This element can have children.
  var container = Blockly.utils.xml.createElement('mutation');
  container.setAttribute('items', this.objectCount_);

  return container;
},

domToMutation: function(xmlElement) {
  this.objectCount_ = parseInt(xmlElement.getAttribute('items'), 10);

  // This is a helper function which adds or removes inputs from the block.
  this.updateShape_();
},  
      /**
   * Reconnects child blocks.
   * @param {!Array<?RenderedConnection>} valueConnections List of
   * value connections for 'if' input.
   * @param {!Array<?RenderedConnection>} statementConnections List of
   * statement connections for 'do' input.
   * @param {?RenderedConnection} elseStatementConnection Statement
   * connection for else input.
   * @this {Block}
   */
    reconnectChildBlocks_: function(
      valueConnections ) {
    for (let i = 1; i <= this.objectCount_; i++) {
      Blockly.Mutator.reconnect(valueConnections[i], this, 'o' + i);
    }
  },
     saveConnections: function(topBlock) {
    let clauseBlock = topBlock.getInput('object').connection;
    let i = 1;
    while (clauseBlock) {
      switch (clauseBlock.type) {
        case 'object': {
          const inputIf = this.getInput('o' + i);
          clauseBlock.valueConnection_ =
              inputIf && inputIf.connection.targetConnection;
          i++;

          break;
        }
        default:
          throw TypeError('Unknown block type: ' + clauseBlock.type);
      }
      clauseBlock = clauseBlock.nextConnection &&
          clauseBlock.nextConnection.targetBlock();
    }
  },
};

And throws the following error in both compose and saveConnections

Screenshot 2022-01-22 3.27.47 PM.png

I am not sure what i've done wrong so that's why i am asking for some help.

THANKS
Op vrijdag 21 januari 2022 om 14:45:39 UTC+1 schreef Sketch:

Beka Westberg

unread,
Jan 22, 2022, 2:38:47 PM1/22/22
to blo...@googlegroups.com
Absolutely no problem! Always happy to help.

From the stack trace, it looks like the error is coming from this line:
```
var connection = topBlock.getInput('object').connection;
```

Does your topBlock have an input called 'object'? If not you'll either want to add one, or use the name of a different input.

Best wishes =)
--Beka

Sketch

unread,
Jan 23, 2022, 2:19:41 PM1/23/22
to blo...@googlegroups.com
Yello Beka,

Just to confirm something. 
'object' in var connection = topBlock.getInput('object').connection;
Should be the statement name. Right?
Because i did that and it fixed the problem.

Secondly i encountered another error:
Unknow block type :3.
This happens in the save conecctions function.

I checked the decompos and 'case block' save connection and as fae as i know it's  the same.

Thanks for your help.

Beka Westberg

unread,
Jan 24, 2022, 1:09:28 PM1/24/22
to blo...@googlegroups.com
> 'object' in var connection = topBlock.getInput('object').connection; Should be the statement name. Right? Because i did that and it fixed the problem.

Yep that is correct!

> Secondly i encountered another error: Unknow block type :3.

Hmm that is definitely weird, because you're right, saveConnections is very similar to your compose function, and 'object' matches the type you're using in the decompose function.

Are there any other blocks in your mutator flyout/connected to your top block that could be messing with things? Any block type not named exactly 'object' would be causing these problems.

I hope that helps!
--Beka


Sketch

unread,
Jan 24, 2022, 1:24:26 PM1/24/22
to blo...@googlegroups.com
Yello Beka,

>> Are there any other blocks in your mutator flyout/connected to your top block that could be messing with things? Any block type not named exactly 'object' would be causing these problems.

Nope.

Here is the new definition but i don't think it will be much help in research:

Blockly.Blocks['create_object'] = {
  init: function() {
    this.appendDummyInput()
        .appendField("create object");
    this.setOutput(true, null);
    this.setColour("#F99EA3");
 this.setTooltip("Create a new object with some value's (optionally) ");
 this.setHelpUrl("");
      this.setInputsInline(true);
      this.setMutator(new Blockly.Mutator(['object']));
  },
    updateShape_: function() {
    for (let i = 1; this.getInput('o' + i); i++) {
      this.removeInput('o' + i);
    }
    // Rebuild block.

    // Observe how it is looking at the `this.elseifCount_` property
    for (let i = 1; i <= this.objectCount_; i++) {
      this.appendValueInput('o' + i).appendField("").setAlign(Blockly.ALIGN_RIGHT);
    }
    },
decompose: function(workspace) {
  var topBlock = workspace.newBlock('object_top_block');
  topBlock.initSvg();
    var connection = topBlock.getInput('NAME').connection;
    let clauseBlock = topBlock.getInput('NAME').connection;

    let i = 1;
    while (clauseBlock) {
      switch (clauseBlock.type) {
        case 'object': {
          const inputIff = this.getInput('o' + i);
          clauseBlock.valueConnection_ =
              inputIff && inputIff.connection.targetConnection;

          i++;
          break;
        }
        default:
          throw TypeError('Unknown block type: ' + clauseBlock.type);
      }
      clauseBlock = clauseBlock.nextConnection &&
          clauseBlock.nextConnection.targetBlock();
    }
  },
};

Thanks

Op ma 24 jan. 2022 om 19:09 schreef Beka Westberg <bekawe...@gmail.com>:

Beka Westberg

unread,
Jan 25, 2022, 8:14:31 AM1/25/22
to blo...@googlegroups.com
Ahh I think I found the problem!

```
saveConnections: function(topBlock) {
    let clauseBlock = topBlock.getInput('NAME').connection;
    let i = 1;
    while (clauseBlock) {
      switch (clauseBlock.type) {
        case 'object': {
          const inputIff = this.getInput('o' + i);
          clauseBlock.valueConnection_ =
              inputIff && inputIff.connection.targetConnection;
          i++;
          break;
        }
        default:
          throw TypeError('Unknown block type: ' + clauseBlock.type);
      }
      clauseBlock = clauseBlock.nextConnection &&
          clauseBlock.nextConnection.targetBlock();
    }
  },
```

The first line `let clauseBlock = topBlock.getInput('NAME').connection;` is setting `clauseBlock` to be the connection, instead of an actual block. I think it should actually be `let clauseBlock = topBlock.getInputTargetBlock('NAME');`

I hope that helps! If you have any further questions please reply!
--Beka
Message has been deleted

Sketch

unread,
Jan 25, 2022, 9:41:27 AM1/25/22
to Blockly
THANKS Beka,.That fixed the mutator UI.
Now sadly When i add a block to the mutator i get a error.
Cannot Read null of 'conect' or something like that.

Thanks

Op dinsdag 25 januari 2022 om 14:14:31 UTC+1 schreef bekawe...@gmail.com:

Beka Westberg

unread,
Jan 26, 2022, 9:58:51 AM1/26/22
to blo...@googlegroups.com
I'm sorry you're still getting an error man :/ Could you tell me a little bit about how you're debugging? I obviously love to help out/give advice, but it seems like a bit of a pain for you that you have to keep waiting on me haha. Maybe I could give you some tips on debugging so you could get work done faster?

Best wishes,
--Beka

Sketch

unread,
Jan 26, 2022, 1:21:11 PM1/26/22
to Blockly
Hi Beka,

Thanks for writing back.

>> Could you tell me a little bit about how you're debugging?

Well most of the time i open the console and look at the error.
but the error are most of the time not that clear.

>> I obviously love to help out/give advice, but it seems like a bit of a pain for you that you have to keep waiting on me haha

Please don't worry about it i am glad you are helping.
Actually i feel like it is a pain for you to keep helping me.

Op woensdag 26 januari 2022 om 15:58:51 UTC+1 schreef bekawe...@gmail.com:

Mark Friedman

unread,
Jan 26, 2022, 6:51:17 PM1/26/22
to blo...@googlegroups.com
Luke,

  I find the Chrome (or other browser's) debugger to be one of the most useful (and underutilized tools) for debugging.

  One of the basic things you can do to debug is to click on an item in the stack trace in the console and set a breakpoint there.  In particular, I would suggest picking the highest line in the stacktrace that appears to refer to your own written code (i.e. rather than some code that might be within some Blockly library function). If there is no stacktrace then just click on the single link in the console that appears with the error message. Then run your program again and it will pause at that point.  

  Now you can inspect the values of your local variables, evaluate expressions, etc. to see if the data is what you expected.  Moreover, you can then switch to other points in the live stacktrace and see what got passed into each of the functions in the stack and how those values were computed.  Often, that will lead you to set a new breakpoint at some earlier statement in your program so that you can step through the statements, inspecting the state of the program as you go through it.

  One thing to note is that since it appears that you are using Webpack, all of your code will be in a single file that Chrome will display as something like "index.js_+_23_modules:5917:21".   That will essentially be the concatenation of all your imports (and their imports ...) with your source code.  There may also be some relatively minor changes to the code due to Webpack's dealing with the ES6 module system.

  For an introduction to the Chrome debugger, one place to start is here.  There are probably other useful resources as well.

  Hope this helps.

-Mark


Message has been deleted

Sketch

unread,
Jan 27, 2022, 10:48:41 AM1/27/22
to blo...@googlegroups.com
Hi Mark and Beka.

Thanks for sharing the explanation.

I"va managed to fix the issue I had 'topBlock.getInputTargetBlock('NAME');' instead of 'topBlock.getInput('NAME').connection;'

I didn't notice it until now 😅

Anyways THANKS for all your help!!
image.png

Reply all
Reply to author
Forward
0 new messages