How To Limit Which Blocks Are Accepted As Statement Input

708 views
Skip to first unread message

Seth Swango

unread,
Aug 3, 2021, 2:53:06 PM8/3/21
to Blockly
Screenshot 2021-08-03 135039.pngI'll start by saying I am new to blocky and inheriting this complex project. Please be as verbose and detailed in your explanations as you have time for. Your patience is much appreciated. 

I have a block called 'launch_task' which accepts a nested child block as 'statement input'. I want to place restrictions on this block such that only certain blocks can be nested as a child. 

The Parent Block: 
        Blockly.Blocks['launch_task'] = {
            init: function () {
                this.appendDummyInput()
                    .appendField('Launch Task')
                    .appendField(new Blockly.FieldDropdown(taskMenu), self.taskDropdownName);
                this.appendDummyInput()
                    .appendField('Resequence')
                    .appendField(new Blockly.FieldCheckbox('FALSE'), 'Resequence')
                    .appendField('Copy Assignments')
                    .appendField(new Blockly.FieldCheckbox('TRUE'), 'CopyAssignments')
                    .appendField('Copy Attachments')
                    .appendField(new Blockly.FieldCheckbox('FALSE'), 'CopyAttachments');
                this.appendStatementInput('task_actions')
                    .setCheck(null)
                    .appendField('with')
                    .appendField(new Blockly.FieldVariable('task'), 'TASK_VAR')
                    .appendField('do');
                this.setPreviousStatement(true, null);
                this.setNextStatement(true, null);
                this.setColour(self.blockColor);
                this.setTooltip('Launch a task from the current process template.');
                this.setHelpUrl(this.helpUrl);
            }
        };
    }

The Child Block: 

    buildBlock(): void {
        const self = this;
        const possiblePermissions = [['Edit', '8'], ['Read-Only', '4']];
        Blockly.Blocks['route_task_to_role'] = {
            init: function () {
                this.appendDummyInput()
                    .appendField('Route Task');
                this.appendDummyInput()
                    .appendField('to Role');
                this.appendValueInput(self.role_field_value)
                    .setCheck('String');
                this.appendDummyInput()
                    .appendField('with Permission as')
                    .appendField(new Blockly.FieldDropdown(possiblePermissions), self.permissionSelected);
                this.setInputsInline(true);
                this.setPreviousStatement(true, null);
                this.setNextStatement(true, null);
                this.setColour(self.blockColor);
                this.setTooltip('Route a task');
                this.setHelpUrl(this.helpUrl);
            }
        };


How can I allow for this behavior: 
Screenshot 2021-08-03 134504.png

But none of these behaviors: 
Screenshot 2021-08-03 134620.png

I have attempted to utilize  @blockly/plugin-strict-connection-checks but I am first not sure if this can provide the functionality I desire, and second got an install error that seems to indicate I need to change versions of the blockly primary package- which is out of the question for this project as it would break the production implementation.

Screenshot 2021-08-03 135039.png

Anyone have any ideas on how to address this? Thanks in advance! 

Beka Westberg

unread,
Aug 3, 2021, 7:14:49 PM8/3/21
to Blockly
Hello,

First of all, congratulations on inheriting a new codebase :D I know that experience is always *so* fun haha.

Now, connection checks are pretty confusing and you asked for detail, so I'm going to give a brief explanation. For a more in-depth explanation you can see my blog post here.
Every connection in Blockly is associated with an array of strings. This is called a "connection check". Usually the strings in these arrays represent *data types* like "String", "Number", or "Array", but they can be anything you want (see blog post). Two connections can connect if *any* of the strings in *either* array match. And additionally, if *either* connection has an empty/null array, the connections can connect.

For example, these pairs of arrays can connect:
  • ["String"], ["String", "Number"]
  • ["String", "Number"], ["Number"]
  • ["String"], []
  • [], ["Number"]  
These pairs of arrays cannot connect:
  • ["String"], ["Number"]
  • ["String", "Number"], ["Array"]
Now let's talk about the strict-connection-checker. I said before that "if *either* connection has an empty/null array, the connections can connect". The strict-connection-checker removes that ability. This is useful if you are using Blockly's built-in blocks (like if-blocks). Without the strict-connection-checker if-blocks could go anywhere. With the strict-connection-checker they can *only* connect to other connections with *empty* connection checks.

But if you're not using any of Blockly's built-in blocks, you don't need to worry about this. Just be sure to always give your connections a connection check!

Now let's talk about your specific situation. It seems like you only want actions that deal with tasks to be able to connect to that statement input. So you should add a connection check which represents that to both blocks. Something like "task-action" could work:
```
        Blockly.Blocks['launch_task'] = {
            init: function () {
                this.appendDummyInput()
                    .appendField('Launch Task')
                    .appendField(new Blockly.FieldDropdown(taskMenu), self.taskDropdownName);
                this.appendDummyInput()
                    .appendField('Resequence')
                    .appendField(new Blockly.FieldCheckbox('FALSE'), 'Resequence')
                    .appendField('Copy Assignments')
                    .appendField(new Blockly.FieldCheckbox('TRUE'), 'CopyAssignments')
                    .appendField('Copy Attachments')
                    .appendField(new Blockly.FieldCheckbox('FALSE'), 'CopyAttachments');
                this.appendStatementInput('task_actions')
                     // This line changed!
                     // Now only connection checks that are empty, or contain "task-action" will be able to connect.
                    .setCheck("task-action")
                    .appendField('with')
                    .appendField(new Blockly.FieldVariable('task'), 'TASK_VAR')
                    .appendField('do');
                this.setPreviousStatement(true, null);
                this.setNextStatement(true, null);
                this.setColour(self.blockColor);
                this.setTooltip('Launch a task from the current process template.');
                this.setHelpUrl(this.helpUrl);
            }
        };
    }
    buildBlock(): void {
        const self = this;
        const possiblePermissions = [['Edit', '8'], ['Read-Only', '4']];
        Blockly.Blocks['route_task_to_role'] = {
            init: function () {
                this.appendDummyInput()
                    .appendField('Route Task');
                this.appendDummyInput()
                    .appendField('to Role');
                this.appendValueInput(self.role_field_value)
                    .setCheck('String');
                this.appendDummyInput()
                    .appendField('with Permission as')
                    .appendField(new Blockly.FieldDropdown(possiblePermissions), self.permissionSelected);
                this.setInputsInline(true);
                
                // This line changed!
                this.setPreviousStatement(true, "task-action");
                // Add "task-action" here if you want more task actions to connect to the bottom of this block.
                // Otherwise, just remove this line to remove the bottom connection.
                this.setNextStatement(true, "task-action");

                this.setColour(self.blockColor);
                this.setTooltip('Route a task');
                this.setHelpUrl(this.helpUrl);
            }
        };
```

Now for the problem of not allowing users to connect the "Route Task" block to the top and bottom of the parent you will need to set the next and previous connections of the parent block to something non-null/empty. Because again, empty checks can connect to anything by default:
```
        Blockly.Blocks['launch_task'] = {
            init: function () {
                this.appendDummyInput()
                    .appendField('Launch Task')
                    .appendField(new Blockly.FieldDropdown(taskMenu), self.taskDropdownName);
                this.appendDummyInput()
                    .appendField('Resequence')
                    .appendField(new Blockly.FieldCheckbox('FALSE'), 'Resequence')
                    .appendField('Copy Assignments')
                    .appendField(new Blockly.FieldCheckbox('TRUE'), 'CopyAssignments')
                    .appendField('Copy Attachments')
                    .appendField(new Blockly.FieldCheckbox('FALSE'), 'CopyAttachments');
                this.appendStatementInput('task_actions')
                    .setCheck("task-action")
                    .appendField('with')
                    .appendField(new Blockly.FieldVariable('task'), 'TASK_VAR')
                    .appendField('do');

                // These lines changed!
                this.setPreviousStatement(true, "some check besides task-action");
                this.setNextStatement(true, "some check besides task-action");

                this.setColour(self.blockColor);
                this.setTooltip('Launch a task from the current process template.');
                this.setHelpUrl(this.helpUrl);
            }
        };
    }
```

And finally, the problem of not allowing users to connect the green block (or other blocks) to the parent block's statement input. To achieve this, your green block must have non-null/empty connection checks on its next and previous connections (as show above). This is true of *all* of the blocks that you want to prevent from going into that statement input. So if you add connection checks (which are not "task-action") to all of those blocks' next and previous connections, you can get this working. But that may take quite a bit of work :/ If you want to go for it, I recommend regex haha.

I hope that helps! I know it's a lot, so if you have any further questions definitely ask! Best of luck :D
--Beka


Message has been deleted

Seth Swango

unread,
Aug 4, 2021, 5:01:14 PM8/4/21
to Blockly

This is perfect Beka!

Thank you for all your help. You rock.

I'm trying to work through installing the strict type checking extension in a more recent thread if you by chance have time to take a look.

Best,

Seth

Beka Westberg

unread,
Aug 4, 2021, 6:40:48 PM8/4/21
to blo...@googlegroups.com
Hi again Seth :D

> In the case of statement input, how do I define the 'output' of the statement block?

Hmm statement blocks don't have an output connection, but they do have a previous connection which is basically the equivalent.
Screenshot 2021-08-04 15.31.17.png
(sorry for the terrible writing, I did that with a mouse haha)

Each connection is associated with an array of strings, and when one connection tries to connect to another connection that's when they try to find a string that exists in both arrays.

To set the type of a previous connection on a statement block you want to do:
```
this.setPrevious(true, ['my connection check'])
```

> I'm trying to work through installing the strict type checking extension in a more recent thread if you by chance have time to take a look.
I think Sam is helping you out, and he's more knowledgeable in this area haha. But I'll definitely take a look and post if I have anything to add o7

Best,
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/1cf7ecc1-697c-4ba1-9f90-67f1d1337208n%40googlegroups.com.
Reply all
Reply to author
Forward
0 new messages