Add numeric range integer input validation for input(s) of a custom block

30 views
Skip to first unread message

Mäh W.

unread,
Jan 16, 2026, 6:20:16 AM (yesterday) Jan 16
to Blockly
Hi all,

I am the developer of WebPBrick which allows to program the first generation LEGO Mindstorms RCX brick with NQC via a web-based IDE (https://www.webpbrick.com/ide/).

There's also a second IDE where you can program the RCX block-based. It's using Blockly and emits NQC code which can then again be compiled in the browser (using WebAssembly) and transferred to the brick: https://www.webpbrick.com/nqc/blocknqc/

As pupils might use this, they shall be supported in preventing errors by adding input validation on fields of custom blocks.

One of those fields is the 'output_setpower' block:

Screenshot 2026-01-15 at 12.06.00.png

```
// Block for setting motor/output power
{
"type": "output_setpower",
"message0": "set output %1 power level to %2",
"args0": [
{
"type": "field_dropdown",
"name": "OUTPUT",
"options": [
[
"A",
"OUT_A"
],
[
"B",
"OUT_B"
],
[
"C",
"OUT_C"
],
[
"A + B",
"OUT_A+OUT_B"
],
[
"A + C",
"OUT_A+OUT_C"
],
[
"B + C",
"OUT_B+OUT_C"
],
[
"A + B + C",
"OUT_A+OUT_B+OUT_C"
]
]
},
{
"type": "input_value",
"name": "POWER",
"check": "Number"
}
],
"inputsInline": true,
"previousStatement": null,
"nextStatement": null,
"colour": outputsCategoryCol,
"tooltip": "",
"helpUrl": ""
},
```

The code for the toolbox looks as follows:
```

{
type: 'output_setpower',
kind: 'block',
inputs: {
'POWER': {
'shadow': {
'type': 'math_number',
'fields': {
'NUM': 7
}
}
}
}
}
```

(Sorry, copy-pasting the code here made it loose its indentation.)

This way, it currently accepts numeric literals, variables and mathematical expressions -which gives great flexibility.

However, I'd like to limit the valid range for numeric literals to 0, 1, 2, ..., 6, 7 -- so integers in the range from 0 to 7. I have tried different approaches, but have not succeeded so far:

1. Switching from field type "input_value" to "field_number": this allows to add constraints "min", "max" and "precision". So exactly, what I'd like to have. However, this prevents further use of a "variable" block and the "math_number" block -- the can no longer be dragged in an replace the default shadow.

2. Adding a validator function to the block or field -- does not seem to be called for field type "input_value.

3. Registering a Blockly extension as shown here: Validating Blocks and Displaying a Warning Indicator -- the accessor `getField()` obviously does apply to fields and not inputs. Also it only seems to be called when the block is being created and dragged around ((dis-)connected?).

Any idea how to achieve this and keep the previously described flexibility?

I might want to add other validators for other blocks - when using numeric literals, they shall still be limited to integers and/or a specific value range (some may be OK to be floats).

Cheers
Mäh

Maribeth Moffatt

unread,
Jan 16, 2026, 9:53:20 AM (21 hours ago) Jan 16
to Blockly
Hello,

Thank you for the question, and for sharing your site, it looks great!

I'll start by clarifying some terms as the difference is important. The input_value is a type of input; it's not a field. Inputs can contain fields and the input_value allows you to connect a block to it. You can see some examples in the docs here: https://developers.google.com/blockly/guides/create-custom-blocks/define/block-anatomy#inputs

When you connect a variable or a math block to the input, you're using a standalone block that has its own dummy input (meaning there are no places to attach more blocks to it) that contains a field_variable or a field_number.

The zelos renderer that you're using kind of obscures this, because it's harder to tell the difference between a block with a field_number, and a block that has an input_value with a number block attached. When you switched the block to use the field_number, did you notice the number area is now rectangular shaped instead of circular? That's the only difference and it's pretty subtle. If you switch to the thrasos renderer it's more clear what's going on (in my opinion).

Now, onto validation.

One thing to note is that you can't validate things perfectly when you allow the user to connect variables. Blockly doesn't *run* the code, it only generates it. There is no way to know the value of a variable while you're manipulating the blocks. So you still need to take care to validate all of the inputs during code generation time. For this block, you might want to, for example, round the number and and cap it to 7 so that your program doesn't crash (or motor overheat). While it's nice to do as much of this validation as possible while the user is constructing their block program, you should still take precautions because you simply can't know what the value is when there are variables involved.

You can add some validation when a user connects a simple math_number block. You can add an onchange listener to the motor block that checks if the block type connected to the POWER input is math_number. If it is, then check if the math_number's NUM field meets your criteria, and either change the value or put a warning on the block.

But, then what if a user connects a math_operation block? Or a complex mathematical expression? You'll be back to the same problem with variables, as you won't know the "value" of the block that's being connected until the code is run. So you still need to take care when you're generating code.

So, yes, it is possible to generate a warning for the simple case of only a single math_number block being connected. But it is not possible to give a warning in all cases when you allow users to connect arbitrarily complex blocks. 

One way some applications get around this is to provide two alternative blocks. One simple block that just has a field_number with all of the input validation. You might even use a dropdown if the only choices are numbers 1-7. And then a more complex block that allows users to connect other blocks. How exactly to offer these multiple levels of complexity within the same application is certainly open to debate though!

I hope that helps. Let us know if you have additional questions about implementing the onchange listener or anything else.

Best,
Maribeth

Mark Friedman

unread,
Jan 16, 2026, 5:32:35 PM (13 hours ago) Jan 16
to blo...@googlegroups.com
I agree with everything Maribeth said and would just add a comment related to the following that you said:

1. Switching from field type "input_value" to "field_number": this allows to add constraints "min", "max" and "precision". So exactly, what I'd like to have. However, this prevents further use of a "variable" block and the "math_number" block -- the can no longer be dragged in an replace the default shadow.

If you create a new output block which contains a field_number with the constraints that you want, then you can make that the shadow block that you attach to your output_setpower block in the toolbox.  If you do that then you have the convenience of that field_number for the case where the power level is a constant,  while still allowing variables and other math blocks to be connected (since the block is a shadow block).

Hope this helps.

-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.
To view this discussion visit https://groups.google.com/d/msgid/blockly/92f0fdd5-3b35-43cb-97e8-4728442e9d9an%40googlegroups.com.

Mäh W.

unread,
Jan 16, 2026, 5:48:55 PM (13 hours ago) Jan 16
to Blockly
Hi Maribeth,

thanks a lot for your very detailed and helpful answer!

I had indeed noticed the different style after changing to field_number but I couldn't recall these basic details as I had not worked with Blockly for a long time. Your explanations made it clear! Also thanks for pointing to the different renderers!

Also thanks for explaining the limits (or potential pitfalls) of input validation. It's really only meant as a first level of validation for the user. The NQC compiler and the LEGO RCX firmware will have additional checks (and the code generation could add additional checks). It's clear that expressions more complex than having a simple number require more complex validation - this is where I don't want to dig deeper and keep the flexibility. It's OK to let the compiler and/or firmware handle those more complex errors in these cases.

Pointing me to the onChange event listener was exactly, what I was missing and am using now.

A generic helper function that I have added. It may contain some redundant checks though. Maybe others find it useful, so I post it here.
(I'll also add a question about this approach below.)

```
function checkIntegerInputRange(block, inputName, minValue, maxValue) {
  let warnText = "";
  let targetBlock = block.getInput(inputName).connection.targetBlock();
  if (targetBlock) {
    let inputList = targetBlock.inputList;
    if (Array.isArray(inputList) && (inputList.length == 1)) {
      if (inputList[0].type == Blockly.inputTypes.DUMMY) {
        let fieldRow = inputList[0].fieldRow;
        if(Array.isArray(fieldRow) && (fieldRow.length == 1) && (fieldRow[0].name == "NUM")) {
          let value = fieldRow[0].getValue();
          if (!Number.isInteger(value)) {
            warnText += 'Accepting only integer numbers: rounded last input. ';
            value = Math.round(value);
          }
          if (value < minValue) {
            warnText += 'Accepting only numbers up to ' + minValue + '. ';
            value = minValue;
          } else if(value > maxValue) {
            warnText += 'Accepting only numbers below ' + maxValue + '. ';
            value = maxValue;
          }
          fieldRow[0].setValue(value);
        }
      }
    }
  }
  block.setWarningText(warnText); // empty warning text is not displayed, which makes it a nice default
}

```

Having the core functionally in a function, this allows me to use it in multiple blocks using the second mechanism: extensions.

```
    {
      "type": "output_setpower",
      "message0": "set output %1 power level to %2",
      "args0": [
...
        {
          "type": "input_value",
          "name": "POWER",
        }
      ],
      "inputsInline": true,
      "previousStatement": null,
      "nextStatement": null,
      "colour": outputsCategoryCol,
      "tooltip": "",
      "helpUrl": "",
      "extensions": [
        'power_range_validation',
      ],
    },
(...)
    
    Blockly.Extensions.register('message_range_validation', function() {
  this.setOnChange(function(changeEvent) {
    if (changeEvent instanceof Blockly.Events.BlockChange) {
      checkIntegerInputRange(this, 'MESSAGE', 0, 255);
    }
  });
});

```

+

```
    {
      "type": "infrared_msgsend",
      "message0": "send IR message %1 via IR",
      "args0": [
        {
          "type": "input_value",
          "name": "MESSAGE",

          "check": "Number"
        }
      ],
      "inputsInline": true,
      "previousStatement": null,
      "nextStatement": null,
      "colour": infraredCategoryCol,
      "tooltip": "",
      "helpUrl": "",
      "extensions": [
        'message_range_validation',
      ],
    },
(...)

   Blockly.Extensions.register('power_range_validation', function() {
  this.setOnChange(function(changeEvent) {
    if (changeEvent instanceof Blockly.Events.BlockChange) {
      checkIntegerInputRange(this, 'POWER', 0, 7);
    }
  });
});

```

Overall, this works quite nicely. However, there's one final question:

`checkIntegerInputRange()` builds a warning message and performs automatic corrections, i.e.
- rounding floats to integers (e.g. 3.2 to 3 or 5.7 to 6)
- range checking (setting -4 to lower limit of 0 and 9 to upper limit of 7).
And even a combination of both, i.e. 12.4 also becomes 7.
To inform the user about these automatic corrections, I want to display them to the user as well by calling `block.setWarningText()`.

The "problem" is that calling `setValue()` with the auto-corrected value will not only set the value but trigger another onchange event with a valid value which leads to the removal of the warning message. Is there a way to work around this? Manipulating the protected value field directly instead of calling a setter method? Feels wrong and I don't know if this would even work.
Or do I have to choose between "warn and let the user correct their input themselves" and "auto-correct but do not show a warning about what happened"?

I also feared that calling.`setValue()` leads to an infinite number of calls ("recursion"), but luckily overwriting with the same value seems not to cause a change event (makes sense).

Again, thanks a lot for your help!

Regards
Mäh
Reply all
Reply to author
Forward
0 new messages