Ordering Errors with setting inputs inline/adding new inputs.

124 views
Skip to first unread message

Amber B

unread,
Dec 3, 2018, 12:57:17 PM12/3/18
to Blockly
I can't figure this out, and it's been driving me up the wall:

I have a block definition like so:

Blockly.Blocks['Import_Email'] = {
    init: function() {
        this.setNextStatement(true);
        this.setPreviousStatement(true);
        this.setInputsInline(false);
        // generate the runtime variables to use when the emails are read from the server
        if(this.workspace) {
            let varsToCreate = [
                {
                    varName: 'emailReadId',
                    key: 'varId'
                },
                {
                    varName: 'emailReadSubject',
                    key: 'varSubject'
                },
                {
                    varName: 'emailReadBody',
                    key: 'varBody'
                },
                {
                    varName: 'emailReadFromAddress',
                    key: 'varFromAddress'
                },
                {
                    varName: 'emailReadToAddresses',
                    key: 'varToAddresses'
                },
                {
                    varName: 'emailReadCCAddresses',
                    key: 'varCCAddresses'
                },
                {
                    varName: 'emailReadBCCAddresses',
                    key: 'varBCCAddresses'
                }
            ];
            if(this.workspace.getVariable) {
                varsToCreate.forEach(function(varToCreate){
                    let varName = varToCreate.varName;
                    let key = varToCreate.key;
                    if(!this.workspace.getVariable(varName)) {
                        this[key] = this.workspace.createVariable(varName);
                    }
                }.bind(this));
            } else {
                var variableList = this.workspace.variableList || [];   
                varsToCreate.forEach(function(varToCreate){
                    let varName = varToCreate.varName;
                    let key = varToCreate.key;
                    if(variableList.indexOf(varName) === -1) {
                        this[key] = this.workspace.createVariable(varName);
                    }
                }.bind(this));
            }
        }
        
        // configure the block inputs
        this.appendDummyInput()
            .appendField('Read Email');
        this.setTooltip('Reads HTML-enabled emails from specified server using entered credentials.');
        this.setColour(Blockly.Msg.INTEGRATIONS_HUE ? Blockly.Msg.INTEGRATIONS_HUE : Blockly.Blocks.integrations.HUE); 
        this.appendInputs();
  },
    appendInputs: function() {
        this.appendDummyInput()
            .appendField('Read From')
            .setAlign(Blockly.ALIGN_RIGHT)
            .appendField(new Blockly.FieldDropdown([['Outlook365', 'outlook365'], ['Exchange', 'exchange']]), 'serverType');
        this.appendValueInput('host')
            .appendField('Host')
            .setAlign(Blockly.ALIGN_RIGHT);        
        this.appendValueInput('username')
            .appendField('Username')
            .setAlign(Blockly.ALIGN_RIGHT);
        this.appendValueInput('password')
            .appendField('Password')
            .setAlign(Blockly.ALIGN_RIGHT);
        this.appendValueInput('dateRangeStart')
            .appendField('Date Start')
            .setAlign(Blockly.ALIGN_RIGHT);
        this.appendValueInput('dateRangeEnd')
            .appendField('Date End')
            .setAlign(Blockly.ALIGN_RIGHT);
        this.appendValueInput('mailbox')
            .appendField('Mailbox')
            .setAlign(Blockly.ALIGN_RIGHT);
        this.appendDummyInput('BEFORE_BLOCK_LABEL')
            .appendField('For Each Email Read, do');
        this.appendStatementInput('emailActions');
    }
};

This mostly works fine; I can load the block up without incident, etc. However, whenever I first load up the drawer to drag the block into the workspace, I get "Uncaught TypeError: Cannot read property 'connectionDBList' of null". The full stack trace is:

Uncaught TypeError: Cannot read property 'connectionDBList' of null
    at Blockly.RenderedConnection.Blockly.Connection (blockly_compressed.js:1015)
    at new Blockly.RenderedConnection (blockly_compressed.js:1047)
    at Blockly.BlockSvg.makeConnection_ (blockly_compressed.js:1461)
    at Blockly.BlockSvg.Blockly.Block.appendInput_ (blockly_compressed.js:1404)
    at Blockly.BlockSvg.appendInput_ (blockly_compressed.js:1460)
    at Blockly.BlockSvg.Blockly.Block.appendValueInput (blockly_compressed.js:1396)
    at Blockly.BlockSvg.appendInputs (citdev-blocks.js:6268)
    at Blockly.BlockSvg.init (citdev-blocks.js:6261)
    at Blockly.BlockSvg.Blockly.Block (blockly_compressed.js:1369)
    at new Blockly.BlockSvg (blockly_compressed.js:1425)


This appears to happen in a few different blocks; weirdly, all in this drawer. I know this happens specifically in appendInputs when I call appendValueInput. (We often use a separate appendInput function to add all of our inputs for various reasons, but this issue still happens when I paste the contents of appendValueInput in directly.)

As near as I can tell, the problem is that this specific block has a null workspace when it attempts to load up in the drawer. What I can't figure out is why. It works perfectly fine the second time I reopen the drawer, and other blocks in other drawers evidently have workspaces under the same conditions.

We have a React and Flux-based app, so we're using the following package to manage our Blockly, including their drawer setup, if that helps: https://github.com/patientslikeme/react-blockly-component

(Of note is that if I just give in and change the Blockly Connection instructor to check that the workspace exists before trying to read a property off of it, it instead kicks me down to the BlockSvg constructor erring out, so this rabbit hole goes deeper than I'd thought.)

Amber B

unread,
Dec 3, 2018, 12:58:14 PM12/3/18
to Blockly
Shoot, I meant to title this differently; the title was from when I thought this issue was specific to the order in which I did something (which later turned out not to be true).

Rachel Fenichel

unread,
Dec 3, 2018, 6:29:51 PM12/3/18
to Blockly
Hi Amber,

This sounds like an issue in initialization of the flyout (drawer).  Particularly because it only happens the first time.  It happens on the value inputs but not dummy inputs because dummy inputs don't have connections.  

What's really weird to me is that the block constructor uses the workspace object well before the connection constructor does.

Initialization order is:
- Call block constructor
  - Block constructor does a bunch of basic setup, adds itself to the workspace, etc.
  - Block constructor calls your provided init function
  - Your init function calls appendValueInput
    - which calls appendInput_
      - which calls makeConnection_
        - which calls the RenderedConnection constructor
          - which calls the Connection constructor

Here's the connection constructor:
/**
 * Class for a connection between blocks.
 * @param {!Blockly.Block} source The block establishing this connection.
 * @param {number} type The type of the connection.
 * @constructor
 */

Blockly.Connection = function(source, type) {
 
/**
   * @type {!Blockly.Block}
   * @protected
   */

 
this.sourceBlock_ = source;
 
/** @type {number} */
 
this.type = type;
 
// Shortcut for the databases for this connection's workspace.
 
if (source.workspace.connectionDBList) {                            //  <------------ Error is here
   
this.db_ = source.workspace.connectionDBList[type];
   
this.dbOpposite_ =
        source
.workspace.connectionDBList[Blockly.OPPOSITE_TYPE[type]];
   
this.hidden_ = !this.db_;
 
}
};


source is the parent block, which definitely exists--that's where this function was called from.  And it should have a workspace, because this line has already happened.

That *really* sounds like an issue in the react component, but I have a few questions for continuing debugging.

You say it's for only one drawer, but for multiple blocks.  
- Does it happen for every block in that drawer?  
- If you add a block that was working in another drawer into your problem drawer, does it also fail?
- Is it the first drawer that ever opens?

Rachel

Amber B

unread,
Dec 4, 2018, 8:38:39 AM12/4/18
to Blockly
Hi Rachel,

It continues to happen to the problem block in other drawers, and does not happen to every block in the drawer, nor does moving a block to the drawer cause the problem. The other problem block (which I now think may actually have a different issue, so we should focus on this block specifically) still has the problem in other drawers as well.

Rachel Fenichel

unread,
Dec 4, 2018, 1:50:05 PM12/4/18
to Blockly
Okay, so it's block-specific.  Cool. 

I just pulled that block definition into the playground (and set the color appropriately so it'll load) and I'm seeing the same thing you are:
- works if I load from XML in the workspace
- works if it's in an always-open flyout
- fails when in a flyout category, on the first time I open that category
- works after the first time

So it's not the react component, and I have a way to debug it in the playground.  I'll take a look and get back to you.

Cheers,
Rachel

Rachel Fenichel

unread,
Dec 4, 2018, 5:14:07 PM12/4/18
to Blockly
I've got it, and it's recursion all the way down!
If you expand the stack on the error, you'll see it's a lot deeper.  Here's part of mine:


Blockly.Connection@connection.js:48
Blockly.RenderedConnection@rendered_connection.js:43
Blockly.BlockSvg.makeConnection_@block_svg.js:1334
Blockly.Block.appendInput_@block.js:1507
Blockly.BlockSvg.appendInput_@block_svg.js:1286
Blockly.Block.appendValueInput@block.js:1233
appendInputs@playground.html?dir=ries&side=start:209
init@playground.html?dir=ries&side=start:202
Blockly.Block@block.js:173
Blockly.BlockSvg@block_svg.js:100
Blockly.WorkspaceSvg.newBlock@workspace_svg.js:580
Blockly.Xml.domToBlockHeadless_@xml.js:632
Blockly.Xml.domToBlock@xml.js:547
Blockly.Flyout.show@flyout_base.js:451
Blockly.Toolbox.refreshSelection@toolbox.js:493
Blockly.WorkspaceSvg.refreshToolboxSelection@workspace_svg.js:1077
Blockly.WorkspaceSvg.createVariable@workspace_svg.js:1120
(anonymous)@playground.html?dir=ries&side=start:182
init@playground.html?dir=ries&side=start:178
Blockly.Block@block.js:173
Blockly.BlockSvg@block_svg.js:100
Blockly.WorkspaceSvg.newBlock@workspace_svg.js:580
Blockly.Xml.domToBlockHeadless_@xml.js:632
Blockly.Xml.domToBlock@xml.js:547
Blockly.Flyout.show@flyout_base.js:451
Blockly.Toolbox.refreshSelection@toolbox.js:493
Blockly.WorkspaceSvg.refreshToolboxSelection@workspace_svg.js:1077
Blockly.WorkspaceSvg.createVariable@workspace_svg.js:1120
(anonymous)@playground.html?dir=ries&side=start:182
init@playground.html?dir=ries&side=start:178
...

The problem is that when createVariable is called in your init function in the flyout, it makes the flyout refresh.  That clears the flyout, including its variables, so it when it repopulates it creates those variables again, and so on.  

To fix this, call Blockly.Variables.getOrCreateVariablePackage  in your varsToCreate loop, rather than using getVariable and createVariable directly.  Blockly has a concept of a "potential variable" to solve exactly this problem, and that function handles all the messy details. It's also what the variable field uses to get around this problem.

Here's the code, with the variable taking the default type (empty string):
varsToCreate.forEach(function(varToCreate){
                      let varName
= varToCreate.varName;
                      let key
= varToCreate.key;

                     
this[key] = Blockly.Variables.getOrCreateVariablePackage(this.workspace, null, varName, '');
                 
}.bind(this));

The function is named *Package because I didn't expect it to be used much by external developers, but this is a reasonable case for it.

Cheers,
Rachel

Amber B

unread,
Dec 4, 2018, 5:28:51 PM12/4/18
to Blockly
That fixed it! Thank you so much!

Rachel Fenichel

unread,
Dec 4, 2018, 7:16:23 PM12/4/18
to Blockly
I can probably add a check on createVariable to see if it's in a flyout workspace, and either error or warn and say what to do instead.  That should help anyone else who runs into this in future.

Amber B

unread,
Dec 5, 2018, 9:00:47 AM12/5/18
to Blockly
That would definitely be good. As an update, the other block that was causing an issue had a different console error but actually turned out to be resolved by the same solution (e.g. the use of getOrCreateVariablePackage), so all of our drawers are now loading without incident. Thanks again for your help!
Reply all
Reply to author
Forward
0 new messages