Skip to content

Blockly

Created: 2018-04-16 14:11:49 -0700 Modified: 2020-02-03 15:13:01 -0800

  • Order of operations for a language are denoted by ORDER_ATOMIC, ORDER_FUNCTION_CALL, etc. You can find these in something like javascript.js in the official repo.
  • Blocks can be editable, movable, and deletable. Unfortunately, if you set a block as uneditable, then it won’t be obvious that it can’t be edited, so users may think there’s an error on their end.
  • For the toolbox, if you want a category to be expanded by default, you can modify the XML to include the “expanded” attribute, e.g.
    • <category name=“Mission-specific snippets” colour=“120” expanded=“true”>

For most actions in Blockly, there are events that get fired so that you can react to them.

I tried adding an event listener for when the toolbox resized, but there doesn’t seem to be one. There are events some of the time, but not all of the time, so I just ended up using a setInterval call to figure out when the toolbox changed sizes.

Note that eventually, for this specific use-case of trying to see when an element resizes, I could eventually use ResizeObserver (reference), but that’s currently only on Chrome (so there’s a polyfill in the meantime (reference)).

At one point in early 2020, I wanted to be able to detect when the user had stopped dragging a block, but I couldn’t find any way to do this. The Blockly team did add dragStop (reference), but the only way that’s accessible is through BlockDragger (which has a function for fireDragEndEvent), and the only way you get one of those is via Gesture, which only gets created by an event (and then it’s sort of a singleton until it gets disposed).

this.state.workspace.addChangeListener is not suitable for detecting changes the way I want since it triggers while the user is dragging.

Checking if the flyout’s visibility changed

Section titled Checking if the flyout’s visibility changed

This uses events to get us a little closer to whether the visibility changed, but there isn’t an event specific to the visibility changing, so we check to see if you clicked a category and then inspect the visibility ourselves:

blocklyChangeListener(event) {
const { type } = event;
if (type === Blockly.Events.UI && event.element === 'category') {
// Use blocklyWorkspace.getFlyout_().isVisible();
}
}

Mutators are how you can change a block’s basic functionality/shape:

For example, if you want a variable number of arguments, you can use a mutator. Examples of blocks like this are:

If you want to be able to reuse the mutator in multiple blocks, you can register the mutator as was done in blocks/logic.js (reference)

I did this in Bot Land once so far:

Blockly.Blocks['helper_filter_entities'] = {
init: function() {
this.appendValueInput('ENTITIES')
.setCheck('Array')
.appendField('filter entities');
this.appendDummyInput()
.appendField('by');
this.appendDummyInput()
.appendField(new Blockly.FieldDropdown(entitySortOrderOptions), 'SORT_ORDER');
this.appendDummyInput()
.appendField(' ');
this.appendDummyInput()
.appendField(new Blockly.FieldDropdown(entitySortByOptions), 'SORT_BY');
this.appendDummyInput()
.appendField(' ');
const validationFunction = function(useSecondFilter) {
// TODO: figure out how this can be null (the docs say 'unless the
// value is null, in which case the change is aborted').
this.sourceBlock_.updateShape_(useSecondFilter);
};
this.appendDummyInput()
.appendField(new Blockly.FieldCheckbox('FALSE',
validationFunction), 'TEST_CHECKBOX')
.appendField('use second filter', 'useSecondFilterText');
this.setInputsInline(true);
this.setOutput(true, ['BOT', 'CHIP']);
this.setColour(290);
this.setTooltip('');
this.setHelpUrl('');
},
/**
* Create XML to represent whether the 'divisorInput' should be present.
* @return {Element} XML storage element.
* @this Blockly.Block
*/
mutationToDom: function() {
const container = document.createElement('mutation');
const useSecondFilter = util.isTrueOrStringTrue(this.getFieldValue('TEST_CHECKBOX'));
container.setAttribute('use_second_filter', useSecondFilter);
return container;
},
/**
* Parse XML to restore the 'useSecondFilter'.
* @param {!Element} xmlElement XML storage element.
* @this Blockly.Block
*/
domToMutation: function(xmlElement) {
const useSecondFilter = (xmlElement.getAttribute(
'use_second_filter') == 'true');
this.updateShape_(useSecondFilter);
},
/**
* Modify this block to have (or not have) a section for the second
* filter options.
* @param {boolean} useSecondFilter True if this block has a second
* filter
* @private
* @this Blockly.Block
*/
updateShape_: function(useSecondFilter) {
const inputExists = this.getInput('secondFilter');
const useSecondFilterText = this.getField('useSecondFilterText');
if (useSecondFilter) {
if (!inputExists) {
useSecondFilterText.setText('');
this.appendDummyInput('secondFilter')
.appendField('then by')
.appendField(new Blockly.FieldDropdown(entitySortOrderOptions), 'SORT_ORDER_2')
.appendField(' ')
.appendField(new Blockly.FieldDropdown(entitySortByOptions), 'SORT_BY_2');
}
} else if (inputExists) {
this.removeInput('secondFilter');
useSecondFilterText.setText('use second filter');
}
},
};
Blockly.JavaScript['helper_filter_entities'] = function(block) {
let entities = Blockly.JavaScript.valueToCode(block, 'ENTITIES', Blockly.JavaScript.ORDER_ATOMIC);
if (entities === '') {
entities = 'null';
}
const sortByArray = [];
const sortOrderArray = [];
sortByArray.push(block.getFieldValue('SORT_BY'));
sortOrderArray.push(block.getFieldValue('SORT_ORDER'));
const inputExists = block.getInput('secondFilter');
if (inputExists) {
sortByArray.push(block.getFieldValue('SORT_BY_2'));
sortOrderArray.push(block.getFieldValue('SORT_ORDER_2'));
}
// We can't just do a JSON.stringify here because then the strings will
// have quotations around them, but we want them to be code API
// constants.
const sortByString = `[${sortByArray.join(', ')}]`;
const sortOrderString = `[${sortOrderArray.join(', ')}]`;
const code = `filterEntities(${entities}, ${sortByString}, ${sortOrderString})`;
return [code, Blockly.JavaScript.ORDER_FUNCTION_CALL];
};

Functions from the toolbox do not result in code changes

Section titled Functions from the toolbox do not result in code changes

Quick summary:

  • This is a bug in your code, not Blockly’s
  • This is because functions are collected in Blockly[YOUR_LANGUAGE].definitions_ to be emitted later from Blockly[YOUR_LANGUAGE].finish
  • I wrote a note only visible to myself about the full write-up here.