A common question is how to modify the definition of an existing block. For example, you might want to add a connection check or change a field to a value input.
In general, it is not possible to modify a block definition in place.
How to modify an existing definition
If you want to modify the definition of an existing block type:
- Make a copy of the existing definition, including block-code generators.
- Modify the copy and give your type a new name.
- Add your new definition to
Blockly.Blocks
.
Why can't I directly modify an existing definition?
If you're curious as to why you can't modify an existing definition, read on. We'll consider some possibilities.
Modify an existing definition directly
There are two ways to directly modify the definition of an existing block: monkeypatching and forking the code. Both are strongly discouraged, as you run the risk of breaking code that depends on the monkeypatched or forked code. Both techniques also make it difficult to integrate updates and bug fixes. For more information, see What about monkeypatching? and Fork Blockly.
Subclass an existing definition
It might occur to you to somehow subclass an existing definition. Unfortunately,
this isn't possible because block definitions aren't classes -- they're mixins.
There is, for example, no way to overwrite a colour property because the
definition does not have a colour property. Instead, it has an init
function
that calls setColour
to set the colour property on the block. Because the call
is inside init
, there is no way to replace it without replacing the entire
init
function.
Overwrite a function in an existing definition
It is possible to overwrite a function in an existing definition:
Blockly.Blocks['existing_block'].init = function() {/*new function*/};
This works, but you have to copy and modify the existing function -- you can't magically replace just one line. There are several problems with this:
- It isn't much different from copying and modifying the entire definition.
- You can't use it to modify the
init
function of blocks that are defined in JSON, such as Blockly's built-in blocks. This is because there is noinit
function to copy -- it's generated at run time. - It requires you to define your block using JavaScript, which may cause problems with localization.
Overwrite the results of init
One way to "replace just one line" of an init
function is to replace the
init
function with a function that calls the original init
function and then
overwrites the result of that call. For example, the following code changes the
color of the logic_null
block:
const originalInit = Blockly.Blocks['logic_null'].init;
Blockly.Blocks['logic_null'].init = function() {
originalInit.call(this);
this.setColour(300);
}
Unfortunately, this is less useful that it seems. For example:
Broadening connection checks or applying a less-restrictive field validator may invalidate assumptions made by block-code generators and event handlers.
Replacing a field with a value input will break block-code generators and field validators and may break event handlers. It may also be extremely difficult to do for localized blocks because different locales may result in blocks with different types and orders of inputs and fields.
Overwrite a key-value pair in a JSON definition
If the JSON for a block is publicly available, it may be possible to overwrite individual JSON values. For example:
// Block definition.
blockJson = {...};
Blockly.Blocks['my_block'] = {
init: function() {
initJson(blockJson); // Called when the block is created.
}
}
// Third-party code.
blockJson.colour = 100;
However, this only works if the JSON object is publicly available, the
definition explicitly defines the init
function, and the init
function calls
initJson
. It doesn't work if the JSON is passed to defineBlocksWithJsonArray
or createBlockDefinitionsFromJsonArray
because the JSON is processed before a
third party has a chance to modify it. (Note that Blockly's built-in blocks use
createBlockDefinitionsFromJsonArray
.)
But what if the JSON isn't defined this way? Shouldn't it still be possible to
overwrite JSON properties? Unfortunately, no. The definition contains an init
function (not JSON) and there is no function to convert an init
function to
JSON.
// Doesn't work. There is no getJson() function.
const json = Blockly.Blocks['existing_block'].getJson();
json['message0'] = 'my new message0';
Blockly.Blocks['existing_block'].init = function () {
initJson(json);
};
Design for reuse
As you design your own custom blocks, you may be able to design them in ways that promote reuse.
Reuse JSON
If you have two blocks that are substantially similar, you can create a parent JSON definition and reuse this in child definitions. For example:
const parentJson = {
// shared properties
};
Blockly.Blocks['child_block_1'] = {
init: function() {
initJson({...parentJson, colour: 100})
}
}
Blockly.Blocks['child_block_2'] = {
init: function() {
initJson({...parentJson, colour: 200})
}
}
Another alternative is to define your JSON in a publicly available object and
pass that object to initJson
in your init
function. This makes it possible
for others to overwrite individual properties. For more information, see
Overwrite a key-value pair in a JSON
definition.
Reuse functions
Blocks may define a number of standard functions, such as block-level event handlers, custom tooltips, field validators, and the functions used by mutators, as well as functions that provide custom behavior, such as a function that sets field values from external data, like the current position of a robot's arm.
It may be possible to reuse these functions across blocks.
Use a dropdown field
If you have a set of blocks that are substantially the same except for an operator, you may be able to design a single block that has a dropdown field for the operator. For example:
- The built-in
logic_operation
block uses a dropdown with the operatorsand
andor
. - The built-in
math_arithmetic
block uses a dropdown with the operators+
,-
,×
,÷
, and^
.
Writing code generators for such blocks is usually a bit more complex, but still easier than writing and maintaining multiple blocks.
Use a mutator
If you have a set of blocks that represent different variations of the same
programming structure, you may be able to create a single block that uses a
mutator. For example, the
built-in controls_if
block can represent multiple variations of if-then-else
statements.