How to find child blocks of a block

1,383 views
Skip to first unread message

Andrew Stratton

unread,
Dec 27, 2017, 2:27:19 PM12/27/17
to Blockly
Hi Everyone
  Does anyone know of an easy way to get the children of a block - so that I can generate the code from each child block individually?

  e.g. I have a statement block in a custom 'pick one at random' block. The statement block contains two blocks - one each for 'heads' and 'tails'.  I want to generate the separate code for the heads block and the tails, embed them in anonymous functions and store the functions in an array.  Then, at run time, a random number can be generated to pick one of the two functions to run. If the 'pick' block was in a loop, this could generate different sequences of heads and tails...

  I could do this by having the generated blocks see if their parent is a 'pick' block, but this means adding behaviour to every block - and doesn't feel like the right way to do this.

  Best wishes
    Andy

picklesrus

unread,
Dec 27, 2017, 4:26:01 PM12/27/17
to Blockly
There is Block's getChildren method. 

Can the statement blocks inside the "pick one at random" function connect to any blocks outside a "pick one at random" block?

Andrew Stratton

unread,
Dec 28, 2017, 3:43:17 AM12/28/17
to Blockly
Hi picklesrus - I tried getChildren already - but it didn't let me iterate through the children...it returned an array with one child in it, itself containing the next child - so I can't get the code for the first child by itself...maybe that's a bug?

You asked - 'Can the statement blocks inside the "pick one at random" function connect to any blocks outside a "pick one at random" block?'

Yes - they can - how would this affect a solution?

Cheers
  Andy

Andrew Stratton

unread,
Dec 28, 2017, 4:29:53 AM12/28/17
to Blockly
Is this a bug?
My block looks like this:


In my javascript (generator) for 'Pick one', I have

let child = block.getChildren()[0]


getChildren() (correctly) finds the statement container and returns it in an array as the first element (in case there are multiple statements - e.g. like and if else).

But the child is the first block, with the next block attached to it - so if I then call blockToCode on this block, i get ALL the children's code - not just the first child's.

I can see why a block at the top of a sequence might return all the next blocks - but I need to insert something between each block to put them in an array - which I can't do this way. I also can't traverse the tree, since I don't know any way to get the code from the top block, without getting the code for any next blocks as well. i.e. I could get Show Text code by itself, but not Show Title code...

Any thoughts anyone?

Andrew Stratton

unread,
Dec 28, 2017, 4:16:03 PM12/28/17
to Blockly
Solved - sigh - just call the javascript code direct, e.g. if block is the block found (say by getChildren()[0]), then just call

Blockly.JavaScript[block.type].call(block, block)


This returns the code for the specific block - to get the next block in line, use block.getChildren()[0] until (block.getChildren() is) null.

It would be useful for this to be a standard part of Blockly - but not essential...

Merry Xmas
  Andy

Rachel Fenichel

unread,
Dec 28, 2017, 5:22:53 PM12/28/17
to Blockly
Hi Andy,

There are a couple of different solutions to this one, all of which involve making your generators a little more complicated--which is totally fine.  For instance, if you wanted all of your code to come out as arrays of lines of code, instead of concatenated, you would be able to do that by making your own generator file (which would replace javascript.js) and overriding functions as needed.  Mixing the two paradigms is a little bit more complicated, as you've found.

For the way that you're doing it, here are some tips to make it more robust:
- Don't use getChildren, because it also includes blocks connected to value inputs.  Instead, get the block connected to your statement input with getInputTargetBlock.  Then loop on it while the block has a next connection and the next connection has a target block:

var resultArray = [];
var currentBlock = this.getInputTargetBlock('input_name');
while (currentBlock) {
  var codeForBlock = getCodeForSingleBlock(currentBlock);
  resultArray.push(codeForBlock);
  currentBlock = currentBlock.getNextBlock();
}
// Do stuff with resultArray.

- You can create named functions in your generated code with provideFunction_.  Here's an example use by one of the color blocks: it creates a named function, and the generated code for the block is just a call to that function.  It sounds like you want something similar with your anonymous functions.

- Calling the generator function for the individual block is fine, but you can add some of the safety checks that the blockToCode does by default.  You basically want all parts of blockToCode except for the calls to scrub_, which are making it include the code for following blocks in the stack.  Something like this (not tested): 
/**
 * Generate code for the specified block but not following blocks.
 * @param {Blockly.Block} block The block to generate code for.
 * @return {string|!Array} For statement blocks, the generated code.
 *     For value blocks, an array containing the generated code and an
 *     operator order value.  Returns '' if block is null.
 */
function getCodeForSingleBlock(block) {
  if (!block) {
    return '';
  }
  if (block.disabled) {
    // Skip past this block if it is disabled.
    return getCodeForSingleBlock(block.getNextBlock());
  }

  var func = Blockly.JavaScript[block.type];
  goog.asserts.assertFunction(func,
      'Language "%s" does not know how to generate code for block type "%s".',
      'JavaScript', block.type);
  // First argument to func.call is the value of 'this' in the generator.
  // Prior to 24 September 2013 'this' was the only way to access the block.
  // The current prefered method of accessing the block is through the second
  // argument to func.call, which becomes the first parameter to the generator.
  var code = func.call(block, block);
  if (goog.isArray(code)) {
    // Value blocks return tuples of code and operator order.
    goog.asserts.assert(block.outputConnection,
        'Expecting string from statement block "%s".', block.type);
    return [code[0], code[1]];
  } else if (goog.isString(code)) {
    var id = block.id.replace(/\$/g, '$$$$');  // Issue 251.
    if (this.STATEMENT_PREFIX) {
      code = this.STATEMENT_PREFIX.replace(/%1/g, '\'' + id + '\'') +
          code;
    }
    return code;
  } else if (code === null) {
    // Block has handled code generation itself.
    return '';
  } else {
    goog.asserts.fail('Invalid code generated: %s', code);
  }
};

Cheers,
Rachel + pickles
Message has been deleted

Andrew Stratton

unread,
Dec 29, 2017, 7:37:06 AM12/29/17
to Blockly
Thanks Rachel - I went all round the houses pulling out the child that had the right parent...

I now have this solution - here's an example set of blocks:

This generates this - which works :) 

quando.every(1, function() {
  var a=[function(){
  quando.image_update_video("/client/media/traffic lights/red_light.png");
  quando.setDefaultStyle('#quando_image', 'background-image', 'url("/client/media/traffic lights/red_light.png")');
  },
  function(){
  quando.image_update_video("/client/media/traffic lights/green_light.png");
  quando.setDefaultStyle('#quando_image', 'background-image', 'url("/client/media/traffic lights/green_light.png")');
  }]
  var i = Math.floor(Math.random() * a.length)
  if (i == a.length) { i-- }
  a[i]()
}, false);
I will put this in an api call - to simplify it (like the quando.every, etc. calls) - but it's easier to read by itself.

The generator looks like this (please ignore my JSON like constructor):
    let PICK_RANDOM_BLOCK = 'Pick one at Random'
    
self.defineAdvanced({
      name
: PICK_RANDOM_BLOCK,
      
interface: [
        
{ statement: STATEMENT }
      
],
      javascript 
: (block) => {
        let stateBlock 
= block.getInputTargetBlock(STATEMENT)
        let arr 
= 'var a=[' + _getIndividualChildCode(stateBlock, 'function(){\n', '}', ',\n') + ']'
        
return `${arr}\nvar i = Math.floor(Math.random() * a.length)\nif (i == a.length) { i-- }\na[i]()\n`
      
}
    
})

Where I've defined function to do the individual block code generation (thank you Rachel for the optimisations):

    function _getIndividualChildCode(start, prefix, postfix, separator) {
      let result 
= ''
      let child 
= start
      
while (child != null) {
        let code 
= quando_editor.getIndividualBlockCode(child)
        
if (result != '') {
          result 
+= separator
        
}
        result 
+= prefix + code + postfix
        child 
= child.getNextBlock()
      
}
      
return result
    
}

And the getIndividualBlockCode looks like this (some error checking included...thank you again Rachel)

    self.getIndividualBlockCode = (block) => {
        let result 
= ''
        
if (block && !block.disabled) {
            let javascript 
= Blockly.JavaScript[block.type]
            
if (javascript) {
                result 
= javascript.call(block, block)
            
} else {
                result 
= `Javascript missing for ${block.type}`
            
}
        
}
        
return result
    
}

Phew - now just the tidying up to do - i.e. creating an api call that makes sense.

Thank you Rachel and Pickles
  Andy

Jason Morris

unread,
Sep 10, 2019, 1:13:26 AM9/10/19
to Blockly
Rachel:

Would it be possible to get an updated version of this example that doesn't use goog?

Jason

Neil Fraser

unread,
Sep 10, 2019, 7:12:13 AM9/10/19
to blo...@googlegroups.com
On Mon, 9 Sep 2019 at 22:13, Jason Morris <ja...@roundtablelaw.ca> wrote:
Rachel:

Would it be possible to get an updated version of this example that doesn't use goog?

Not tested, but should work.  Changes in bold:

/**
 * Generate code for the specified block but not following blocks.
 * @param {Blockly.Block} block The block to generate code for.
 * @return {string|!Array} For statement blocks, the generated code.
 *     For value blocks, an array containing the generated code and an
 *     operator order value.  Returns '' if block is null.
 */
function getCodeForSingleBlock(block) {
  if (!block) {
    return '';
  }
  if (block.disabled) {
    // Skip past this block if it is disabled.
    return getCodeForSingleBlock(block.getNextBlock());
  }

  var func = Blockly.JavaScript[block.type];
  if (typeof func != 'function') {
    throw Error('Language "JavaScript" does not know how to generate ' +
        'code for block type: ' + block.type);
  }
  // First argument to func.call is the value of 'this' in the generator.
  // Prior to 24 September 2013 'this' was the only way to access the block.
  // The current preferred method of accessing the block is through the second
  // argument to func.call, which becomes the first parameter to the generator.
  var code = func.call(block, block);
  if (Array.isArray(code)) {
    // Value blocks return tuples of code and operator order.
    if (!block.outputConnection) {
      throw Error('Expecting string from statement block: ' + block.type);
    }
    return [code[0], code[1]];
  } else if (typeof code == 'string') {
    var id = block.id.replace(/\$/g, '$$$$');  // Issue 251.
    if (this.STATEMENT_PREFIX) {
      code = this.STATEMENT_PREFIX.replace(/%1/g, '\'' + id + '\'') +
          code;
    }
    return code;
  } else if (code === null) {
    // Block has handled code generation itself.
    return '';
  } else {
    throw Error('Invalid code generated: ' + code);
  }
};

Hope this helps.

Reply all
Reply to author
Forward
0 new messages